Secure Cryptographic Hashing with 'bcrypt-hash'

bcrypt is a great utility for encrypting files with the blowfish cipher, which was designed by the legendary cryptographer Bruce Schneier.

One of the reasons that bcrypt is so secure is the internal hashing scheme it uses.

Fast hash functions

In a typical hash function, such as MD5 or SHA-1/2, there is only one loop iteration of the hash function. This is because MD5 and the SHA family of hashes were designed to be fast, and they are.

Hashing with MD5 or SHA is still incredibly useful for certain tasks, like generating checksums, caching, and other tasks that need to happen quickly, but neither MD5 nor SHA are resistant to brute-force password cracking.

Why? Precisely because of how they were designed. Password crackers love fast hashing algorithms, because they can generate millions or even billions of guesses per second while brute forcing. Users also love fast hashing algorithms, because they don't have to wait for 20 seconds on the signup page for their plaintext password to be hashed. They love it, that is, until the database of weak hashes is inevitably leaked.

Both MD5 and SHA are considered to be 'cryptographically secure' hash functions, but MD5 has known vulnerabilities to the birthday attack, and just being 'cryptographically secure' doesn't mean you should use them for hashing secure information.

In addition, neither MD5 nor SHA implement secure salting on their own without the developer stumbling through their own implementation and failing 90% of the time, usually by unintentionally reducing the overall keyspace, using the same salt for everyone (this is actually called 'peppering', but the actual use-case is different) or using bad salts such as usernames. Given all these issues with MD5 and SHA, it is clear to see why they fall short of the task when it comes to hashing secure data such as passwords.

TL;DR: Don't ever use MD5 or SHA to hash passwords!

Slow hash functions

So how does bcrypt do a better job? When hashing a string of plaintext, bcrypt runs the main loop over a specified number of loop iterations. The number of iterations is defined by a count variable, which is given to bcrypt hash function. The function will complete a number of iterations defined by:

$$iterations = 2^{count}$$

This can be simulated for other hash functions, like SHA-256, but this, again, is way too easy to screw up. A better idea is to stick with the crypto mantra:

"Don't roll your own Crypto"

Of course, like anything, you can still screw up implementing bcrypt, like Ashley Madison developers did, but if you use a library, chances are you'll do it correctly.

In any case, what bcrypt achieves by running through, say, 4096 iterations is that the time it takes to brute-force hashes using tools like John the Ripper or Hashcat is increased super-duper-significantly.

Like, instead of cracking at 2.2 billion guesses per second, you could be cracking at 6 guesses per second.

Again, you could just run over 4096 iterations of SHA-256, but you will almost certainly screw it up:

hash = sha256(salt + password);

for (int i = 0; i < 4096; i++) {  
    hash = sha256(hash);
}

Wrong, wrong, wrong. Depending on the length of your input and the number of iterations, you just increased the number of potential collisions dramatically, which is not good.

You could feed the input back into each iteration, like this:

hash = sha256(salt + password);

for (int i = 0; i < 4096; i++) {  
    hash = sha256(salt + password + hash);
}

which is the correct thing to do, but really, just use bcrypt.

Side note: the PBKDF2 algorithm uses this scheme, but it is not as resistant to ASIC- or GPU-based brute-force attacks as bcrypt or scrypt.

Fun fact: on OS X your passwords are now stored in a Plist file on a per-user basis. By default, OS X will use PBKDF2 with 25125 rounds of SHA-256 and 42 digits of base-64 encoded salt. You can see your password hash with:

sudo defaults read /var/db/dslocal/nodes/Default/users/username.plist ShadowHashData|tr -dc 0-9a-f|xxd -r -p|plutil -convert xml1 - -o -  


Generating a bcrypt hash

So, after all this, you want to use bcrypt to check out what all the fuss is about. Well, that's easy, since it is already installed on Linux (most distros actually use bcrypt to hash your login password), and it's super easy to grab for OS X:

brew install bcrypt  

You can now easily encrypt and decrypt files and the like with the bcrypt command line utility. Nice!

But... what if you just want to generate a hash, or mess around with using different count values?

Well, that's why I wrote bcrypt-hash (https://github.com/gibsjose/bcrypt-hash).

bcrypt-hash

Install it with:

brew tap gibsjose/crypto  
brew install bcrypt-hash  

Now we can check out a couple of use-cases:

# Example: Hash the plaintext 't3rr1bl3_p4$$w0rd' with a cost factor of 12
bcrypt-hash -c 12 't3rr1bl3_p4$$w0rd'  

Which generates:

$2y$12$UzKl7mitlZJt52PAMemYmeb9YUC9XhvX6DlbtbaVtdqI32TCPPCj6

Or you can omit the cost factor in favour of the default:

# Example: Hash the plaintext 'Look! Here is some plaintext...' with the default cost factor of 10
bcrypt-hash 'Look! Here is some plaintext...'  
$2y$10$k8pe9htFbLrJD/EjOE3In.RPOFpPz2WZ44lwQVt8RJRmUgXNnfnSC

Finally, you can also check a plaintext against a hash you have generated or have otherwise obtained:

# Example: Check the plaintext 'test' against a correct hash
bcrypt-hash check 'test' '$2y$10$5ixGI4bAKbWI4bdlzbXi9uqaOrysHRuqbBLP4N8HhgPL6c5yIuS2a'  

Which will give:

Verified  


Conclusions

  1. Don't ever use MD5 or SHA for hashing passwords
  2. Use bcrypt
  3. Use a library, like PHP 5.5+'s built in password_hash() and crypto() functions
  4. Install bcrypt-hash if you want to mess with generating or verifying standalone bcrypt hashes from the command-line