Using Rfc2898DeriveBytes for PBKDF2
DRAFT DOCUMENT - WORK IN PROGRESS
Password storage is a large topic in application security. If a security failure occurs, and the database is stolen, the passwords of the users are some of the most important data stored. Given the state of contemporary authentication, they do not need to be stored in plain text, so they should not. A hashed representation of the password, using a contemporary encryption algorithm and process, is the accepted way to store a password in today’s systems. More information can be found in the Password Storage Cheat Sheet.
Common .NET password storage
In .NET, the SQL Membership or ASP.NET membership patterns are often used for identity. In a best case scenario, Active Directory Federated Services (ADFS) or Azure Active Directory are used. In each of these cases, the password storage is either handled by the subsystem, or not handled by the application at all. Sometimes, however, there is no choice but to store the password in the application using home grown code. When this is the case, it is upon the software developer to select and use the correct hashing algorithm and process for password storage. Hashing is the process of deriving a unique, repeatable value form a text input and salt. This prevents the storage of the password itself, thus protecting the password if the database is stolen.
Hashes create unique values that cannot be reversed into their source values, however, brute force could potentially lead an attacker to the source values. As such, a key derivation function is often used to increase the work factor needed to create the representative value. Often this key derivitation function is PBKDF2, or the Password-Based Key Derivation Function 2.
PBKDF2 uses a pseudorandom function and a work factor to create a process for hashing a string. The benefit to using an algorithm like PBKDF2 is that the work factor can increase as the power of computing in the environment increases. Because brute force is used to crack hashes, making the process harder to reverse is the primary source of security in the hash creation process.
The details of PBKDF2 are openly published, and the goal of this document is not to replicate that information. Generally speaking, the function is one that accepts a pseudorandom function (such as SHA1), a salt, the number of iterations, the length of the resultant hash, and the text to be hashed. The goal is one of ‘key stretching’, making the overall process of generating or reversing the hash harder. The .NET Framework can abstract the details of the algorithm from the developer.
Implementing PBKDF2 in .NET
Microsoft’s .NET platform supports PBKDF2 out of the box. Rfc2898DeriveBytes allows a developer to hash a value using PDKDF2 without implementing the algorithm. Using a number of iterations and a salt, a developer can easily implement the key stretching hash then store that data in the database. During registration, rather than storing the password entered by the user, you should store the password and salt. The RNCGCryptoServiceProvider will generate a pseudorandom salt, and Rfc2898DeriveBytes will generate a hash. You need to store them both.
Here is an example of using the System.Security.Cryptography namespace in a simple method. It returns the salt and hash in a pipe delimited string.
public static string HashPassword(string password)
// Generate a random salt RNGCryptoServiceProvider rNGCryptoServiceProvider = new RNGCryptoServiceProvider(); byte salt = new byte; rNGCryptoServiceProvider.GetBytes(salt); // Generate the hash Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt); rfc2898DeriveBytes.IterationCount = 1000; byte hash = rfc2898DeriveBytes.GetBytes(24); //Return the salt and the hash return Convert.ToBase64String(salt) + "|" + Convert.ToBase64String(hash);
Using the hash on login
When a user later logs in, rather than using the password to confirm authentication, you can use the hashing function to generate a hash with the stored salt rather than a generated salt. Then compare the hash with the stored hash.