0
$\begingroup$

I want to store passwords in a database in a way I can reuse them to authenticate users (symmetrical encryption).

I read some good information here: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#cryptographic-storage-cheat-sheet

After reading, here is what I (think I) need:

  • Symmetrical encryption (not hashing),
  • Store my encryption key in my application securely (will be another subject),
  • Use salt,
  • Use encryption versioning and key rotation,
  • No need to guarantee integrity and authenticity of the data: if my password is corrupted and wrong, that's fine for my use case,
  • The encrypted data should not be the same length as the password (if it's 8 character-long, the potential attacker should not know),
  • The logic should be in my code (do not depend on the database encryption features)

I found that AES encryption should work for my use case, but I'm not sure about the cipher method. Apparently, CTR provides the same data length before and after encryption. CBC doesn't, but it's said to add complexity to the code. Is AES fine for my use case? If yes, what cipher method should I use (CBC, CTR, GCM, CCM...)?

Edit 2022-10-03: I do need to retrieve the clear information. To give more context: I store one public/private key file per user and each file is password-protected. Once the user is logged in to my service, my service does the authentication itself to other services so the user doesn't have to manage his private key.

Edit 2022-10-04: Several users may share the same PKCS#12 which is actually used to authenticate a group. Users are not necessarily aware of the PKCS#12 password.

$\endgroup$
4
  • 2
    $\begingroup$ The source quoted is clear, and correct: "Passwords should not be stored using reversible encryption - secure password hashing algorithms should be used instead. The Password Storage Cheat Sheet contains further guidance on storing passwords". That makes many of the question's bullet points wrong, including the first. $\endgroup$
    – fgrieu
    Oct 3, 2022 at 14:44
  • $\begingroup$ "In the context of password storage, encryption should only be used in edge cases where it is necessary to obtain the original plaintext password. This might be necessary if the application needs to use the password to authenticate with another system that does not support a modern way to programmatically grant access, such as OpenID Connect (OIDC)." This is exactly my use case: I actually need to obtain the original password. $\endgroup$
    – Merinorus
    Oct 3, 2022 at 14:54
  • $\begingroup$ I don't get "I use passwords to unlock certificates"; much less how that matches "reuse (passwords) to authenticate user". Certificates are generally public, and it's unclear what "locked" means in their context. $\endgroup$
    – fgrieu
    Oct 3, 2022 at 15:02
  • 1
    $\begingroup$ This is actually a password-protected pkcs#12 file that contains a public key and a private key. $\endgroup$
    – Merinorus
    Oct 3, 2022 at 15:09

2 Answers 2

2
$\begingroup$

From comments, we learn that the application is storage of password-protected PKCS#12 private keys. Best practice for this would not be symmetric encryption of the passwords protecting the PKCS#12 private keys; but standard password hashing (e.g. Argon2), and re-encrypting the PKCS#12 private keys under a new key that's a byproduct of Argon2.

For example (many variations are possible):

  • on enrollment of a user, we get their PKCS#12 private key and original password, and check that the original password allows decryption of the PKCS#12 file; we give the opportunity to the user to keep that password, or enter a new one as current password;
  • we generate random salt; Argon2-hash current password, salt, and pepper (replacement of the question's "encryption key"), with
    • the first part of the resulting hash (say, 12 bytes base64 encoded to 16 chars) used as key for re-encrypting the PKCS#12 private key (which we somewhat store; it's still PKCS#12-conformant, but it's key is strong)
    • the other part is stored (along the salt and Argon2 parameters) in the database, as usual in password encryption.
  • when we need to authenticate a user, we ask the user password, retrieve the salt and Argon2 parameters from the database, recompute the Argon2 hash, use the other half stored in the database to validate the password, then the first half can be used if the PKCS#12 private key is needed.

The advantages of such method are that after leak of the database content, pepper, and encrypted PKCS#12 certificates, the current user password is still a line of defense, with Argon2 for the key stretching. Also, that user password can be meaningfully updated.


Now if we ignore that advice and want to store a password symmetrically encrypted in a database without using the database encryption feature, and without leaking the password's length, that's possible (if not optimum). We define a maximum password length, pad the password to that length (e.g. with zero bytes removed after decryption), and use a standard authenticated encryption method with the user ID (or it's hash) as Additional Authenticated Data. AES-GCM will do if the random IV (which replaces salt) is generated using a secure TRNG; if in doubt use AES-GCM-SIV.

Note: I suggest using authenticated encryption and entering the user ID as AAD in order to avoid an attack that substitutes an encrypted version of an unknown password with the encrypted version of a known password.

$\endgroup$
1
  • $\begingroup$ Thank you very much! I forgot to say a PKCS#12 authenticates actually a group of users. Several users may share a PKCS#12 private key and are not necessarily aware of the password. Therefore the second solution seems perfectly doable. The first solution, while more secure (and smart!), seems harder to implement in my case. $\endgroup$
    – Merinorus
    Oct 4, 2022 at 7:34
1
$\begingroup$

First note that it isn't said that PKCS#12 should be protected by a password; it is fully valid to protect it with a key instead. It does seem that OpenSSL requires a password, for what it is worth and this is probably the most compatible option.

If your user is already logging in using a password one might wonder why a (hex encoded) key derived from that password isn't used to directly perform the encryption within PKCS#12. Also, how are you going to encode that secret key? You could use PKCS#12 for that, but it seems a bit weird to have two PKCS#12 containers.


But let's assume that this double encryption is necessary. In that case, yes, I'd use AES to encrypt. Which mode is rather inconsequential if you don't need integrity protection / authentication, as long as it isn't ECB. You could use AES-CTR for a simple option and AES-SIV or AES-GCM-SIV with a random nonce in the associated data as complex option. You will need to store a randomized IV or nonce, otherwise (partially) identical passwords will encrypt to the same ciphertext values.

With regards to the size of the password: if you directly encrypt the password then you should probably pad before applying the encryption mode, i.e. without relying on the cipher to do this for you. To leak none of the size, determine a maximum size and always (zero) pad to that size (say 128 bytes to allow for pass phrases). You can then unpad or trim to the right size after decryption.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.