Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bcrypt vs pbkdf2 for encrypting private keys

I'm building an application in which a password is used on the client side to encrypt a private key of a elliptic curve key pair. Then the password is bcrypted and sent to the server (along with the encrypted private key) and the public key.

Originally, I was using pbkdf2 to hash the password before encrypting the private key, but since I'm also bcrypting the password, could I use the bcrypted one instead?

According to https://medium.com/@mpreziuso/password-hashing-pbkdf2-scrypt-bcrypt-1ef4bb9c19b3#.sj4jcbynx the answer is not only yes, but bcrypt is even better as it's more GPU-ASIC resilient. Anything I'm missing?

like image 203
pupeno Avatar asked Nov 12 '16 19:11

pupeno


1 Answers

You should not be using the bcrypt hash output as an encryption key; it is not meant to be key material:

  • BCrypt is not a key-derivation function
  • BCrypt it is a password storage function

You have an elliptic curve private key that you want to encrypt using a user's password. Of course you don't want to use the password directly - you want to use the password to derive an encryption key. For that you can use:

  • PBKDF2
  • scrypt

These are both key-derivation functions (e.g. password-based key derivation function). Their purpose is to generate an encryption key given a password. They are designed to be "hard".

You feed both these algorithms:

  • a password
  • cost parameters
  • salt
  • desired number of bytes (e.g. 32 ==> 32 bytes ==> 256 bits)

and it returns you a 256-bit key you can use as an encryption key to AES-256.

You then want to backup the user's key

I gather that you then want to:

  • store the encrypted elliptic curve private key on your server
  • store a hash of their password on your server

And your question was: since you already ran their password through "a hashing funtion" can't you just use that hash as their stored password?

No! That hash is also the encryption key protecting their private key. You don't want that private key transmitted anywhere. You don't want it existing anywhere. That 32-byte encryption key should be wiped from memory as soon as you're done with it.

What you should do, if you also wish to store a hash of the user's password is use an algorithm that is typically used for password storage:

  • pbkdf2 (a key-derivation function abused into password storage)
  • bcrypt (better than pbkdf2)
  • scrypt (a key-derivation function abused into password storage; better than bcrypt)
  • argon2 (better than scrypt)

You should separately run the user's password through one of these password storage algorithms. If you have access to bcrypt; use that over pbkdf2. If you have scrypt, use that for both:

  • derivation of an encryption key
  • hashing of the password

The security of your system comes from (in addition to the secrecy of the password), the computational distance between the user's password and the encryption key protecting their private key:

"hunter2"  --PBKDF2--> Key material
"hunter2"  ---------bcrypt-------> Key material
"hunter2"  ----------------scrypt----------> Key material

You want as much distance between the password and the key.

Not-recommended cheat

If you're really desperate to save CPU cycles (and avoid computing scrypt twice), you technically could take:

Key Material ---SHA2---> "hashed password"

And call the hash of the encryption key your "hashed password" and store that. Computation of a single SHA2 is negligible. This is acceptable because the only way an attacker can use this is by trying to guess every possible 256-bit encryption key - which is the problem they can't solve in the first place. There's no way to bruteforce a 256-bit key. And if they were to try to brute-force it, the extra hashed version doesn't help them, as they could just test their attempt by trying to decrypt the private key.

But it's much less desirable because you're storing (a transformed) version of the encryption key. You want that key (and any transformed versions of it) stored as little as possible.

To sum up

  • generate EC key pair
  • encryptionKey = scryptDeriveBytes(password, salt, cost, 32)
  • encryptedPrivateKey = AES256(privateKey, encryptionKey)
  • passwordHash = scryptHashPassword(password, salt, cost)

and upload

  • encryptedPrivateKey
  • passwordhash
like image 197
Ian Boyd Avatar answered Oct 30 '22 06:10

Ian Boyd