Fix a buffer overflow in password encryption, and allow arbitrary password length.
[claws.git] / doc / src / password_encryption.txt
index aed0cb0..f618378 100644 (file)
@@ -2,29 +2,37 @@ Unless --with-password-encryption=old is active, account passwords are
 stored encrypted using AES-256-CBC, using following scheme:
 ----------------------------------------------------------------------
 
-Encryption/decryption key is either PASSCRYPT_KEY, or user-selected master password.
+Encryption/decryption key is derived from either PASSCRYPT_KEY, or
+user-selected master passphrase, using PBKDF2, using salt from
+'master_passphrase_salt', and number of rounds (iterations) from
+'master_passphrase_pbkdf2_rounds'.
 
-We take the digest of the key using SHA-512, which gives us a 64 bytes
-long hash.
-
-The first half of the hash is XORed with the second (1st byte with
-33rd, 2nd with 34th, etc.). This is gives us 32 bytes, which is
-ey length required for AES-256-CBC.
-
-IV for the cipher is filled with random bytes.
+IV (initialization vector) for the cipher is filled with random bytes.
 
 
 Encryption
 ----------
-
-We prepare a buffer 128+blocksize bytes long, with one block of random
-data at the beginning, followed by the password we want to encrypt,
-rest is padded with zero bytes.
-
-We encrypt the buffer.
+We prepare a buffer long enough to fit the NULL-terminated password string
+plus one cipher block in it, with one block of random data at the beginning,
+followed by the password we want to encrypt (in UTF-8), rest is padded
+with zero bytes.
+
+The minimal buffer size is 128+blocksize, and if the password (including
+the trailing NULL byte) is longer than 128 bytes, the size is increased by
+another 128 bytes until it is long enough to fit the password plus one
+cipher block. This is to make it harder to guess the password length from
+length of the encrypted string. So for example, if the password (again,
+including the trailing NULL byte) is 129 characters long, our buffer will
+be 256+blocksize bytes long.
+
+We encrypt the buffer using the encryption key and IV mentioned above,
+resulting in ciphertext of the same length as the buffer.
 
 We base64-encode the ciphertext, and store it as:
-"{algorithm}encodedciphertext"
+"{algorithm,rounds}encodedciphertext"
+
+"rounds" is an integer value set to number of PBKDF2 rounds used to
+generate the key derivation used as encryption key.
 
 
 Decryption
@@ -32,16 +40,32 @@ Decryption
 We strip the "{algorithm}" (after verifying that it matches what we
 expect) and base64-decode the remaining ciphertext.
 
-We decrypt the ciphertext.
+We decrypt the ciphertext using decryption key and IV mentioned above,
+resulting in plaintext of the same length as the ciphertext.
 
-We discard the first block, and the rest is a zero-terminated string
-with our password.
+We discard the first block from plaintext, and the rest is a
+zero-terminated string with our password in UTF-8.
 
 
 Why the random block at the beginning?
 --------------------------------------
-
 We are taking advantage of property of CBC mode where decryption with
 a wrong IV results in only first block being garbled. Therefore we
 prepend a random block to our plaintext before encryption, and discard
 first block from plaintext after decryption.
+
+
+Master passphrase
+-----------------
+This can be any string user chooses. We store its 64 bytes long key
+derivation (KD), using PBKDF2 with HMAC-SHA1, and later check correctness
+of user-entered passphrase by making same KD from it and comparing it
+to the stored one. Only if the two KDs match, the passphrase is accepted
+and remembered for session, thus giving access to account or plugin
+passwords.
+
+Salt used for PBKDF2 is stored in 'master_passphrase_salt', encoded
+as base64. It consists of 64 randomly generated bytes.
+
+Number of rounds for PBKDF2 is stored in hidden preference
+'master_passphrase_pbkdf2_rounds'.