How to hash a password
Solution 1
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);
Solution 2
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
.
Solution 3
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.
Solution 4
@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);
Solution 5
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:
- Part of the .NET platform. Much safer and trustworthier than building your own crypto algorithm.
- Configurable iteration count and future compatibility (see
PasswordHasherOptions
). - Took Timing Attack into consideration when verifying password (source), just like what PHP and Go did.
Cons:
- Hashed password format incompatible with those hashed by other libraries or in other languages.
Related videos on Youtube
Skoder
Updated on December 08, 2021Comments
-
Skoder over 2 years
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?
-
vcsjones over 13 yearsI would REALLY recommend using SHA1. MD5 is a no-no unless you are maintaining backward compatibility with an existing system. In addition, make sure you put it in a
using
statement or callClear()
on it when you are done using the implementation. -
zerkms over 13 years@vcsjones: I don't want to holy-war here, but
md5
is good enough for the almost all kind of tasks. Its vulnerabilities also refers to very specific situations and almost requires for attacker to know a lot about cryptography. -
vcsjones over 13 years@zerkms point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry".
-
vcsjones over 13 years@zerkms MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the
Clear
method. -
Slartibartfast over 13 yearsWhile I agree wholeheartedly with zerkms, if you have the option between the two, and the difficulty is approximately the same, choose SHA1 or allow for either.
-
zerkms over 13 years@vcsjones: oh, thanks. I did not even know crypto-classes need to be disposed :-S The samples at msdn.microsoft.com/en-us/library/… don't do that in some reason...
-
zerkms over 13 years@Slartibartfast: indeed,
sha1
is better, butmd5
is not so bad as most of people want to present. -
Gerald Davis over 13 yearsNo reason to use MD5 at this point. Given that computation time is insignificant there is no reason to use MD5 except as compatibility with existing systems. Even if MD5 is "good enough" there is no cost with user the far more secure SHA. I am sure zerkms know this the comment is more for the questioner.
-
Chris Rae about 13 yearsSome systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data).
-
milan over 12 yearsSurprisingly no one says why it's bad to use MD5: because it's incredibly fast (say takes microseconds to hash a 5-10 char string), so it's possible to run a brute force attack to find the password for a given md5 hash (say 10 billion possible password 5 chars long => 10000 seconds or less than 3 hours on a single cpu).
-
CodesInChaos almost 12 yearsThree big mistakes: 1) ASCII silently degrades passwords with unusual characters 2) Plain MD5/SHA-1/SHA-2 is fast. 3) You need a salt. | Use PBKDF2, bcrypt or scrypt instead. PBKDF2 is easiest in the Rfc2898DeriveBytes class (not sure if present on WP7)
-
CodesInChaos almost 12 years-1 for using plain SHA-1, which is fast. Use a slow key derivation function, such as PBKDF2, bcrypt or scrypt.
-
Samuel Parkinson almost 12 yearsSee en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea.
-
Mickael V. almost 11 yearsThank you for the update and the detailed comments on your code, big help !
-
Daniel over 9 yearsThis is more like a question, isn't easier, for checking the password, to hash the given password and compare the stored string with the one given by the user and hashed again?
-
csharptest.net over 9 years@Daniel basically the post is about using something more secure than a hash alone. If you simply hash a password, even with salt, your users passwords are going to be compromised (and likely sold/published) before you even have a chance to tell them to change it. Use the above code to make it difficult for the attacker, not easy for the developer.
-
Marco Staffoli almost 9 yearsI find a website what go very deep in the argument: codeproject.com/Articles/704865/… and here is the code that the author propose: crackstation.net/hashing-security.htm#aspsourcecode
-
Luke Vo almost 9 yearsPlease help me clarify: the step 1 should be runned once and use that array forever for an application, is it correct?
-
csharptest.net almost 9 years@DatVM No, new salt for every time you store a hash. that is why it's combined with the hash for storage so that you can verify a password.
-
Mike Cole almost 8 yearsJust to verify, if you do upgrade the hashing engine you'd increment the V1 section of your hash and key off of that?
-
Christian Gollhardt almost 8 yearsYes that is the plan. You would then decide based on
V1
andV2
which verification method you need. -
wa4_tasty_elephant over 7 yearsWhy don't you use SecureString?
-
Christian Gollhardt over 7 yearsIn my scenario there is no need for it @Wavum. The password is provided by the user via web, so this is a bigger atack vector then the possibility that somebodody has access to my server and reads the ram. Anyway if you work with client application, the usage of
SecureString
makes sence. -
Ciprian Jijie over 7 years@csharptest.net thanks, works fine but how I can decript to see initial password ? Thanks
-
csharptest.net over 7 years@CiprianJijie the whole point is your not suppose to be able to.
-
Jesú Castillo almost 7 yearsIn case anyone is doing a VerifyPassword method, if you would like to use Linq and a shorter call for a boolean, this would do: return hash.SequenceEqual(hashBytes.Skip(_saltSize));
-
Nelson Silva almost 7 yearsIm tryin to use this but everytime I run the hash, using the same string, it creates a new hash. is it normal ?
-
Christian Gollhardt almost 7 yearsYes @NelsonSilva. That's because of the salt.
-
lenny over 5 years@csharptest.net What kind of array sizes do you recommend? does the size of the array affect the security much anyway? I don't know that much about hashing/cryptography
-
csharptest.net over 5 years@lenny The primary means of adjusting the “security” of this routine is by adjusting the rounds of PBKDF2 iterations. The size pf the salt can be increased; however, since it is stored with the hash increasing it really adds very minimal complexity.
-
pettys about 5 yearsWith all the copy/pasting of this code (including me), I hope someone speaks up and the post gets revised if an issue is found with it! :)
-
Christian Gollhardt about 5 yearsIn the meantime, you probably want add some more iterations here
return Hash(password, 10000);
, other than that I have had no issues so far and it's in production since ~ 4 years :) For the newer .net core, I also written a class, which lives at code review @pettys -
Albert Lyubarsky over 4 yearsBTW, the previous answer stackoverflow.com/a/57508528/11603057 is not correct and harmful. That is an example of hashing, not password hashing. Must be iterations of the pseudo-random function during the key derivation process. There is no. I cannot comment it or downvote (my low reputation). Please don't miss out uncorrect answers!
-
ibrahimozgon about 4 years@csharptest.net your answer is 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 you don't dispose them. I will write my solution as an answer if someone needs with disposed version.
-
derekbaker783 about 4 yearsFor those curious about warnings in Visual Studio while using 'new Rfc2898DeriveBytes()', see this answer: stackoverflow.com/questions/18648084/…
-
Pang over 3 yearsIs this vulnerable to Timing Attack when verifying the hash? Should we use
CryptographicOperations.FixedTimeEquals()
or something, just like what ... -
Pang over 3 years... PHP did in
password_verify()
and what Go did inConstantTimeCompare()
called byCompareHashAndPassword()
? -
csharptest.net over 3 years@pang for timing you need to also consider user not found. The easiest implementation is that if anything fails in a logon attempt you wait a random amount of time and then throw unauthorized. With a long enough minimum delay you also slow down a single connection’s number of attempts.
-
John Ranger over 3 yearsyou seem to understand crypto quite well. So it would be really kind to provide both the crypto and the verification routine - so that people (like me) with less crypto skills just can use your secure proposition. I have looked up your link over to MS - and unfortunately they also don't provide the verification method.
-
Albert Lyubarsky over 3 years@John Ranger. 1. Enroll your user. 1.1 Take his user name (or email), password and repeat password fields. 1.2 If password == repeat password then make hashed from password. 1.3. Persist the value of hashed and user name. 2. Verification. 2.1 Take user's password and user name. 2.2 make hashed from it. 2.3 Retrieve stored hashed by his user name. 2.4 if stored hashed == current hashed - OK if not NOT OK. That's all.
-
Albert Lyubarsky over 3 yearsOne more thing if user name doesn't exist and nothing to retrieve make slight delay before return NOT OK. And never tell the user what's wrong : user name doesn't exist or stored hashed != current hashed, just general error. After N retries consider to lock the user for K minutes to prevent Brutal Force Attack.
-
marsh-wiggle over 3 years
Most of the other answers here are somewhat out-of-date with today's best practices.
this is from 2012 and "today" is 2020. When anyone reads this it's probably 2030 -
Klyuch over 2 yearsOne more to Cons:
<TUser>
as fake parameter. And it is part of ASP.NET Core Identity, that means in future it could have some dependencies of Identity framework. -
mlaan about 2 yearsIt looks to me like a '$' got lost in your version of IsHashSupported.