2

We are currently implementing envelope encryption for our app. That means, we need to derive a key from a user's password, which then will be used as a key encryption key(KEK) to wrap another key using the AES-KW algorithm. Basically, on our database the wrapped key (which is the data encryption key in our case) will be stored in the same schema as BCrypt hashes of the user password.

My concern: according RFC 3394, §2.2.3.1 there is always a constant initial value prefixed with the wrapped key to check key integrity. However, in that case an attacker could just try to brute-force the PBKDF2 algorithm by iterating the password space, try to unwrap the key with the derived KEK, and check whether the initial value of the unwrapped key is A6A6A6A6A6A6A6A6. In case of a match he will not only be able to unwrap the key, but also get the user password, thus circumventing BCrypt.

An example:

Let's assume a user chooses a password with 5 characters of [A-Za-z0-9]. That means, the password space cardinality is (26 + 26 + 10)^5 = 62^5 ~ 9.16e8.

Our current idea:

PBKDF2 with HMAC-SHA512 @ 100.000 iterations

BCrypt (lets assume an average run time of 0.1s per hash)

Thus, to brute- force PBKDF2, it would require ~9.16e13 SHA512 hash evaluations. I have searched on ebay and found racks (mostly for mining bitcoin) for $150 with a hash rate of 1TH/s (that means 1e12 H/s). That means, on average it would take 9.16e1=91.6s to brute-force a 5 character password.

Using the BCrypt example, let's assume 16 cores+ hyperthreading -> 320 BCrypt H/s on a single device. Thus it would take 2862915.1s to brute- force the same password on BCrypt, what is 33days.

Now the main question:

why OWASP still recommends PBKDF2 despite the easy paralellization of SHA hashes on GPU? Would it maybe be better to derive a key with sha256(bcrypt(password, salt)) instead of pbkdf2(password, salt)? Or maybe even pbkdf2(bcrypt(password, salt), salt)?

4
  • 3
    OWASP doesn't generally recommend using PBKDF2 - the main recommendation in the Password Storage Cheat Sheet recommends using Argon2id. PBKDF2 is only recommended "If FIPS-140 compliance is required". And even in that case, they're recommending a much higher work factor than you use in your example.
    – Gh0stFish
    Commented Nov 14 at 10:54
  • And it also seems rather unfair to compare the cracking rate of unspecified ASICs (which probably don't even support PBKDF2) to a rather arbitrary "0.1s per hash" for bcrypt.
    – Gh0stFish
    Commented Nov 14 at 10:58
  • @Gh0stFish: yes, it may be unfair, however the issue remains the same, namely SHA-x is paralellizable on GPU, while bcrypt is not. I think we will go with Argon2, as suggested below
    – oleg_zh
    Commented Nov 14 at 11:43
  • 1
    But Bcrypt is not very resistant to FPGAs (see this article from 2019) or to ASICs, which is what you're comparing to when you look the hash rates on at mining rigs.
    – Gh0stFish
    Commented Nov 14 at 12:01

1 Answer 1

5

It's true that an attacker can pick the weaker of both algorithms (which in this case is PBKDF2) and use it to attack the password.

Hashing the same password twice generally doesn't make a lot of sense. Not only do you create a shortcut unless you use the exact same algorithms with the exact same parameters. You're also putting yourself at a disadvantage: While you have to perform the hash calculations twice, the attacker only needs to do them once, because it doesn't matter if they perform the attack based on the derived key or the password hash. This is obviously a bad approach when the goal is to make the calculations as difficult as possible for attackers but still acceptable on your own system.

You should ditch both PBKDF2 and bcrypt. They're legacy algorithms which provide little resistance to attacks with specialized hardware. Instead, switch to a modern algorithm like Argon2. It can be used to simultaneously derive a key and a verification hash from a password. Simply make the output long enough. For example, if you want a 128-bit AES key and a 256-bit password hash, then generate a 384-bit output with Argon2. Use the first 128 bits as the AES key, and store the remaining 256 bits as a password hash.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .