Recently, privacy has become a hot-button issue in the tech world. With the development and advancement of facial recognition software, the extraordinary power of GPS tracking on our cell phones, and the storage of more and more sensitive data online, we’ve become increasingly aware just how important it is to build and/or strengthen privacy measures.
But even when it comes to new developers learning to build small-potatoes applications, privacy remains relevant -- and extremely important. In general, it seems like good practice to safeguard any accounts with usernames and passwords. Luckily, there are many tools available to make that process fairly painless for a programmer. We’ve got loads of helper gems on our side. Last week, while my classmates and I were working on building our first web apps (many of which attempted to implement logins and passwords) we were introduced to one popular option: BCrypt.
BCrypt, we learned, does a LOT of heavy lifting behind the scenes. In fact, it seemed like writing about 5 lines of code accomplished just about every one of the authentication and authorization measures a basic login requires. Literally. First, you have to go make sure you’ve got the BCrypt gem in your Gemfile. Easy enough. And the User table in the database needs to be updated; we’ll have to add a “password_digest” column. The User class itself also gets a quick one-liner: “has secure password”. And voila! Suddenly the User really does have a secure password.
...But given that we started the lesson by chatting about cyber-security, transparency in privacy policies, and the power that knowledge about such matters provides, it seemed prudent to dig a bit deeper and attempt to understand what, exactly, BCrypt is doing behind the scenes.
BCrypt does its job of adding security in several ways. First of all, it saves us from ever having to transmit or store a password in its unencrypted version. A user will need to enter the plainform string when creating an account -- but that password is never stored as such. As I mentioned above, the only column added to the User table (besides, presumably, username) is password_digest. And the entries in this column are never stored in their original format. Rather, BCrypt handles the work of automatically salting and hashing the entered password -- and it persists only this encrypted version to the database.
This helps us programmers by allowing us to circumvent some potentially hairy areas. Normally, in order to grab user-entered data from a form, we’d need to have corresponding attributes for the object being created. And each of those attributes would need a writer method. For example, if I were creating a book object and trying to give it a title and author (by entering this data into a form), I would need to have access to the title= and author= methods. Otherwise, assigning those values is not available to me. (Think about strong params: Strong params are essentially a way of controlling which writer methods the user has permission to access. If an attribute isn’t listed in the strong params, the user cannot successfully re-assign any values that we would like to remain fixed.)
But back to our User model. According to our current schema, a User doesn’t have a password attribute. There is no such column in the User table. So ActiveRecord, which we can usually rely on to provide reader and writer methods, is of no help to us. We could manually code out a writer method password=, but this would require more direct contact with the initial, unencrypted password. Not great. We also would ideally like to avoid involving the password_digest in the strong params -- we do not want there to be indiscriminate access to that data.
BCrypt offers a neat solution. It saves us from having to add a “password” column in our User table, but still gives us access to a .password instance method. We aren’t responsible for writing our own password= method because BCrypt does that for us, providing the code that 1) gathers the plaintext version of the password from a form, 2) automatically salts and hashes a password, and 3) persists this transformed, encrypted version in the password_digest column.
Ultimately, what this means is that because of BCrypt, a user can create a new password and have it be safely stored without it ever being unencrypted. It’s also a boon for us programmers because it saves us from having to translate back and forth between the string and salty hash versions of the password. But BCrypt goes a step even further: To explore, let’s look at an example.
Let’s say I’m creating an account and I enter my password as “banana123”. BCrypt automatically grabs this string, salts it (adding random characters to the front), and encrypts it. The end result may look something like this: “$2a$12$uwk8FBhpVQbvyHTHppO67ezQ..shaGuBKpIgNp2LSiV8tVm3/r8Sy”. This is what is stored as the password_digest in my database. So what happens when I come back and want to login again? I enter my password as “banana123”. Without BCrypt, we would stuck with the challenge of comparing “banana123” and “$2a$12$uwk8FBhpVQbvyHTHppO67ezQ..shaGuBKpIgNp2LSiV8tVm3/r8Sy”. Not only is this messy and ugly, it also means we’re spending more time in contact with the plain-string password. But BCrypt cleverly has its own version of the == comparison function… And their version can directly compare my “banana123” with the database’s “$2a$12$uwk8FBhpVQbvyHTHppO67ezQ....”. That’s right. Check it out:
Clearly, the two strings are not equal (hence the return of ‘false’ when we compare them directly, above). But remarkably, comparing the BCrypt version of the password with the string DOES evaluate to true.
In other words, BCrypt can look at the string entered by the user upon login and the encrypted salty hash persisted in the database, and successfully determine whether they match.
Indeed, another service BCrypt provides is the “authenticate” method, which performs exactly this comparison, again saving us from any manual conversions.
So yes, BCrypt does some magic. By leveraging a handful of methods in the background, it can provide authentication and authorization gateways to protect an account. But it’s important to also understand the limitations of BCrypt.
Since we’re dealing with users and logins, we’re creeping into the realm of sessions. It may be tempting to believe that BCrypt has the power to also do work related to sessions: If it can store a password, perhaps it can also store user information in a session as part of the login and authentication process? If it acts as the gatekeeper for logins, maybe it also passes along login information about the user (such as id) to the session? But ‘tis not so. BCrypt will not automatically persist any user information to the current session. If a new User is creating their account, and then logs in for the first time, we will have to handle the work of assigning a sessions[:user_id]. We will be responsible for writing those methods that can pass along data to the session, or save info about a current_user, or destroy that information upon logout. BCrypt is a powerful tool, but it cannot do everything for us. So, as always, take care in using it -- not because it’s dangerous or insecure. But because it always serves you well to be aware of what the technology you are using is capable of: to know how it works behind the scenes, to understand its underlying processes, and to keep in mind its potential shortcomings or loopholes.
Level up every day