0

I am using PBKDF2 on NodeJS to authenticate a user who submits username & password. To do this, when the user first registers, I generate a salt, I use 200000 iterations, I specify a key length of 32 bytes, and I use sha256 as the digest method. With the output of that operation, I store a serialized JSON object containing the derived key, hex-encoded salt, quantity of iterations and the digest method. This works for authenticating subsequent login attempts.

However I am now interested in using the user's same password to encrypt certain data, in addition to authenticating the username and password.

I'm not sure how to do this, since storing the derived key from PBKDF2 obviously exposes that key.

What is a secure way of doing this? Do I have to just run PBKDF2 a second time and use a different salt? Seems like I'm needlessly doubling the amount of time by doing that.

Is there a better way?

vrtjason
  • 1,045
  • 9
  • 10

1 Answers1

0

You can't directly use the user's password as the encryption key, or when the user changes the password, you have to re-encrypt everything or the user loses access to all his previous data.

You can generate a random key, and use less iterations on PBKDF2 to generate a KEK (Key-Encription Key). Use this KEK to encrypt the random key. When the user changes the password, you only have to decrypt the symmetric key, generate the new key with the new password, and re-encrypt the symmetric key. This way you store the encrypted KEK, and need the user's password to decrypt it.

Massive downside of this: when the user forgets its password, all his data is lost forever.

You could solve this problem but create a whole new one by using one internal key to encrypt a copy of the symmetric key. That means that if the user forgets the password you can decrypt the symmetric key for him (solve a problem), and if your internal key leaks, every single encrypted file can be now compromised (create a bigger problem).

You can use fewer iterations without compromising security because you will never expose the KEK.

ThoriumBR
  • 50,648
  • 13
  • 127
  • 142
  • If I wrap the DEK with the KEK, then how would I recognize a failed login attempt? Would I attempt decryption of the wrapped DEK within a try/catch block? As for the scenario of the user forgetting his password, I weighed this possibility. I was thinking of having an application-wide keypair, with the private key wrapped by the admin's password. The public key could encrypt the DEK. In this way, if the user ever forgot his password, he could file a data-recovery ticket with the admin. Just an idea, I haven't tried it yet. – vrtjason Aug 27 '21 at 05:34
  • The login attempt will be validated before that. User tries to log in, you password storage routine checks that. If the login is valid, you use the password to decrypt the symmetric key. – ThoriumBR Aug 27 '21 at 10:42
  • How can the login attempt be validated before unwrapping the DEK? Wouldn't the database need to store the KEK (meaning, the key derived directly from the user's password), which could be compared to the output of a PBKDF2 operation, for authentication purposes. But leaving the KEK exposed like this in the database makes it trivial for a hacker to unwrap the DEK without even considering the user's password. – vrtjason Aug 27 '21 at 16:59
  • Forget the KEK for login. You will use your current password storage as always. If the password is correct, you use the same password to derive the KEK. The symmetric key is encrypted, and the encryption key is just a 128-bit random string, so an attacker have no way to know he have a good key by bruteforce. – ThoriumBR Aug 27 '21 at 17:18