Secure Password Hashing with Bcrypt in Erlang (and Nitrogen)

Introduction

Intended Audience: Erlang developers or Nitrogen developers who've made it through the tuturial. I take this a bit slow for make it easier for folks new to Erlang and Nitrogen. The proficient can probably scroll all the way down to the complete source code and get a feel for how this works.

When storing passwords, the only proper secure method is to run the passwords through some hashing algorithm. A hashing algorithm takes a string of characters or bytes and converts them into a new string which cannot be reversed into the original string. For years, MD5 and SHA were the common hashes used for storing passwords in web application, however due to significant improvements to hardware speed and parallelism, MD5 and SHA (even salted MD5 and SHA) have proven to be completely ineffective in the event of a password table leak.

The main problem with MD5 and SHA is, paradoxically, that they're just too fast, and in cryptography and hashing, fast is bad. Further, with recent high-profile sites leaking their hashed passwords only to have the majority of the passwords cracked within a week, it's more critical than ever to ensure for your users and customers that even their password hashes do leak (website hacked, or you have a laptop with a backup stolen, etc) they can rest assured that their password won't be recovered from the hash in any kind of timeframe that could be summarized as "soon." Indeed, with a proper hashing algorithm, a "common" password (ie, an English word) should take at least days to crack, and an "uncommon" (non-word) password should take years to crack.

So we turn our focus to bcrypt, a hashing algorithm that's intentionally designed to be slow. Further, you can define just how slow you want it to be by setting a "work factor", allowing you to scale the strength of the hash as hardware becomes ever faster. Currently, a work factor of 7 or 8 is generally sufficiently slow, but maybe 10 years down the road, upping the work factor to something like 15 or 20 will be necessary.

I won't go into any more detail about why you should use bcrypt, as someone else already did it better than I could anyway. There is a completely acceptable (and probably superior) alternative to bcrypt called scrypt, which is memory bound instead of CPU bound. I'm unaware, at the moment, of any Erlang bindings for scrypt.

Then you can store hash in your user table (or login or whatever you call it). When the user then tries to log in, you can verify the password by rehashing the it (conveniently, because the hash happens to contain the salt, you can provide the previously hashed password as the Salt argument to the bcrypt:hashpw/2 function).

%% Load the original Password hash from the database
OriginalHash = db_user:get_password_hash(Username),
%% Hash the provided password with the original hash
{ok, NewHash} = bcrypt:hashpw(ProvidedPassword,OriginalHash),
%% Compare the New Hash against he old one. If they match, the password is the same
NewHash == OriginalHash.

That's the meat of using bcrypt with pure Erlang. Now that you know the basics, let's jump right into putting it to use in a Nitrogen application.

Incorporating bcrypt into your Nitrogen-based Webapp

For this, we'll be creating three modules:

login: Our page for logging in

register: Our page for creating an account

db_login: The database interface, and where we hash

Creating an Account

Before we can try to log in, we first need to create an account, so let's make a new page called /register.

Now, we need to actually create the account by handing the register postback:

event(register) ->
%% Get the values from the form
%% Notice that we don't need to worry about the
%% "confirm" field, since that's handled by the
%% validator
[Username,Password] = wf:mq([username,password]),
%% Create the account
db_login:create(Username, Password),
%% Notify the user and redirect
wf:wire(#alert{text="Account Created. Redirecting you to the login page"}),
wf:redirect("/login").

You see here, we've referenced the db_login:create/2 function. Let's make this function here for demonstration purposes. Note, that we're going to use a module we make up called db which contains some function calls to use SQL. Obviously, you can use your own database, but for the sake of demonstration, we're going to use this db module and SQL.

We need to make the code for db_login:create/2, so let's jump right into that:

As you can see, we simply take the Password, hash it, and stick the hash alongside the Username into a user table in the database using our made-up db:insert call.

Did we forget something? Yup.

The observant reader would have noticed that we didn't even check to make sure that the username we're inserting doesn't already exist. While this is not necessarily something that demonstrates the use of bcrypt, it's something that should go into any decent registration form, so we'll do that here.

Let's reverse order here first, and let's make a function to quick check the database if a username exists. We'll do this by creating the function db_login:username_exists/1, which returns a boolean. It is defined as follows:

So far, our login page is looking pretty simple. All we're doing is making a form, and getting the form values on login attempt, then verifying the provided info with db_login:attempt_login/2. From there, we do something depending on the result of the login attempt. If the attempt fails, let's show a message.

You'll notice bcrypt has not yet been seen. It's abstracted away in the db_login:attempt_login/2 function, which returns merely the atoms fail or success, and does nothing else.

Let's see how we would implement the db_login:attempt_login/2 function.

Note: I'm going to rely on an implied call db:first_record/2, which prepares and returns a SQL query from the database. You're free to use whichever database system you prefer. Note, this db module does not exist in Erlang, and is merely an abstraction for the purposes of demonstration.

Here we finally see bcrypt in use, and its use is simple. We've simply retrieved the existing hash from the database, rehashed the provided password, and compared the hashes.

Conclusion

As demonstrated, we've shown how to use the bcrypt application with Erlang. There is also an application out there that provides a "thin wrapper" for the Erlang bcrypt application called erlpass that is worth looking into. I leave converting the code above to erlpass as an exercise for the reader.

Follow-up

The hypothetical db module referenced throughout this article is a variation of one that I use for my own projects, and serves as a basic wrapper for the erlang mysql driver, along with a simple method of preparing SQL statements. I will eventually be writing an article about it, as well as releasing the source code for it on Github.