I'd like to store the hash of a password on the phone, but I'm not sure how to do it. I can only seem to find encryption methods. How should the password be hashed properly?
This question is related to
c#
security
hash
passwords
windows-phone-7
UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from the https://stackoverflow.com/a/10402129/251311 instead.
You can either use
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
or
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
To get data
as byte array you could use
var data = Encoding.ASCII.GetBytes(password);
and to get back string from md5data
or sha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
Most of the other answers here are somewhat out-of-date with today's best practices. As such here is the application of using PBKDF2/Rfc2898DeriveBytes
to store and verify passwords. The following code is in a stand-alone class in this post: Another example of how to store a salted password hash. The basics are really easy, so here it is broken down:
STEP 1 Create the salt value with a cryptographic PRNG:
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
STEP 2 Create the Rfc2898DeriveBytes and get the hash value:
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
STEP 3 Combine the salt and password bytes for later use:
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
STEP 4 Turn the combined salt+hash into a string for storage
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
STEP 5 Verify the user-entered password against a stored password
/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
if (hashBytes[i+16] != hash[i])
throw new UnauthorizedAccessException();
Note: Depending on the performance requirements of your specific application, the value 100000
can be reduced. A minimum value should be around 10000
.
@csharptest.net's and Christian Gollhardt's answers are great, thank you very much. But after running this code on production with millions of record, I discovered there is a memory leak. RNGCryptoServiceProvider and Rfc2898DeriveBytes classes are derived from IDisposable but we don't dispose of them. I will write my solution as an answer if someone needs with disposed version.
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
using (var rng = new RNGCryptoServiceProvider())
{
byte[] salt;
rng.GetBytes(salt = new byte[SaltSize]);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return $"$HASH|V1${iterations}${base64Hash}";
}
}
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("HASH|V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$HASH|V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
}
Usage:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
Based on csharptest.net's great answer, I have written a Class for this:
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);
// Create hash
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("$MYHASH$V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
Usage:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
A sample hash could be this:
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
As you can see, I also have included the iterations in the hash for easy usage and the possibility to upgrade this, if we need to upgrade.
If you are interested in .net core, I also have a .net core version on Code Review.
public class CryptographyProcessor
{
public string CreateSalt(int size)
{
//Generate a cryptographic random number.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
public string GenerateHash(string input, string salt)
{
byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
SHA256Managed sHA256ManagedString = new SHA256Managed();
byte[] hash = sHA256ManagedString.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
public bool AreEqual(string plainTextInput, string hashedInput, string salt)
{
string newHashedPin = GenerateHash(plainTextInput, salt);
return newHashedPin.Equals(hashedInput);
}
}
Use the below class to Generate a Salt first. Each user needs to have a different salt, we can save it in the database along with the other user properties. The rounds value decides the number of times the password will be hashed.
public class HashSaltWithRounds
{
int saltLength = 32;
public byte[] GenerateSalt()
{
using (var randomNumberGenerator = new RNGCryptoServiceProvider())
{
var randomNumber = new byte[saltLength];
randomNumberGenerator.GetBytes(randomNumber);
return randomNumber;
}
}
public string HashDataWithRounds(byte[] password, byte[] salt, int rounds)
{
using(var rfc2898= new Rfc2898DeriveBytes(password, salt, rounds))
{
return Convert.ToBase64String(rfc2898.GetBytes(32));
}
}
}
We can call it from a console application as follows. I have hashed the password twice using the same salt.
public class Program
{
public static void Main(string[] args)
{
int numberOfIterations = 99;
var hashFunction = new HashSaltWithRounds();
string password = "Your Password Here";
byte[] salt = hashFunction.GenerateSalt();
var hashedPassword1 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
var hashedPassword2 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
Console.WriteLine($"hashedPassword1 :{hashedPassword1}");
Console.WriteLine($"hashedPassword2 :{hashedPassword2}");
Console.WriteLine(hashedPassword1.Equals(hashedPassword2));
Console.ReadLine();
}
}
In ASP.NET Core, use PasswordHasher<TUser>
.
• Namespace: Microsoft.AspNetCore.Identity
• Assembly: Microsoft.Extensions.Identity.Core.dll
(NuGet | Source)
To hash a password, use HashPassword()
:
var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);
To verify a password, use VerifyHashedPassword()
:
var passwordVerificationResult = new PasswordHasher<object?>().VerifyHashedPassword(null, hashedPassword, password);
switch (passwordVerificationResult)
{
case PasswordVerificationResult.Failed:
Console.WriteLine("Password incorrect.");
break;
case PasswordVerificationResult.Success:
Console.WriteLine("Password ok.");
break;
case PasswordVerificationResult.SuccessRehashNeeded:
Console.WriteLine("Password ok but should be rehashed and updated.");
break;
default:
throw new ArgumentOutOfRangeException();
}
Pros:
PasswordHasherOptions
).Cons:
I use a hash and a salt for my password encryption (it's the same hash that Asp.Net Membership uses):
private string PasswordSalt
{
get
{
var rng = new RNGCryptoServiceProvider();
var buff = new byte[32];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
}
private string EncodePassword(string password, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(password);
byte[] src = Encoding.Unicode.GetBytes(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inarray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inarray);
}
I think using KeyDerivation.Pbkdf2 is better than Rfc2898DeriveBytes.
Example and explanation: Hash passwords in ASP.NET Core
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
public class Program
{
public static void Main(string[] args)
{
Console.Write("Enter a password: ");
string password = Console.ReadLine();
// generate a 128-bit salt using a secure PRNG
byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
Console.WriteLine($"Hashed: {hashed}");
}
}
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/
This is a sample code from the article. And it's a minimum security level. To increase it I would use instead of KeyDerivationPrf.HMACSHA1 parameter
KeyDerivationPrf.HMACSHA256 or KeyDerivationPrf.HMACSHA512.
Don't compromise on password hashing. There are many mathematically sound methods to optimize password hash hacking. Consequences could be disastrous. Once a malefactor can get his hands on password hash table of your users it would be relatively easy for him to crack passwords given algorithm is weak or implementation is incorrect. He has a lot of time (time x computer power) to crack passwords. Password hashing should be cryptographically strong to turn "a lot of time" to "unreasonable amount of time".
One more point to add
Hash verification takes time (and it's good). When user enters wrong user name it's takes no time to check that user name is incorrect. When user name is correct we start password verification - it's relatively long process.
For a hacker it would be very easy to understand if user exists or doesn't.
Make sure not to return immediate answer when user name is wrong.
Needless to say : never give an answer what is wrong. Just general "Credentials are wrong".
Source: Stackoverflow.com