How to create a PBKDF2-SHA256 password hash in C# / Bouncy Castle

后端 未结 1 1706
别那么骄傲
别那么骄傲 2021-02-13 16:52

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 t

相关标签:
1条回答
  • 2021-02-13 17:42

    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));
    
    }
    
    0 讨论(0)
提交回复
热议问题