-
- Notifications
You must be signed in to change notification settings - Fork 143
Description
I know this has been discussed in #587, but even though there’s the limitation that the server (by necessity) has to know the client’s password, there are better methods than storing that password in plain text. I explain.
Is your feature request related to a problem? Please describe.
The Subsonic API design is inherently limited regarding modern security practices. Because it requires the server to validate a token (created by the client using md5(password + salt)), the server must have access to the user's plaintext password to recreate the hash and verify the request. Currently, this often leads to storing passwords in plaintext or reversible formats in the database, which is a significant security risk if the database is compromised.
Describe the solution you'd like
I propose implementing a Symmetric Encryption layer (specifically AES-256-GCM) for storing user credentials. The flow would be:
- Introduce an optional environment variable, e.g.,
GONIC_MASTER_KEY(a 32-byte string). - When a user sets or updates their password, Gonic encrypts it using this Master Key and a unique random nonce (IV) before saving it to the DB. This random nonce can be stored in database with username and password.
- For Subsonic API calls: Gonic retrieves the encrypted blob, decrypts it in memory using the GONIC_MASTER_KEY, performs the token validation, and immediately clears the plaintext from memory.
Technical Implementation Details
Using crypto/aes and crypto/cipher in Go, we can implement Authenticated Encryption (GCM) to ensure both confidentiality and integrity.
// Example of how the encryption could be handled func encrypt(plaintext []byte, key []byte) ([]byte, error) { block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } // Concatenate nonce + ciphertext return gcm.Seal(nonce, nonce, plaintext, nil), nil }Implementation Strategy (Backwards Compatibility)
To avoid breaking existing installations, I suggest the following:
- Default Key: If
GONIC_MASTER_KEYis not set, use a hardcoded internal string (clearly documented as insecure). - Auto-Detection: When fetching a password from the DB:
Try to decrypt it using theGONIC_MASTER_KEY.
If decryption fails (e.g., due to an invalid GCM tag), assume it's an old plaintext password for compatibility. - Migration: On every successful login, if the password was in plaintext, automatically encrypt it and save it back to the DB using the Master Key.
- Warning: Display a startup warning if the default key is being used, encouraging users to set their own.
Side note: Users must be warned that losing the
GONIC_MASTER_KEYmeans all Subsonic-compatible passwords stored in the database will be lost and must be reset manually.
Describe alternatives you've considered
- Scrypt/Bcrypt: Not viable for Subsonic's token/salt authentication as they are one-way hashes.
- Plaintext: Current (or common) state, but risky for production environments.
Additional context
This approach follows the principle of "Separation of Concerns": even if an attacker gains access to the database (SQL injection or backup leak), they cannot recover any passwords without the GONIC_MASTER_KEY stored in the server's environment variables.