How to store P@ssw0rd$ today in .NET



Password security – the DONTs

Well, we all know that it’s nonsense to store passwords in plain textNever ever do that.

Do not encrypt  your passwords.  While “encryption” may sound secure once your key has been compromised your “encrypted data” is nothing more than plain text.

What comes in mind when dealing with that question is hashes and salts.  While that’s basically the way to go, do not rely on generic hashes like f.e. MD5 or SHA.  While this may have been OK in 2005, we now have 2018 and such a GPU power for brute fore attacks available that billions of passwords can be iterated in the blink of an eye.

Password security – HOW TO

Today you should rely on one of these hash functions:

Implementation in .NET

PBKDF2 is the way to go in .NET since we have an implementation for it: the Rfc2898DeriveBytes Class.  It’s more secure to rely on this implementation than using some random 3rd party implementation of BCRYPT or SCRYPT.

Generating a salt of a certain length:

1
2
3
4
5
6
7
8
9
byte[] GenerateSalt(int length)
{
    var bytes = new byte[length];
    using (var rng = new RNGCryptoServiceProvider())
    {
        rng.GetBytes(bytes);
    }
    return bytes;
}

Generating a password hash using PBKDF:

1
2
3
4
5
6
7
byte[] GenerateHash(byte[] password, byte[] salt, int iterations, int length)
{
    using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations))
    {
        return deriveBytes.GetBytes(length);
    }
}

Storing and reusing Passwords

Above hashing is for validation-only of a password which the user enters.  If you are storing the password for reuse, such as supplying it to a third party, use the Windows Data Protection API (DPAPI). This uses operating system generated and protected keys and the Triple DES encryption algorithm to encrypt and decrypt information. This means your application does not have to worry about generating and protecting the encryption keys, a major concern when using cryptography.

To encrypt a piece of data, use ProtectedData.Protect():

1
2
3
4
5
6
7
8
9
10
11
12
// Data to protect.
// Convert a string to a byte[] using Encoding.UTF8.GetBytes().byte[] plaintext;

// generate additional entropy (= initialization vector)
byte[] entropy = new byte[20];
using(RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
    rng.GetBytes(entropy);
}

byte[] ciphertext = ProtectedData.Protect(plaintext, entropy,
    DataProtectionScope.CurrentUser);

Use ProtectedData.Unprotect() to get access again:

1
2
byte[] plaintext= ProtectedData.Unprotect(ciphertext, entropy,
    DataProtectionScope.CurrentUser);



see also: Hash Passwords in ASP.NET CORE



.