Attacking Devise Bcrypt Hashes

2 minute read

Let’s explore a path I went down one day when I got curious about how you would “practically crack” a bcrypt hash generated by the Authentication library Devise.

I am not going to explain in mathematical language how Bcrypt or any of it’s implementations work. But I have provided code that will allow you to check to see if a password was used to generate a specific Bcrypt hash.

The Scenario (or use case)

Lets place ourselves in a scenario where you have access to a bcrypt hash on your own system. You have a rails app that connects to a mysql database, and you use Devise to manage your User model’s encrypted password field.

One day, you forget which one of your LastPass password manager passwords maps to the account you have on your own rails app.

You decide to ssh into the host running your backend, and therefore your mysql database. Connecting to that database you retrieve the value of your User accounts encrypted password field. You also read your config/devise.rb file and retrieve the pepper value.

markohwhy $ cat config/initializers/devise.rb | grep pepper
config.pepper = "some long entropic string"

You gather your password list from LastPass, and place each password on it’s own line in a file. That file will be your “Dictionary” for passwords that will be checked against the Bcrypt hash you retrieved from the database earlier.

The password list might look like this… (but hopefully not)

123456
qwerty
eagles

The Bcrypt hash generated by Devise might look like

$2a$10$sR/ovbuzNdw5YZKItWq33eRKQ5lJ6hOk0AweL7ht25GQdHusPhR1G

So then you plug in

hash = "$2a$10$sR/ovbuzNdw5YZKItWq33eRKQ5lJ6hOk0AweL7ht25GQdHusPhR1G"
password_guess = "123456"
pepper = "some long entropic string"

into

Using the above code in the following way shows you the hash’s password is “qwerty”, as you can see in the ruby REPL session below:

pry(main)> hash =
"$2a$10$sR/ovbuzNdw5YZKItWq33eRKQ5lJ6hOk0AweL7ht25GQdHusPhR1G"
pry(main)> password_guess = "123456"
pry(main)> pepper = "some long entropic string"

pry(main)> Dpc.crack_hash hash, password_guess, pepper
=> false

pry(main)> password_guess = "qwerty"

pry(main)> Dpc.crack_hash hash, password_guess, pepper
=> true

And wala, on the last call to crack_hash with “qwerty” as our password guess we are able to generate a matching BCrypt hash checksum. Meaning the password for that hash was “qwerty”.

Now armed with our recently forgotten password, we are able to sign into the rails app as the user with the “qwerty” password.

In closing

You will realize that if you try to create your own bcrypt hash with the same password and pepper, you will still end up with a DIFFERENT bcrypt hash even though the password is the same.

This is due to the fact that BCrypt psuedo-randomly generates a salt for each Bcrypt hash, and places itself in the hash. It is actually prepended to the checksum of the hash.

Bcrypt hash disected

$2a$10$sR/ovbuzNdw5YZKItWq33eRKQ5lJ6hOk0AweL7ht25GQdHusPhR1G

$2a$10$sR/ovbuzNdw5YZKItWq33e

                             RKQ5lJ6hOk0AweL7ht25GQdHusPhR1G

        salt                           checksum

As far as I am aware, there is no publicly known method for cracking BCrypt hashes that exploits a vulnerability in the cryptographic algorithm itself.

Trying to crack a bcrypt hash through a dictionary attack is slow, due to the fact that bcrypt was implemented to be slow. The slow hashing algorithm coupled with built in salts means that Rainbow table attacks are impossible. This is because you cannot precompute hashes for every single password unless you created a table for each random salt that you may or may not run into.

All in all, use bcrypt! :)

There is much more information on how Bcrypt works here.

Thank you for reading, feel free to contact me on my LinkedIn.

Updated: