45

I'm writing a register form for a application but still having problems with being new to c#.

I am looking to encrypt/hash passwords to md5 or sha-256, preferably sha-256.

Any good examples? I want it to be able to take the information from "string password;" and then hash it and store in the variable "string hPassword;". Any ideas?

Sean
  • 541
  • 1
  • 6
  • 4
  • 4
    What are you going to do with the hashed password? Store it in a database? Then simply hashing it is not enough ( [Rainbow table](http://en.wikipedia.org/wiki/Rainbow_table) ). Use salts. – dtb Dec 01 '10 at 22:47
  • 2
    I will be storing it in a database. What do you recommend. – Sean Dec 01 '10 at 23:48
  • 1
    People please be aware that the answers below are getting out of date. And keep in mind you should never ever use MD5 for hashing password any more. It's old, broken, and obsolete. The easiest, up-to-date and secure solution for most people would be [bcrypt](https://bcrypt.codeplex.com/). At least when this comment was written :) Argon2 is probably going to be the standard recommendation soon. – Sámal Rasmussen Apr 07 '16 at 09:23

9 Answers9

80

Don't use a simple hash, or even a salted hash. Use some sort of key-strengthening technique like bcrypt (with a .NET implementation here) or PBKDF2 (with a built-in implementation).

Here's an example using PBKDF2.

To generate a key from your password...

string password = GetPasswordFromUserInput();

// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
    byte[] salt = deriveBytes.Salt;
    byte[] key = deriveBytes.GetBytes(20);  // derive a 20-byte key

    // save salt and key to database
}

And then to test if a password is valid...

string password = GetPasswordFromUserInput();

byte[] salt, key;
// load salt and key from database

using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
    byte[] newKey = deriveBytes.GetBytes(20);  // derive a 20-byte key

    if (!newKey.SequenceEqual(key))
        throw new InvalidOperationException("Password is invalid!");
}
LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 3
    I assume in your post where you say **of** PBKDF2, you intended to say **OR**. I would not recommend the use of bcrypt in .NET, StackOverflow abandoned bcrypt to use PBKDF2. See the remarks from Kevin Montrose on the StackOverflow blog http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/ – Chris Marisic Aug 08 '11 at 16:48
  • 1
    @Chris: Yes, I meant "or PBKDF2"; updated. I'd personally go for PBKDF2 too, although I'm not aware of any strong reasons not to use bcrypt. (I've only ever heard good things said about it.) – LukeH Aug 08 '11 at 17:09
  • 2
    The issue with bcrypt is that there is no .NET implementation of it that has been verified, this is why StackOverflow choose to drop their usage of bcrypt. They did not wish to spend the resources to have the implementation verified whereas the included PBKDF2 native implementation in .NET has been verified for Microsoft already. – Chris Marisic Aug 08 '11 at 17:09
  • only thing I would add would be to change the salt length you have put, the wiki link says "The standard recommends a salt length of at least 64 bits.". – Jon Nov 23 '12 at 20:27
  • @Jon: 20 bytes is 160 bits. – LukeH Nov 24 '12 at 01:27
  • doh... my bad... should have read that more closely... have used your code snippet in my current app, most grateful. – Jon Nov 25 '12 at 17:03
  • 1
    A direct link to where StackOverflow details changing to use PBKDF2 is here: http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/#comment-59112 – Brad Parks Aug 09 '13 at 13:18
  • @LukeH, [What data type to use when storing PBKDF2 encrypted passwords ?](http://stackoverflow.com/questions/12855336/what-data-type-to-use-when-storing-pbkdf2-encrypted-passwords) it seems `nvarchar(max)` has performance issue , what you think can is use [128 length](http://stackoverflow.com/questions/2863034/sql-serverwhat-data-type-to-use-for-password-salt-and-hash-values-and-what-len) will the length change ? – Shaiju T Jan 13 '16 at 11:22
  • 1
    @NH. - You're right, that link isn't good anymore... I used the Wayback Machine to go and find the old version of that page, and it turns out it was in the comments of the page, which have gone missing since they moved the domain to a `.blog` domain. So here is [a link to where StackOverflow details changing to use PBKDF2](https://web.archive.org/web/20130501134327/http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/). – Brad Parks Oct 19 '17 at 21:57
55

You're going to want to use the System.Security.Cryptography namespace; specifically, the MD5 class or the SHA256 class.

Drawing a bit from the code on this page, and with the knowledge that both classes have the same base class (HashAlgorithm), you could use a function like this:

public string ComputeHash(string input, HashAlgorithm algorithm)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);

   return BitConverter.ToString(hashedBytes);
}

Then you could call it like this (for MD5):

string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());

Or for SHA256:

string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());

Edit: Adding Salt Support
As dtb pointed out in the comments, this code would be stronger if it included the ability to add salt. If you're not familiar with it, salt is a set of random bits that are included as an input to the hashing function, which goes a long way to thwart dictionary attacks against a hashed password (e.g., using a rainbow table). Here's a modified version of the ComputeHash function that supports salt:

public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
   Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

   // Combine salt and input bytes
   Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
   salt.CopyTo(saltedInput, 0);
   inputBytes.CopyTo(saltedInput, salt.Length);

   Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);

   return BitConverter.ToString(hashedBytes);
}

Hope this has been helpful!

Donut
  • 110,061
  • 20
  • 134
  • 146
  • OP has a string and wants the hash of that string as a string. The cryptography classes deal only with byte arrays. – dtb Dec 01 '10 at 22:50
  • `ASCIIEncoding.Default` confusingly returns UTF-16 encoding. I suggest `Encoding.UTF8` (ASCII would be too limited). – dtb Dec 01 '10 at 22:56
  • 1
    If you now also add a `byte[] salt` parameter to your `ComputeHash` method and prepend those bytes to `inputBytes` then I officially love your answer :-) – dtb Dec 01 '10 at 23:05
  • 8
    Using PBKDF2 (via `Rfc2898DeriveBytes`) is more secure and actually requires *less* code. – LukeH Dec 02 '10 at 15:52
  • 4
    `MD5CryptoServiceProvider` and `SHA256CryptoServiceProvider` (and related `HashAlgorithm`s) implement the `IDisposable` interface and therefore their creation and lifetime should be optimally wrapped in a `using` block rather than the new object just being passed as a parameter. – Jesse C. Slicer May 11 '12 at 21:23
  • @Sammi - StackOverflow has abandoned Bcrypt in favour of PBKDF2 – Danny Watson Jan 03 '17 at 16:07
6

You should always salt the password before hashing when storing them in the database.

Recommended database columns:

  • PasswordSalt : int
  • PasswordHash : binary(20)

Most posts you find online will talk about ASCII encoding the salt and hash, but that is not needed and only add unneeded computation. Also if you use SHA-1, then the output will only be 20 bytes so your hash field in the database only needs to be 20 bytes in length. I understand your asking about SHA-256, but unless you have a compelling reason, using SHA-1 with a salt value will be sufficient in most business practices. If you insist on SHA-256, then the hash field in the database needs to be 32 bytes in length.

Below are a few functions that will generate the salt, compute the hash and verify the hash against a password.

The salt function below generates a cryptographically strong salt as an Integer from 4 cryptographically created random bytes.

private int GenerateSaltForPassword()
{
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] saltBytes = new byte[4];
    rng.GetNonZeroBytes(saltBytes);
    return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}

The password can then be hashed using the salt with the function below. The salt is concatenated to the password and then the hash is computed.


private byte[] ComputePasswordHash(string password, int salt)
{
    byte[] saltBytes = new byte[4];
    saltBytes[0] = (byte)(salt >> 24);
    saltBytes[1] = (byte)(salt >> 16);
    saltBytes[2] = (byte)(salt >> 8);
    saltBytes[3] = (byte)(salt);

    byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);

    byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
    System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
    System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);

    SHA1 sha1 = SHA1.Create();
    return sha1.ComputeHash(preHashed);
}

Checking the password can be done simply by computing the hash and then comparing it to the expected hash.


private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
    byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);

    return hashedPassword.SequenceEqual(correctPasswordHash);
}

Adam Spicer
  • 2,703
  • 25
  • 37
  • 4
    -1 for plain SHA-1. Use a slow hash, such as PBKDF2, bcrypt or scrypt. It's also recommended to use a 64 bit salt, but that's a minor issue. – CodesInChaos May 11 '12 at 21:16
  • SHA1 is not 100% reliable, though it took a Google team 2 years to prove it: https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html May as well use SHA-256 or SHA3 – Daniel Williams Mar 30 '18 at 01:57
3

TL;DR use Microsoft.AspNetCore.Cryptography.KeyDerivation, implementing PBKDF2 with SHA-512.

The good idea to get started with password hashing is to look at what OWASP guidelines say. The list of recommended algorithms includes Argon2, PBKDF2, scrypt, and bcrypt. All these algorithms can be tuned to adjust the time it takes to hash a password, and, correspondingly, the time to crack it via brute-force. All these algorithms utilize salt to protect from rainbow tables attacks.

Neither of these algorithms is terribly weak, but there are some differences:

  • bcrypt has been around for almost 20 years, has been widely used and has withstood the test of time. It is pretty resistant to GPU attacks, but not to FPGA
  • Argon2 is the newest addition, being a winner of 2015 Password hashing competition. It has better protection against GPU and FPGA attacks, but is a bit too recent to my liking
  • I don't know much about scrypt. It has been designed to thwart GPU- and FPGA- accelerated attacks, but I've heard it turned out to be not as strong as originally claimed
  • PBKDF2 is a family of algorithms parametrized by the different hash functions. It does not offer a specific protection against GPU or ASIC attacks, especially if a weaker hash function like SHA-1 is used, but it is, however, FIPS-certified if it matters to you, and still acceptable if the number of iterations is large enough.

Based on algorithms alone, I would probably go with bcrypt, PBKDF2 being the least favorable.

However, it's not the full story, because even the best algorithm can be made insecure by a bad implementation. Let's look at what is available for .NET platform:

  • Bcrypt is available via bcrypt.net. They say the implementation is based on Java jBCrypt. Currently there are 6 contributors and 8 issues (all closed) on github. Overall, it looks good, however, I don't know if anyone has made an audit of the code, and it's hard to tell whether an updated version will be available soon enough if a vulnerability is found. I've heard Stack Overflow moved away from using bcrypt because of such reasons
  • Probably the best way to use Argon2 is through bindings to the well-known libsodium library, e.g. https://github.com/adamcaudill/libsodium-net. The idea is that the most of the crypto is implemented via libsodium, which has considerable support, and the 'untested' parts are pretty limited. However, in cryptography details mean a lot, so combined with Argon2 being relatively recent, I'd treat it as an experimental option
  • For a long time, .NET had a built-in an implementation of PBKDF2 via Rfc2898DeriveBytes class. However, the implementation can only use SHA-1 hash function, which is deemed too fast to be secure nowadays
  • Finally, the most recent solution is Microsoft.AspNetCore.Cryptography.KeyDerivation package available via NuGet. It provides PBKDF2 algorithm with SHA-1, SHA-256, or SHA-512 hash functions, which is considerably better than Rfc2898DeriveBytes. The biggest advantage here is that the implementation is supplied by Microsoft, and while I cannot properly assess cryptographic diligence of Microsoft developers versus BCrypt.net or libsodium developers, it just makes sense to trust it because if you are running a .NET application, you are heavily relying on Microsoft already. We might also expect Microsoft to release updates if security issues are found. Hopefully.

To summarize the research up to this point, while PBKDF2 might be the least preferred algorithm of the four, the availability of Microsoft-supplied implementation trumps that, so the reasonable decision would be to use Microsoft.AspNetCore.Cryptography.KeyDerivation.

The recent package at the moment targets .NET Standard 2.0, so available in .NET Core 2.0 or .NET Framework 4.6.1 or later. If you use earlier framework version, it is possible to use the previous version of the package, 1.1.3, which targets .NET Framework 4.5.1 or .NET Core 1.0. Unfortunately, it is not possible to use it in even earlier versions of .NET.

The documentation and the working example is available at learn.microsoft.com. However, do not copy-paste it as it is, there are still decisions a developer needs to make.

The first decision is what hash function to use. Available options include SHA-1, SHA-256, and SHA-512. Of those, SHA-1 is definitely too fast to be secure, SHA-256 is decent, but I would recommend SHA-512, because supposedly, its 64-bit operations usage makes it harder to benefit from GPU-based attacks.

Then, you need to choose the password hash output length and the salt length. It doesn't make sense to have output longer than the hash function output (e.g. 512 bits for SHA-512), and it would probably be the most secure to have it exactly like that. For the salt length, opinions differ. 128 bits should be enough, but in any case, the length longer than the hash output length surely doesn't provide any benefits.

Next, there is an iteration count. The bigger it is, the harder password hashes are to crack, but the longer it takes to log users in. I'd suggest to choose it so the hashing takes 0.25 - 1 seconds on the typical production system, and in any case, it should not be less than 10000.

Normally, you would get bytes array as salt and hash values. Use Base64 to convert them to strings. You can opt to use two different columns in the database, or combine salt and password in one column using a separator which is not encountered in Base64.

Don't forget to devise a password hashing storage in a way that allows to seamlessly move to a better hashing algorithm in future.

ovolko
  • 2,777
  • 2
  • 21
  • 26
2

PBKDF2 is using HMACSHA1.......if you want more modern HMACSHA256 or HMACSHA512 implementation and still want key stretching to make the algorithm slower I suggest this API: https://sourceforge.net/projects/pwdtknet/

thashiznets
  • 433
  • 4
  • 6
2

Here is a full implementation of a persistence unaware SecuredPassword class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


    public class SecuredPassword
    {
        private const int saltSize = 256;
        private readonly byte[] hash;
        private readonly byte[] salt;

        public byte[] Hash
        {
        get { return hash; }
    }

    public byte[] Salt
    {
        get { return salt; }
    }

    public SecuredPassword(string plainPassword)
    {
        if (string.IsNullOrWhiteSpace(plainPassword))
            return; 

        using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
        {
            salt = deriveBytes.Salt;
            hash = deriveBytes.GetBytes(saltSize);
        }
    }

    public SecuredPassword(byte[] hash, byte[] salt)
    {
        this.hash = hash;
        this.salt = salt;
    }

    public bool Verify(string password)
    {
        if (string.IsNullOrWhiteSpace(password))
            return false; 

        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
        {
            byte[] newKey = deriveBytes.GetBytes(saltSize);

            return newKey.SequenceEqual(hash);
        }
    }
}

And tests:

 public class SecuredPasswordTests
{
    [Test]
    public void IsHashed_AsExpected()
    {
        var securedPassword = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
        Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
    }

    [Test]
    public void Generates_Unique_Salt()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Salt, Is.Not.Null);
        Assert.That(securedPassword2.Salt, Is.Not.Null);
        Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
    }

    [Test]
    public void Generates_Unique_Hash()
    {
        var securedPassword = new SecuredPassword("password");
        var securedPassword2 = new SecuredPassword("password");

        Assert.That(securedPassword.Hash, Is.Not.Null);
        Assert.That(securedPassword2.Hash, Is.Not.Null);
        Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
    }

    [Test]
    public void Verify_WhenMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("password");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Verify_WhenDifferent_ReturnsFalse()
    {
        var securedPassword = new SecuredPassword("password");
        var result = securedPassword.Verify("Password");
        Assert.That(result, Is.False);
    }

    [Test]
    public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
    {
        var securedPassword = new SecuredPassword("password123");

        var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);

        var result = rehydrated.Verify("password123");
        Assert.That(result, Is.True);
    }

    [Test]
    public void Constructor_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(null));
    }

    [Test]
    public void Constructor_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
    }

    [Test]
    public void Verify_Handles_Null_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
    }

    [Test]
    public void Verify_Handles_Empty_Password()
    {
        Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
    }

    [Test]
    public void Verify_When_Null_Password_ReturnsFalse()
    {
        Assert.That(new SecuredPassword("password").Verify(null), Is.False);
    }
}
JanR
  • 6,052
  • 3
  • 23
  • 30
Sam Shiles
  • 10,529
  • 9
  • 60
  • 72
2

If you are going to be storing the hashed passwords, use bcrypt instead of SHA-256. The problem is that SHA-256 is optimized for speed, which makes it easier for a brute force attack on passwords should someone get access to your database.

Read this article: Enough With The Rainbow Tables: What You Need To Know About Secure Password Schemes and this answer to a previous SO question.

Some quotes from the article:

The problem is that MD5 is fast. So are its modern competitors, like SHA1 and SHA256. Speed is a design goal of a modern secure hash, because hashes are a building block of almost every cryptosystem, and usually get demand-executed on a per-packet or per-message basis.

Speed is exactly what you don’t want in a password hash function.


Finally, we learned that if we want to store passwords securely we have three reasonable options: PHK’s MD5 scheme, Provos-Maziere’s Bcrypt scheme, and SRP. We learned that the correct choice is Bcrypt.

Community
  • 1
  • 1
Jeff Ogata
  • 56,645
  • 19
  • 114
  • 127
  • Well the passwords are going to be stored in a database after hashed. Not stored locally at all. – Sean Dec 01 '10 at 23:42
  • 2
    Ok, then every password should be hashed with a unique salt and you should use bcrypt, or something like it. Hashing with a salt stops attacks using a rainbow table, and using a slower algorithm increases the effort for a brute force attack on the hashed passwords. – Jeff Ogata Dec 01 '10 at 23:55
  • I would not recommend the use of bcrypt in .NET, StackOverflow abandoned bcrypt to use PBKDF2. See the remarks from Kevin Montrose on the StackOverflow blog http://blog.stackoverflow.com/2011/05/stack-exchange-is-an-openid-provider/ – Chris Marisic Aug 08 '11 at 16:46
  • 1
    @ChrisMarisic It's useful to note *why* StackOverflow doesn't use `bcrypt`. When you say *"StackOverflow abandoned bcrypt"*, one might get the impression that there's something wrong with `bcrypt`. – Ian Boyd Aug 18 '12 at 13:11
1

The System.Security.Cryptography.SHA256 class should do the trick:

http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx

James Kovacs
  • 11,549
  • 40
  • 44
1

Please use this as i have the same issues before but could solve it will the litle code snippet

    public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
    {
        Byte[] inputBytes = Encoding.UTF8.GetBytes(input);

        // Combine salt and input bytes
        Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
        salt.CopyTo(saltedInput, 0);
        inputBytes.CopyTo(saltedInput, salt.Length);

        Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);


        StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
        foreach (byte b in hashedBytes)
            hex.AppendFormat("{0:X2}", b);

        return hex.ToString();

    }
  • You should add an explanation, and see [How do I ask a good question?](https://stackoverflow.com/help/how-to-ask) –  May 25 '17 at 23:29