Reputation: 10236
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
Reputation: 256581
Bcrypt is not a key derivation algorithm; it is a password hashing algorithm.
n
desired bitsn
desired bitsThese 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.
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.
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
Bcrypt is not a key derivation function. That is the goal of functions like PBKDF2, scrypt (which uses PBKDF2), and argon2.
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