Hash and salt passwords in C#

后端 未结 14 2100
野趣味
野趣味 2020-11-22 04:00

I was just going through one of DavidHayden\'s articles on Hashing User Passwords.

Really I can\'t get what he is trying to achieve.

Here is his code:

<
14条回答
  •  庸人自扰
    2020-11-22 04:34

    I've been reading that hashing functions like SHA256 weren't really intended for use with storing passwords: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

    Instead adaptive key derivation functions like PBKDF2, bcrypt or scrypt were. Here is a PBKDF2 based one that Microsoft wrote for PasswordHasher in their Microsoft.AspNet.Identity library:

    /* =======================
     * HASHED PASSWORD FORMATS
     * =======================
     * 
     * Version 3:
     * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
     * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
     * (All UInt32s are stored big-endian.)
     */
    
    public string HashPassword(string password)
    {
        var prf = KeyDerivationPrf.HMACSHA256;
        var rng = RandomNumberGenerator.Create();
        const int iterCount = 10000;
        const int saltSize = 128 / 8;
        const int numBytesRequested = 256 / 8;
    
        // Produce a version 3 (see comment above) text hash.
        var salt = new byte[saltSize];
        rng.GetBytes(salt);
        var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
    
        var outputBytes = new byte[13 + salt.Length + subkey.Length];
        outputBytes[0] = 0x01; // format marker
        WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
        WriteNetworkByteOrder(outputBytes, 5, iterCount);
        WriteNetworkByteOrder(outputBytes, 9, saltSize);
        Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
        Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
        return Convert.ToBase64String(outputBytes);
    }
    
    public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
    {
        var decodedHashedPassword = Convert.FromBase64String(hashedPassword);
    
        // Wrong version
        if (decodedHashedPassword[0] != 0x01)
            return false;
    
        // Read header information
        var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
        var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
        var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);
    
        // Read the salt: must be >= 128 bits
        if (saltLength < 128 / 8)
        {
            return false;
        }
        var salt = new byte[saltLength];
        Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);
    
        // Read the subkey (the rest of the payload): must be >= 128 bits
        var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
        if (subkeyLength < 128 / 8)
        {
            return false;
        }
        var expectedSubkey = new byte[subkeyLength];
        Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
    
        // Hash the incoming password and verify it
        var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
        return actualSubkey.SequenceEqual(expectedSubkey);
    }
    
    private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
    {
        buffer[offset + 0] = (byte)(value >> 24);
        buffer[offset + 1] = (byte)(value >> 16);
        buffer[offset + 2] = (byte)(value >> 8);
        buffer[offset + 3] = (byte)(value >> 0);
    }
    
    private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
    {
        return ((uint)(buffer[offset + 0]) << 24)
            | ((uint)(buffer[offset + 1]) << 16)
            | ((uint)(buffer[offset + 2]) << 8)
            | ((uint)(buffer[offset + 3]));
    }
    

    Note this requires Microsoft.AspNetCore.Cryptography.KeyDerivation nuget package installed which requires .NET Standard 2.0 (.NET 4.6.1 or higher). For earlier versions of .NET see the Crypto class from Microsoft's System.Web.Helpers library.

    Update Nov 2015
    Updated answer to use an implementation from a different Microsoft library which uses PBKDF2-HMAC-SHA256 hashing instead of PBKDF2-HMAC-SHA1 (note PBKDF2-HMAC-SHA1 is still secure if iterCount is high enough). You can check out the source the simplified code was copied from as it actually handles validating and upgrading hashes implemented from previous answer, useful if you need to increase iterCount in the future.

提交回复
热议问题