Hash and salt passwords in C#

后端 未结 14 1825
野趣味
野趣味 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:20

    This is how I do it.. I create the hash and store it using the ProtectedData api:

        public static string GenerateKeyHash(string Password)
        {
            if (string.IsNullOrEmpty(Password)) return null;
            if (Password.Length < 1) return null;
    
            byte[] salt = new byte[20];
            byte[] key = new byte[20];
            byte[] ret = new byte[40];
    
            try
            {
                using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
                {
                    randomBytes.GetBytes(salt);
    
                    using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                    {
                        key = hashBytes.GetBytes(20);
                        Buffer.BlockCopy(salt, 0, ret, 0, 20);
                        Buffer.BlockCopy(key, 0, ret, 20, 20);
                    }
                }
                // returns salt/key pair
                return Convert.ToBase64String(ret);
            }
            finally
            {
                if (salt != null)
                    Array.Clear(salt, 0, salt.Length);
                if (key != null)
                    Array.Clear(key, 0, key.Length);
                if (ret != null)
                    Array.Clear(ret, 0, ret.Length);
            } 
        }
    
        public static bool ComparePasswords(string PasswordHash, string Password)
        {
            if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
            if (PasswordHash.Length < 40 || Password.Length < 1) return false;
    
            byte[] salt = new byte[20];
            byte[] key = new byte[20];
            byte[] hash = Convert.FromBase64String(PasswordHash);
    
            try
            {
                Buffer.BlockCopy(hash, 0, salt, 0, 20);
                Buffer.BlockCopy(hash, 20, key, 0, 20);
    
                using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                {
                    byte[] newKey = hashBytes.GetBytes(20);
    
                    if (newKey != null)
                        if (newKey.SequenceEqual(key))
                            return true;
                }
                return false;
            }
            finally
            {
                if (salt != null)
                    Array.Clear(salt, 0, salt.Length);
                if (key != null)
                    Array.Clear(key, 0, key.Length);
                if (hash != null)
                    Array.Clear(hash, 0, hash.Length);
            }
        }
    
        public static byte[] DecryptData(string Data, byte[] Salt)
        {
            if (string.IsNullOrEmpty(Data)) return null;
    
            byte[] btData = Convert.FromBase64String(Data);
    
            try
            {
                return ProtectedData.Unprotect(btData, Salt, DataProtectionScope.CurrentUser);
            }
            finally
            {
                if (btData != null)
                    Array.Clear(btData, 0, btData.Length);
            }
        }
    
        public static string EncryptData(byte[] Data, byte[] Salt)
        {
            if (Data == null) return null;
            if (Data.Length < 1) return null;
    
            byte[] buffer = new byte[Data.Length];
    
            try
            {
                Buffer.BlockCopy(Data, 0, buffer, 0, Data.Length);
                return System.Convert.ToBase64String(ProtectedData.Protect(buffer, Salt, DataProtectionScope.CurrentUser));
            }
            finally
            {
                if (buffer != null)
                    Array.Clear(buffer, 0, buffer.Length);
            }
        }
    
    0 讨论(0)
  • 2020-11-22 04:25

    What blowdart said, but with a little less code. Use Linq or CopyTo to concatenate arrays.

    public static byte[] Hash(string value, byte[] salt)
    {
        return Hash(Encoding.UTF8.GetBytes(value), salt);
    }
    
    public static byte[] Hash(byte[] value, byte[] salt)
    {
        byte[] saltedValue = value.Concat(salt).ToArray();
        // Alternatively use CopyTo.
        //var saltedValue = new byte[value.Length + salt.Length];
        //value.CopyTo(saltedValue, 0);
        //salt.CopyTo(saltedValue, value.Length);
    
        return new SHA256Managed().ComputeHash(saltedValue);
    }
    

    Linq has an easy way to compare your byte arrays too.

    public bool ConfirmPassword(string password)
    {
        byte[] passwordHash = Hash(password, _passwordSalt);
    
        return _passwordHash.SequenceEqual(passwordHash);
    }
    

    Before implementing any of this however, check out this post. For password hashing you may want a slow hash algorithm, not a fast one.

    To that end there is the Rfc2898DeriveBytes class which is slow (and can be made slower), and may answer the second part of the original question in that it can take a password and salt and return a hash. See this question for more information. Note, Stack Exchange is using Rfc2898DeriveBytes for password hashing (source code here).

    0 讨论(0)
  • 2020-11-22 04:25
     protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
    {
    string salt =createSalt(10);
    string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
    m_SaltHash_TextBox.Text=Salt;
     m_SaltSHA256Hash_TextBox.Text=hashedPassword;
    
    }
     public string createSalt(int size)
    {
     var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
     var buff= new byte[size];
    rng.GetBytes(buff);
     return Convert.ToBase64String(buff);
    }
    
    
     public string GenerateSHA256Hash(string input,string salt)
    {
     byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
     new System.Security.Cyptography.SHA256Managed();
     byte[]hash=sha256hashString.ComputedHash(bytes);
     return bytesArrayToHexString(hash);
      }
    
    0 讨论(0)
  • 2020-11-22 04:26

    Salt is used to add an extra level of complexity to the hash, to make it harder to brute-force crack.

    From an article on Sitepoint:

    A hacker can still perform what's called a dictionary attack. Malicious parties may make a dictionary attack by taking, for instance, 100,000 passwords that they know people use frequently (e.g. city names, sports teams, etc.), hash them, and then compare each entry in the dictionary against each row in the database table. If the hackers find a match, bingo! They have your password. To solve this problem, however, we need only salt the hash.

    To salt a hash, we simply come up with a random-looking string of text, concatenate it with the password supplied by the user, then hash both the randomly generated string and password together as one value. We then save both the hash and the salt as separate fields within the Users table.

    In this scenario, not only would a hacker need to guess the password, they'd have to guess the salt as well. Adding salt to the clear text improves security: now, if a hacker tries a dictionary attack, he must hash his 100,000 entries with the salt of every user row. Although it's still possible, the chances of hacking success diminish radically.

    There is no method automatically doing this in .NET, so you'll have go with the solution above.

    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-11-22 04:34

    If you dont use asp.net or .net core there is also an easy way in >= .Net Standard 2.0 projects.

    First you can set the desired size of the hash, salt and iteration number which is related to the duration of the hash generation:

    private const int SaltSize = 32;
    private const int HashSize = 32;
    private const int IterationCount = 10000;
    

    To generare the password hash and salt you can use something like this:

    public static string GeneratePasswordHash(string password, out string salt)
    {
        using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
        {
            rfc2898DeriveBytes.IterationCount = IterationCount;
            byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
            byte[] saltData = rfc2898DeriveBytes.Salt;
            salt = Convert.ToBase64String(saltData);
            return Convert.ToBase64String(hashData);
        }
    }
    

    To verify if the password which the user entered is valid you can check with the values in your database:

    public static bool VerifyPassword(string password, string passwordHash, string salt)
    {
        using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
        {
            rfc2898DeriveBytes.IterationCount = IterationCount;
            rfc2898DeriveBytes.Salt = Convert.FromBase64String(salt);
            byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
            return Convert.ToBase64String(hashData) == passwordHash;
        }
    }
    

    The following unit test shows the usage:

    string password = "MySecret";
    
    string passwordHash = PasswordHasher.GeneratePasswordHash(password, out string salt);
    
    Assert.True(PasswordHasher.VerifyPassword(password, passwordHash, salt));
    Assert.False(PasswordHasher.VerifyPassword(password.ToUpper(), passwordHash, salt));
    

    Microsoft Rfc2898DeriveBytes Source

    0 讨论(0)
提交回复
热议问题