× close Kiandra IT Logo Kiandra IT
Back to blog

Password security for web applications

A system’s top priority should be protecting the security of its users. In recent years there have been a number of high profile security breaches where customer databases have been stolen; one element commonly included in customer databases are passwords. Nowadays passwords are usually stored as a one-way hash of the original value. A hash is the output of running a password (or any data) through a cryptographic function. Unlike normal applications of cryptography the output cannot be decrypted back into the original password. Systems use this value for authentication by taking the password entered by the user and running it through the same cryptographic function and comparing the result with the original hash value stored for the user. In this way systems avoid the need to ever persist user passwords in plain text, it also means no one, not even system administrators can know what the user’s password is.

This practice further protects your users from security breaches on your system cascading into other systems the user has accounts with (as users often use the same password on multiple systems).

Attacking Hashes

Hash values are not invulnerable to attack. If a hacker knows certain information about the how the hash value was created they can attempt to discover the original password by trying to hash values until they discover input that produces matching hash output.

This is mitigated by increasing the number of possibilities to the extent where this sort of brute force approach is impractical due to the sheer number of possibilities and the time required trying them by increasing the password length and salt length. Over time new purpose built hash algorithms have been designed that deliberately make hashing the value a resource intensive process. This is ideal for password as it makes brute force password cracking approaches much less likely to succeed during the life of a password. The algorithm implementation demonstrated here is called PBKDF2.

Creating the Rfc2898HashService

I am going to start by defining a simple interface.
[csharp]
public interface IHashService
{
byte[] CreateSalt(int saltLength);
byte[] Hash(byte[] data, byte[] salt, int iterations = 1000, int outputLength = 64);
bool Verify(byte[] data, byte[] salt, int iterations, byte[] compareTo);
}
[/csharp]
So we have a method to create a new salt, a method to create a hash using the provided arguments and finally a method to verify the supplied information’s derived hash matches the provided hash.

We begin by creating our salt generation method. There’s nothing special about this method’s implementation that makes it unique to PBKDF2, except that it needs to be at least 8 bytes long.

Just for kicks we’ll keep the implementation of that in an abstract class we will call HashService.
[csharp]
public abstract class HashService : IHashService
{
private readonly RNGCryptoServiceProvider _rngCryptoServiceProvider = new RNGCryptoServiceProvider();

public byte[] CreateSalt(int saltLength)
{
if (saltLength < 8)
throw new ArgumentException(“saltLength must be at least 8”);

var salt = new byte[saltLength];
_rngCryptoServiceProvider.GetBytes(salt);

return salt;
}

public abstract byte[] Hash(byte[] data, byte[] salt, int iterations = 1000, int outputLength = 64);
public abstract bool Verify(byte[] data, byte[] salt, int iterations, byte[] compareTo);
}
[/csharp]

Using the RNGCryptoServiceProvider we generate a byte array populate with random values to the desired length.

Now we have everything in place to create our Rfc2898HashService. I am using the inbuilt .Net Framework implementation of PBKDF2, Rfc2898DeriveBytes.
[csharp]
public class Rfc2898HashService : HashService
{
public override byte[] Hash(byte[] data, byte[] salt, int iterations = 1000, int outputLength = 64)
{
if (salt.Length < 8)
throw new ArgumentException(“salt length must be at least 8”);

var crypto = new Rfc2898DeriveBytes(data, salt, iterations);
var result = crypto.GetBytes(outputLength);

return result;
}

public override bool Verify(byte[] data, byte[] salt, int iterations, byte[] compareTo)
{
IStructuralEquatable output = Hash(data, salt, iterations, compareTo.Length);
return output.Equals(compareTo, StructuralComparisons.StructuralEqualityComparer);
}
}
[/csharp]
The Hash method is quite simple. We provide the supplied arguments to the Rfc2989DeriveBytes instance which takes care of all the heavy lifting. The Verify method calls Hash and then compares all the bytes in the derived hash with the hash to be compared with. Now if we implement the same but instead use an MD5 hash generator we get this:
[csharp]
public class MD5HashService :  HashService
{
public override byte[] Hash(byte[] data, byte[] salt, int iterations = 1000, int outputLength = 64)
{
var crypto = new MD5CryptoServiceProvider();
var output = new byte[outputLength];

crypto.TransformBlock(data, 0, data.Length, output, 0);
salt.CopyTo(output, output.Length – salt.Length);

return output;
}

public override bool Verify(byte[] data, byte[] salt, int iterations, byte[] compareTo)
{
IStructuralEquatable output = Hash(data, salt, iterations, compareTo.Length);
return output.Equals(compareTo, StructuralComparisons.StructuralEqualityComparer);
}
}
[/csharp]
I ran a benchmark against these two services. Both tests generate a list of 100 passwords and use them to run 1000 hashes. The MD5 hash service was able to do this in about 7ms, the PBKDF2 service took about 28 seconds (28000ms – or 280ms per hash). That’s 4000 times longer! So you can see from this simple example how much more effective PBKDF2 can be against brute force cracking attacks – increasing the iterations parameter supplied to the Rfc2898HashService hash service will make hashing take even longer. Here in this example we have used 1000, in contrast iOS 3 uses 2,000 and iOS 4 uses 10000. Note that MD5 is already considered a very bad choice because of its known vulnerabilities.

Future proofing

The iterations parameter used to hash a password could be increased over time as the value for what is considered reasonably secure grows as hardware performance improves. The problem is after a system is launched you have all these un-reversible passwords that were hashed using a specific number of iterations and salt length. One strategy is to version these value sets. So for example at launch version 1 is a salt length of 16 and an iterations count of 10000. You can store that in some fashion and read the version number from the hash and match it too the correct iteration parameter value. Let’s examine how we might implement that:

First we will modify the IHashService interface:
[csharp]
byte[] Hash(byte[] data, byte[] salt, int version, int iterations = 1000, int outputLength = 64);
bool Verify(byte[] data, byte[] salt, IDictionary<int, HashParameters> hashParameters, byte[] compareTo);
[/csharp]
And create this class to contain our parameter values.
[csharp]
public class HashParameters
{
public int Iterations { get; set; }
public int Version { get; set; }
}
[/csharp]
Next we will change our Hash method to embed the version number in the resulting byte array.
[csharp]
public override byte[] Hash(byte[] data, byte[] salt, int version, int iterations = 1000, int outputLength = 64)
{
if (salt.Length < 8)
throw new ArgumentException(“salt length must be at least 8”);

var crypto = new Rfc2898DeriveBytes(data, salt, iterations);
var hash = crypto.GetBytes(outputLength – 4);
var result = new byte[4 + hash.Length];

BitConverter.GetBytes(version).CopyTo(result, 0);
hash.CopyTo(result, 4);

return result;
}
[/csharp]
So here we’ve written the version number out as the first 4 bytes of the hash. Now we have an embedded reference to the hash parameters used without actually revealing that information in the hash itself. We will need to modify the Verify method as well to be aware of this extra data.
[csharp]
public override bool Verify(byte[] data, byte[] salt, IDictionary<int, HashParameters> hashParameters, byte[] compareTo)
{
var version = ReadVersionFromData(compareTo);

if (!hashParameters.ContainsKey(version))
return false;

IStructuralEquatable output = Hash(data, salt, version, hashParameters[version].Iterations, compareTo.Length);

return output.Equals(compareTo, StructuralComparisons.StructuralEqualityComparer);
}

private int ReadVersionFromData(byte[] data)
{
if (data == null) throw new ArgumentNullException(“data”);

if (data.Length < 4)
return 0;

return BitConverter.ToInt32(data, 0);
}
[/csharp]
And that’s it. We modified the Verify method to accept the dictionary of hash parameters that have been for the lifecycle of the application. It looks up the version from the hash and pull the corresponding iteration value from the HashParameters class and passes it to the Hash method. Using an approach like this you can create a new version of the hash parameters for a live application and have it used for all new passwords while existing legacy passwords will still continue to function. Nice!

Transport Security

Most web frontends will submit a plain text password to the server where it will then be hashed (as opposed to creating the hash on the client). For this reason it is vital to ensure that authentication is done with a website over SSL to ensure that plain text passwords cannot be intercepted.

So in conclusion if you are going to implement a secure password process ensure you use the right algorithm for the job and ensure you use good values. This is serious stuff!

Example code: https://github.com/worldspawn/passwordtool

References