By now you know passwords should be stored using a hash. Given your decision to do the right thing and hash your passwords you still have to decide some implementation details.
First, choose a hashing algorithm. Choose BCrypt. Why BCrypt? I’ll give you two reasons:
- It is slow, and slow is good because it thwarts brute-force attacks (read more here: http://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage).
- The output from BCrypt is a Base-64 alphabet (http://tools.ietf.org/html/rfc4648#section-4) which means there are no characters that are tricksy to store in a simple character field; CodePage is irrelevant.
Second, find a reliable implementation of BCrypt. I am going to show an example of using C#.Net and SQL Server, but here is a good reference I found using PHP and MySQL (http://oscarm.org/2012/6/using-bcrypt-store-passwords). I also say a “reliable implementation” because there are flaws in some implementations, such as one discovered in 2011 and discussed in these articles (http://en.wikipedia.org/wiki/Crypt_(Unix), http://www.digipedia.pl/usenet/thread/16234/200/ (Search for $2y$) in this second article. – $2y$ indicates you are using a version of BCrypt for Unix that does not contain this bug).
I am using Derek Slager’s C# implementation of BCrypt downloaded from here:
http://derekslager.com/blog/posts/2007/10/bcrypt-dotnet-strong-password-hashing-for-dotnet-and-mono.ashx. Based on a little testing I did myself, I believe it does not contain the flaw cited in the above article, but I am no expert at this. Even if the bug discovered in 2011 exists in this implementation of BCrypt, it is of little concern to me as all of my users are located within the U.S. and are extremely unlikely to be using password characters that cannot be directly entered from a standard keyboard (characters with ASCII values greater than 127). And even if a user does have such a password, the attack vector remains incredibly tiny for exploitation.
Third, understand the inputs and outputs. BCrypt includes a method to generate a salt. When the salt is applied to the password, the resulting hash holds the original salt and the hashed password. You can store the salt and password combined in a CHAR(60) field in your database. You don’t need to store the hashed password separately from the salt, nor should you, since the
BCrypt class contains a method that expects the salt and password combined to be passed in as a parameter when later confirming the correctness of the user-entered password.
Note, the salt always begins with something like $2a$10$ meaning version 2a of BCrypt and 10 rounds of computations. 10 rounds is the default. You can choose larger numbers to make it slower, or smaller numbers to make it faster, but 10 is a really good choice for most of us. Since the rest of the salt is 22 bytes, and the $2a$10$ is 7 bytes for a total of 29 bytes, the hashed password is always the remaining 31 bytes. The total length of the output that you will store in the database is always 60 bytes long.
string myPassword = "password";
string mySalt = BCrypt.GenerateSalt();
string myHash = BCrypt.HashPassword(myPassword, mySalt);
bool doesPasswordMatch = BCrypt.CheckPassword(myPassword, myHash);
Each password stored will have a different salt, and every time a user changes their password you will generate a new salt for the user. I also encourage you
to add a little hard-coded salt to the password. This hard-coded salt adds a little more challenge to brute force attacks from hackers that steal your database,
but have not stolen your code and don’t have the hard-coded salt.
private void SetPassword(string user, string userPassword)
string pwdToHash = userPassword + "^Y8~JJ"; string hashToStoreInDatabase = BCrypt.HashPassword(pwdToHash, BCrypt.GenerateSalt());
using (SqlConnection sqlConn = new System.Data.SqlClient.SqlConnection(...)
SqlCommand cmSql = sqlConn.CreateCommand();
cmSql.CommandText = "UPDATE LOGINS SET PASSWORD=@parm1 WHERE USERNAME=@parm2";
cmSql.Parameters["@parm1"].Value = hashToStoreInDatabase;
cmSql.Parameters["@parm2"].Value = user;
private bool DoesPasswordMatch(string hashedPwdFromDatabase, string userEnteredPassword)
return BCrypt.CheckPassword(userEnteredPassword + "^Y8~JJ", hashedPwdFromDatabase);
Another reference to BCrypt compared to SHA512: http://stackoverflow.com/questions/1561174/sha512-vs-blowfish-and-bcrypt.