Dustin Oprea
Dustin Oprea

Reputation: 10236

bcrypt in Go with KDF for specific output key-length

It seems like the Go ecosystem just has a basic bcrypt implementation (golang.org/x/crypto/bcrypt) and it's left as an exercise for the developer to extract the key from the encoded output string to then further expand it to satisfy a particular key length if you're going to be using it as an encryption key rather than just storing it as a password in a DB somewhere. It confounds me that there don't seem to be any quick treatments of this concept online for Go or just in general.

At the risk of introducing a bug by doing it myself, I suspect that I'm gonna be forced to use scrypt, where, at least in Go, it does take an output-length parameter.

Am I missing something? Is there an implementation of bcrypt somewhere in Go that takes a key-length parameter and manages producing a key of acceptable length directly?

Upvotes: 0

Views: 740

Answers (1)

Ian Boyd
Ian Boyd

Reputation: 256581

Bcrypt is not a key derivation algorithm; it is a password hashing algorithm.

  • PBKDF2 can take a password and output n desired bits
  • scrypt can take a password and output n desired bits

These are key-derivation functions. They take a password and generate n bits that you can then use as an encryption key.

BCrypt cannot do that. BCrypt is not a key-derivation function. It is a password hashing function. It always outputs the same amount of bits.

Bonus: bcrypt always outputs exactly 24-bytes (192 bits), because the output from bcrypt is the result of encrypting OrpheanBeholderScryDoubt.

Note: It's not the result of hashing OrpheanBeholderScryDoubt - the bcrypt algorithm is actually encrypting OrpheanBeholderScryDoubt using the blowfish cipher (and repeating the encryption 64 times).

The strenght of bcrypt comes from the "expensive key setup".

Bonus: The strength of bcrypt comes from the fact that it is expensive. And "expensive" means memory. The more memory an algorithm requires, the stronger it is against bruteforce attacks.

  • SHA-2: can operate in 128 bytes of RAM
  • bcrypt: constantly touches 4 KB of RAM
  • scrypt: constantly touches 16 MB of RAM (in the default configuration in Android and LiteCoin)
  • Argon2: is usually recommended you configure it to touch 1 GB of RAM

Defending against bruteforce attacks means to defend against parallelization. An algorithm that requires 128 bytes can have 7 million parallel operations on a 1 GB video card.

Scrypt, requiring 16 MB of RAM, can only have 62 running in parallel.

Argon2, using 1 GB of RAM, can only have 1 running on a video card. And it runs faster on a CPU anyway.

Kludge bcrypt into a Key Derivation Function (KDF)

You could kludge bcrypt into being a key-derivation function. You can use the standard function PBKDF2 to do it for you.

Normally PBKDF2 is called as:

String password = "hunter2";
String salt     = "sea salt 69 nice";

Byte[] key = PBKDF2(password, salt, 32, 10000); //32-bytes is 256 bits

But instead you can use the bcrypt string result as your salt:

String password = "hunter2";
String salt     = bcrypt.HashPassword(password, 12);

Byte[] key = PBKDF2(password, salt, 32, 1); //32-bytes is 256-bits

And now you've generated a 256-bit key "using bcrypt". It's a neat hack.

In fact the hack is so neat, that it is literally what scrypt does:

String password = "hunter2";
String salt     = ScryptExpensiveKeyHash(password, userSalt, ...);

Byte[] key = PBKDF2(password, salt, 32, 1); //32-bytes is 256-bits

Conclusion

Bcrypt is not a key derivation function. That is the goal of functions like PBKDF2, scrypt (which uses PBKDF2), and argon2.

Using bcrytp when you're only allowed NIST approved algorithms

There is another good reason to use this pbkdf2 construction with bcrypt.

Sometimes a "security expert", who has no idea what they're talking about, will insist that you use PBDKF2 for key derivation. (Yes, it does happen). And you'll try to tell them over and over that PBDKF2 is horribly weak system for key derivation (SHA2 that it is based on runs way too fast, and 10,000 or 100,000 iterations is nowhere near enough to protect you from brute-force attacks - that's what bcrypt, scrypt, and argon2 were invented for).

But this person won't let it go, and will demand the use of PBKDF2. With this construction you can still use bcrypt for security, and PBKDF2 for ignoramus who demands it be in there.

You just happen to use a strong "salt".

Upvotes: 1

Related Questions