Wednesday, November 11, 2015

Best way to secure password using Cryptographic algorithms in C# .NET

Let me explain with some of the common ways of storing passwords in the database with its demerits. By reading this full article you will understand the best way to secure your password  using Cryptographic algorithms in C# .NET.


1. Plain text

Storing password in plain text is the worst way of password management. If the database is compromised by the hacker, with no effort he can reveal all the passwords.

2. Symmetric Key Encryption

One usual way to storing password is using encryption. it's a two-way process. That means the password is encrypted using the secret key when storing and decrypt using the same key for the password authentication.

It's better than storing the password as plain text. But key management is the challenge. Where do you save that key? If it is a database, It won't be difficult for the hacker who got the encrypted password by hacking the database and decrypt it using the same key.

3. Asymmetric Key Encryption

So instead of using symmetric key encryption algorithm. we can use asymmetric key encryption algorithm like RSA where client uses public key to encrypt the password and sends it to the server for storage. When authenticate a private key is used to decrypt the password. That private key should be kept secret. This is also not a great solution as the key management is difficult like the previous way.

4. Hashing

If we use Hashing there won't be any over head of key management. Also no need to decrypt the password back to plain text. As we discussed in my previous article Cryptographic Hashing Algorithm in .NET C#, Hashing is one way operation. Once a data is hashed we cannot reverse and get the original message, It has four important properties,
  • Easy to compute the hash value for any given message
  • Not possible to generate a message from the given hash
  • Not possible to modify a message without changing the hash
  • Not possible to find two different messages with the same hash

Two types of attack is possible on the hashed password. They are,
  1. Brute force attack
  2. Rainbow table attack
Brute force attack

The attacker would try the different combination of passwords hash that is equivalent to the password hash you have stored. Using the latest high performance graphic processor system it is possible to generate billions of random password hash. It only the matter of time to  generate the correct password.

Rainbow table attack

A rainbow table is a listing of all possible plain text permutations of hashed passwords specific to a given hash algorithm. Which is often used for crack the password from the hashed values that we stored in the application database. It can be Giga bytes of size. Once an attacker gains access to a system’s password database, the password cracker compares the rainbow table’s precompiled list of hashes to hashed passwords in the database. The rainbow table relate plaintext possibilities with each of those hashes. Thus attacker could crack the password.

5. Salted Hash

It is common for a web application to store in a database the hash value of a user's password. Without a salt, a successful SQL injection attack may yield easily crackable passwords. Because many users re-use passwords for multiple sites, the use of a salt is an important component of overall web application security


If we append a random value with a hashed password, Which is difficult for the attacker to hack using brute force or rainbow table attack. The random value is called Salt. The Salted hash and the Salt will be stored in the database as the Salt is required when the password authentication.

Salted Hash Sample Code :
using System;
using System.Security.Cryptography;
using System.Text;
static void Main()
{
    const string password = "SampleP455w0rd";
    byte[] salt = GenerateSalt();

    Console.WriteLine("Sample Password : " + password);
    Console.WriteLine("Generated Salt : " + Convert.ToBase64String(salt));
    Console.WriteLine();

    var hashedPassword = HashPasswordWithSalt(Encoding.UTF8.GetBytes(password), salt);

    Console.WriteLine("Salted Hash Password : " + Convert.ToBase64String(hashedPassword));
    Console.WriteLine();
   
    Console.ReadLine();
}
public static byte[] GenerateSalt()
{
    const int saltLength = 32;

    using (var randomNumberGenerator = new RNGCryptoServiceProvider())
    {
        var randomNumber = new byte[saltLength];
        randomNumberGenerator.GetBytes(randomNumber);

        return randomNumber;
    }
}
private static byte[] Combine(byte[] first, byte[] second)
{
    var ret = new byte[first.Length + second.Length];

    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);

    return ret;
}
public static byte[] HashPasswordWithSalt(byte[] toBeHashed, byte[] salt)
{
    using (var sha256 = SHA256.Create())
    {
        var combinedHash = Combine(toBeHashed, salt);

        return sha256.ComputeHash(combinedHash);
    }
}
Console Output



6. Password Based Key Derivation Function (PBKDF2)

Attackers can use super computers for the brute force attack as it could generate large number of random passwords within a sort period of time. To solve this PBKDF2 is used.


The PBKDF2 expect password input with salt also additionally the number of iteration the password should be hashed. It  makes password cracking much more difficult also increase the time taken to generate the hash value. You can see the delay in below sample code based on the number of iteration value u have passed.

When the standard was written in 2000, the recommended minimum number of iterations was 1000, but the parameter is intended to be increased over time as CPU speeds increase. As of 2005 a Kerberos standard recommended 4096 iterations, Apple iOS 3 used 2000, iOS 4 used 10000, while in 2011 LastPass used 5000 iterations for JavaScript clients and 100000 iterations for server-side hashing.

PBKDF2 Sample Code
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
static void Main()
{
    const string passwordToHash = "SamplePassword";

    HashPassword(passwordToHash, 100);

    HashPassword(passwordToHash, 50000);

    HashPassword(passwordToHash, 500000);

    Console.ReadLine();
}
private static void HashPassword(string passwordToHash, int numberOfRounds)
{
    var sw = new Stopwatch();

    sw.Start();
    var hashedPassword = HashPassword(Encoding.UTF8.GetBytes(passwordToHash), GenerateSalt(), numberOfRounds);
    sw.Stop();

    Console.WriteLine("Password to hash : " + passwordToHash);
    Console.WriteLine("PBKDF2 Hashed Password : " + Convert.ToBase64String(hashedPassword));
    Console.WriteLine("Iterations : " + numberOfRounds);
    Console.WriteLine("Elapsed Time : " + sw.ElapsedMilliseconds + "ms");
    Console.WriteLine();
}
public static byte[] GenerateSalt()
{
    using (var randomNumberGenerator = new RNGCryptoServiceProvider())
    {
        var randomNumber = new byte[32];
        randomNumberGenerator.GetBytes(randomNumber);

    return randomNumber;
    }
}
public static byte[] HashPassword(byte[] toBeHashed, byte[] salt, int numberOfRounds)
{
    using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(toBeHashed, salt, numberOfRounds))
    {
        return rfc2898DeriveBytes.GetBytes(32);
    }
}
Console Output