Navigation |
>> Home |
Storing Passwords - done right!
Written by: Christoph Wille In very many - not to say almost all - Web applications user data is administered, from Web forum to Web shop. These user data encompass login information of the users which contain the password besides the user name - and this in plain text. A security leak par excellence. Why is storing the user name and password in plain text a security leak? Well, imagine a cracker gaining system access through eventual OS or server software errors, and being able to read the user database. As he now knows the user name and password of any arbitrary user he can now log on as a 'real' user and do whatever he wants with the permissions for that user - from ordering in the Web shop to character assassination on the forum. And you are the operator... How can this security risk be eliminated? Why should we roam far when a proven method for safe storage of passwords exists since decades: under UNIX, user passwords are stored as so called 'salted hashes'. What is a Salted Hash?A hash is a numerical value of fixed length which unequivocally identifies files of arbitrary legth. An example of a hashing algorithm is SHA1, which already figured as the topic of an ASP article (German). The reader might now say that saving the password as a hash would be sufficient, but why is this wrong? The reason for this is that usually so called 'Dictionary Attacks' are run against hashed passwords - a good example being the MD5 hashed passwords of NT4. This is a Brute Force attack: all entries in a dictionary were hashed using MD5 and those hash values then are compared against the password database. Have a guess how quickly some passwords are found this way. The intention behind a Salted Hash is to have this type of attack fail by attaching a random value - the so called salt - to each password and only then compute the hash over password and salt. For comparison of the password the salt has to be stored alongside the salted hash, but the only vector of attack is to re-code the dictionary for each individually stored password with the salt - and this takes quite a long time. Storing the Salted HashAs previously mentioned, we now need to store three fields instead of user name and password: user name, salt and the salted hash of the password. I also mentioned that when these data get into the hands of a cracker he will have a problem using standard attacks and most probably will look for an easier victim. One point however must be kept in mind: it is now impossible to send a 'password reminder' email - all that can be done is to generate and send a new password for the user. As a number of mistakes is made in this field, we will begin with the .NET code for generating a truly random password. Generating Passwords - done right!The entire class was created in the course of a (C# ASP.NET) community project together with another AspHeute author, namely Alexander Zeitler. In this case the question of how to generate good passwords and how to correctly store them in a database also came up. We created the class Password for this purpose which has the following signature: namespace DotNetGermanUtils { public class Password { public Password(string strPassword, int nSalt) public static string CreateRandomPassword(int PasswordLength) public static int CreateRandomSalt() public string ComputeSaltedHash() } } The method for generating a new password is static and we can set the length of the password to be created: public static string CreateRandomPassword(int PasswordLength) { String _allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789"; Byte[] randomBytes = new Byte[PasswordLength]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(randomBytes); char[] chars = new char[PasswordLength]; int allowedCharCount = _allowedChars.Length; for(int i = 0;i<PasswordLength;i++) { chars[i] = _allowedChars[(int)randomBytes[i] % allowedCharCount]; } return new string(chars); } The principle is similar to the ASP solution presented in the article Generating a secure password (German), however, we added something special here: we use cryptographically secure random numbers to select characters for the password from the 'array' _allowedChars. The class RNGCryptoServiceProvider already was discussed in the article Unbreakable Encryption Using One Time Pads (English). This way, we have generated a truly random password which can be set as initial password for the user - now we only need a salt for it. We create a SaltBasically, this is a helper function which also uses the RNGCryptoServiceProvider: public static int CreateRandomSalt() { Byte[] _saltBytes = new Byte[4]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(_saltBytes); return ((((int)_saltBytes[0]) << 24) + (((int)_saltBytes[1]) << 16) + (((int)_saltBytes[2]) << 8) + ((int)_saltBytes[3])); } A 4 byte salt is created (an integer, for simplicity's sake for storing in the database tables). This salt as well as the generated password form the base for computing the salted hash. Computing the Salted HashComputation of the salted hash is an instance method accessing two member variables which are set in the constructor: public class Password { private string _password; private int _salt; public Password(string strPassword, int nSalt) { _password = strPassword; _salt = nSalt; } This way, the method ComputeSaltedHash only returns the salted hash and does not accept any further parameters. Hashing by the way is done using the well known SHA1 algorithm: public string ComputeSaltedHash() { // Create Byte array of password string ASCIIEncoding encoder = new ASCIIEncoding(); Byte[] _secretBytes = encoder.GetBytes(_password); // Create a new salt Byte[] _saltBytes = new Byte[4]; _saltBytes[0] = (byte)(_salt >> 24); _saltBytes[1] = (byte)(_salt >> 16); _saltBytes[2] = (byte)(_salt >> 8); _saltBytes[3] = (byte)(_salt); // append the two arrays Byte[] toHash = new Byte[_secretBytes.Length + _saltBytes.Length]; Array.Copy(_secretBytes, 0, toHash, 0, _secretBytes.Length); Array.Copy(_saltBytes, 0, toHash, _secretBytes.Length, _saltBytes.Length); SHA1 sha1 = SHA1.Create(); Byte[] computedHash = sha1.ComputeHash(toHash); return encoder.GetString(computedHash); } Now we have all functions we need and can proceed to using the class. The Class Password in Everyday UseI created a small example demonstrating the creation of a new password, a new salt and finally the salted hash: using System; using DotNetGermanUtils; namespace HashPassword { class TestApplication { [STAThread] static void Main(string[] args) { // Generate a new random password string string myPassword = Password.CreateRandomPassword(8); // Debug output Console.WriteLine(myPassword); // Generate a new random salt int mySalt = Password.CreateRandomSalt(); // Initialize the Password class with the password and salt Password pwd = new Password(myPassword, mySalt); // Compute the salted hash // NOTE: you store the salt and the salted hash in the datbase string strHashedPassword = pwd.ComputeSaltedHash(); // Debug output Console.WriteLine(strHashedPassword); } } } The following point is of the highest importance: Positively generate a new salt for every user. Should two users accidentally choose the same password, this way the salted hash will still be different for both user accounts! The source code presented illustrates the creation of a new password and a new salt, but what goes on when the user wants to log in. It is as simple as this: // retrieve salted hash and salt from user database, based on username ... Password pwd = new Password(txtPassword.Text, nSaltFromDatabase); if (pwd.ComputeSaltedHash() == strStoredSaltedHash) { // user is authenticated successfully } else { ... Basically this is not different from the usual username/password implementations, but the data is significantly more secure even in the case of the (server side) password data falling into the hands of unauthorized thirds. ConclusionThe class we presented in today's article can be built into your own .NET projects - either directly in C# projects or as an assembly in other programming languages. From now on, there are no more excuses for unsafe storage of passwords! Download of the CodeClick here to start the download. Related Articles
Unbreakable Encryption Using One Time Pads (English)
©2000-2004 AspHeute.com |