14

I need to create a PBKDF2-SHA256 password hash, but am having some trouble.

I downloaded the Bouncy Castle repo, but got a bit stuck finding what I was looking for in the Unit Tests.

Found some sample code here, but this only does SHA1. The key bit of code is:

/// <summary>
/// Computes the PBKDF2-SHA1 hash of a password.
/// </summary>
/// <param name="password">The password to hash.</param>
/// <param name="salt">The salt.</param>
/// <param name="iterations">The PBKDF2 iteration count.</param>
/// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
/// <returns>A hash of the password.</returns>
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
{
    var pdb = new Pkcs5S2ParametersGenerator();
    pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
                 iterations);
    var key = (KeyParameter)pdb.GenerateDerivedMacParameters(outputBytes * 8);
    return key.GetKey();
}

I need to change this from SHA1 to SHA256.

From the Java documentation and this post, it looked like the following would be possible, but there is no overload on the constructor in the C# library.

var pdb = new Pkcs5S2ParametersGenerator(new Sha256Derived());

Finding another article on stack overflow, i thought the following might be possible, but the SHA hashing algorithms are not in the lookup list ,so the following will not work.

var bcparam = (KeyParameter)pdb.GenerateDerivedParameters("sha256", outputBytes * 8);

What do I need to do to get this working please?

Note: If you read this and don't know how in Bouncy Castle, but do know another way, I'd still appreciate your help.

Community
  • 1
  • 1
JsAndDotNet
  • 16,260
  • 18
  • 100
  • 123
  • The source code for Pkcs5S2ParametersGenerator() is available for you to modify. Why not do it? – President James K. Polk Jan 22 '16 at 21:14
  • 1
    @JamesKPolk - A valid comment. The reasons are: A. Because I don't understand it that well and B. I believe rolling your own in any way is considered a serious no-no when it comes to encryption. However maybe as this is just tampering with the setup it might be ok. I assumed there must be a way, but maybe the C# BouncyCastle is lagging behind the java version and it's just not included? If there really isn't a way to do this in C# then I might try a pull request and update the code, based on the Java version. Would really rather there was something available that I could just use. – JsAndDotNet Jan 23 '16 at 11:22

1 Answers1

17

EDIT (Previous answer history removed for brevity)

There is now a Bouncy Castle Crypto API NuGet package that can be used. Alternatively, you can get the source directly from GitHub, which will work. I had got the standard Bouncy Castle from NuGet, which had not been updated to 1.8.1 at the time of writing.

For the benefit of searchers, here is a C# helper class for hashing. Have tested on multiple threads and seems fine.

NOTE: This class also works for the .NET Core version of the library BouncyCastle.NetCore

/// <summary>
/// Contains the relevant Bouncy Castle Methods required to encrypt a password.
/// References NuGet Package BouncyCastle.Crypto.dll
/// </summary>
public class BouncyCastleHashing
{
    private SecureRandom _cryptoRandom;

    public BouncyCastleHashing()
    {
        _cryptoRandom = new SecureRandom();
    }

    /// <summary>
    /// Random Salt Creation
    /// </summary>
    /// <param name="size">The size of the salt in bytes</param>
    /// <returns>A random salt of the required size.</returns>
    public byte[] CreateSalt(int size)
    {
        byte[] salt = new byte[size];
        _cryptoRandom.NextBytes(salt);
        return salt;
    }

    /// <summary>
    /// Gets a PBKDF2_SHA256 Hash  (Overload)
    /// </summary>
    /// <param name="password">The password as a plain text string</param>
    /// <param name="saltAsBase64String">The salt for the password</param>
    /// <param name="iterations">The number of times to encrypt the password</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <returns>A base64 string of the hash.</returns>
    public string PBKDF2_SHA256_GetHash(string password, string saltAsBase64String, int iterations, int hashByteSize)
    {
        var saltBytes = Convert.FromBase64String(saltAsBase64String);

        var hash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);

        return Convert.ToBase64String(hash);
    }

    /// <summary>
    /// Gets a PBKDF2_SHA256 Hash (CORE METHOD)
    /// </summary>
    /// <param name="password">The password as a plain text string</param>
    /// <param name="salt">The salt as a byte array</param>
    /// <param name="iterations">The number of times to encrypt the password</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <returns>A the hash as a byte array.</returns>
    public byte[] PBKDF2_SHA256_GetHash(string password, byte[] salt, int iterations, int hashByteSize)
    {
        var pdb = new Pkcs5S2ParametersGenerator(new Org.BouncyCastle.Crypto.Digests.Sha256Digest());
        pdb.Init(PbeParametersGenerator.Pkcs5PasswordToBytes(password.ToCharArray()), salt,
                     iterations);
        var key = (KeyParameter)pdb.GenerateDerivedMacParameters(hashByteSize * 8);
        return key.GetKey();
    }

    /// <summary>
    /// Validates a password given a hash of the correct one. (OVERLOAD)
    /// </summary>
    /// <param name="password">The original password to hash</param>
    /// <param name="salt">The salt that was used when hashing the password</param>
    /// <param name="iterations">The number of times it was encrypted</param>
    /// <param name="hashByteSize">The byte size of the final hash</param>
    /// <param name="hashAsBase64String">The hash the password previously provided as a base64 string</param>
    /// <returns>True if the hashes match</returns>
    public bool ValidatePassword(string password, string salt, int iterations, int hashByteSize, string hashAsBase64String)
    {
        byte[] saltBytes = Convert.FromBase64String(salt);
        byte[] actualHashBytes = Convert.FromBase64String(hashAsBase64String);
        return ValidatePassword(password, saltBytes, iterations, hashByteSize, actualHashBytes);
    }

    /// <summary>
    /// Validates a password given a hash of the correct one (MAIN METHOD).
    /// </summary>
    /// <param name="password">The password to check.</param>
    /// <param name="correctHash">A hash of the correct password.</param>
    /// <returns>True if the password is correct. False otherwise.</returns>
    public bool ValidatePassword(string password, byte[] saltBytes, int iterations, int hashByteSize, byte[] actualGainedHasAsByteArray)
    {
        byte[] testHash = PBKDF2_SHA256_GetHash(password, saltBytes, iterations, hashByteSize);
        return SlowEquals(actualGainedHasAsByteArray, testHash);
    }

    /// <summary>
    /// Compares two byte arrays in length-constant time. This comparison
    /// method is used so that password hashes cannot be extracted from
    /// on-line systems using a timing attack and then attacked off-line.
    /// </summary>
    /// <param name="a">The first byte array.</param>
    /// <param name="b">The second byte array.</param>
    /// <returns>True if both byte arrays are equal. False otherwise.</returns>
    private bool SlowEquals(byte[] a, byte[] b)
    {
        uint diff = (uint)a.Length ^ (uint)b.Length;
        for (int i = 0; i < a.Length && i < b.Length; i++)
            diff |= (uint)(a[i] ^ b[i]);
        return diff == 0;
    }

}

Usage Example

public void CreatePasswordHash_Single()
{
    int iterations = 100000; // The number of times to encrypt the password - change this
    int saltByteSize = 64; // the salt size - change this
    int hashByteSize = 128; // the final hash - change this

    BouncyCastleHashing mainHashingLib = new BouncyCastleHashing();

    var password = "password"; // That's really secure! :)

    byte[] saltBytes = mainHashingLib.CreateSalt(saltByteSize);
    string saltString = Convert.ToBase64String(saltBytes);

    string pwdHash = mainHashingLib.PBKDF2_SHA256_GetHash(password, saltString, iterations, hashByteSize);

    var isValid = mainHashingLib.ValidatePassword(password, saltBytes, iterations, hashByteSize, Convert.FromBase64String(pwdHash));

}
JsAndDotNet
  • 16,260
  • 18
  • 100
  • 123
  • confirmed Bouncy Castle Crypto API nuget package referenced above is now version 1.8.1 – kyle Apr 04 '17 at 19:25
  • @HockeyJ Hope I'm not late for the party, how to tell what is good number for iterationCount parameter? – ramires.cabral May 04 '18 at 17:47
  • 1
    @ramires.cabral - It's a judgement call between speed and security. At one point 4,000 was the norm and would probably still be considered 'secure' today. Apple uses 10,000 for iTunes - https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet#Work_Factor . I chose 100,000 in this example as it is super secure and wouldn't give away the number I actually use when hashing. – JsAndDotNet May 08 '18 at 07:56