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?
11 Answers
Most of the other answers here are somewhat outdated considering today's (year 2012) best practices.
The most robust password-hashing algorithm that's natively available in .NET is PBKDF2, represented by the Rfc2898DeriveBytes
class.
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
.

- 34,835
- 7
- 69
- 104

- 62,602
- 11
- 71
- 89
-
Thank you for the update and the detailed comments on your code, big help ! – Mickael V. May 06 '13 at 08:00
-
This 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? – Daniel Nov 11 '14 at 02:42
-
10@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. – csharptest.net Nov 11 '14 at 03:05
-
1I find a website what go very deep in the argument: http://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right and here is the code that the author propose: https://crackstation.net/hashing-security.htm#aspsourcecode – Marco Staffoli May 04 '15 at 14:24
-
Please help me clarify: the step 1 should be runned once and use that array forever for an application, is it correct? – Luke Vo May 08 '15 at 08:56
-
3@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. – csharptest.net May 08 '15 at 22:39
-
@csharptest.net thanks, works fine but how I can decript to see initial password ? Thanks – Ciprian Jijie Nov 24 '16 at 12:31
-
10@CiprianJijie the whole point is your not suppose to be able to. – csharptest.net Nov 24 '16 at 15:56
-
13In 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)); – Jesú Castillo May 04 '17 at 19:17
-
4@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 – lenny Aug 17 '18 at 08:52
-
@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. – csharptest.net Aug 17 '18 at 14:41
-
1@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. – ibrahimozgon Feb 05 '20 at 07:24
-
For those curious about warnings in Visual Studio while using 'new Rfc2898DeriveBytes()', see this answer: https://stackoverflow.com/questions/18648084/rfc2898-pbkdf2-with-sha256-as-digest-in-c-sharp#answer-51690350 – derekbaker783 Mar 16 '20 at 15:00
-
Is this vulnerable to [Timing Attack](https://en.wikipedia.org/wiki/Timing_attack) when verifying the hash? Should we use [`CryptographicOperations.FixedTimeEquals()`](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals) or something, just like what ... – Pang Sep 03 '20 at 08:48
-
... PHP did in [`password_verify()`](https://github.com/php/php-src/blob/91edb907676805bc4fad3ebc758dd6d797c92a63/ext/standard/password.c#L172-L178) and what Go did in [`ConstantTimeCompare()`](https://github.com/golang/gofrontend/blob/51d2cb40a6475b126ed66cefa2aa6c8dbdc806d0/libgo/go/crypto/subtle/constant_time.go#L19-L21) called by [`CompareHashAndPassword()`](https://github.com/golang/crypto/blob/5c72a883971a4325f8c62bf07b6d38c20ea47a6a/bcrypt/bcrypt.go#L111)? – Pang Sep 03 '20 at 08:48
-
@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. – csharptest.net Sep 03 '20 at 12:30
-
`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 – marsh-wiggle Dec 04 '20 at 19:58
-
thnx may i ask which one is better normal hashing or encrypt ... is there different – ABDULLAH MAKKI Jul 06 '22 at 11:14
-
If you want to use this in 2022 (.NET 6 or above), then take a look at [this answer](https://stackoverflow.com/a/73125177/7734384). – Arad Alvand Jul 26 '22 at 15:14
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.

- 16,510
- 17
- 74
- 111
-
1Just to verify, if you do upgrade the hashing engine you'd increment the V1 section of your hash and key off of that? – Mike Cole May 07 '16 at 18:06
-
1Yes that is the plan. You would then decide based on `V1` and `V2` which verification method you need. – Christian Gollhardt May 07 '16 at 21:42
-
Why don't you use [SecureString](https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx)? – wa4_tasty_elephant Oct 27 '16 at 14:34
-
In 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. – Christian Gollhardt Oct 27 '16 at 16:36
-
Im tryin to use this but everytime I run the hash, using the same string, it creates a new hash. is it normal ? – Nelson Silva May 28 '17 at 22:50
-
2Yes @NelsonSilva. That's because of the [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)). – Christian Gollhardt May 28 '17 at 23:55
-
6With 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! :) – pettys Mar 20 '19 at 16:03
-
1In 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](https://codereview.stackexchange.com/questions/176697/net-core-mvc-future-proof-hashing-of-passwords) @pettys – Christian Gollhardt Mar 20 '19 at 16:26
UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from https://stackoverflow.com/a/10402129 or https://stackoverflow.com/a/73125177 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);

- 8,607
- 10
- 51
- 71

- 249,484
- 69
- 436
- 539
-
12I 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 call `Clear()` on it when you are done using the implementation. – vcsjones Nov 15 '10 at 03:37
-
3@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. – zerkms Nov 15 '10 at 03:40
-
4@zerkms point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry". – vcsjones Nov 15 '10 at 03:42
-
1@zerkms MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the `Clear` method. – vcsjones Nov 15 '10 at 03:43
-
While 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. – Slartibartfast Nov 15 '10 at 03:43
-
@vcsjones: oh, thanks. I did not even know crypto-classes need to be disposed :-S The samples at http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5cryptoserviceprovider.aspx don't do that in some reason... – zerkms Nov 15 '10 at 03:45
-
@Slartibartfast: indeed, `sha1` is better, but `md5` is not so bad as most of people want to present. – zerkms Nov 15 '10 at 03:47
-
4No 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. – Gerald Davis Nov 15 '10 at 15:18
-
1Some systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data). – Chris Rae Feb 19 '11 at 00:04
-
2Surprisingly 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). – milan Nov 30 '11 at 15:46
-
12Three 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 May 11 '12 at 21:21
-
See https://en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea. – Samuel Parkinson Jun 27 '12 at 16:13
2022 (.NET 6+) solution:
Most of the other answers here were written years ago and are therefore not taking advantage of many of the more recent features introduced in newer versions of .NET. The same thing can now be achieved much more simply and with a lot less boilerplate and noise. The solution I'm proposing also provides extra robustness by allowing you to modify the settings (e.g. iterations count, etc.) in the future without actually breaking old hashes (which is what would happen if you used the accepted answer's proposed solution, for example).
Pros:
Uses the new static
Rfc2898DeriveBytes.Pbkdf2()
method introduced in .NET 6, eliminating the need to instantiate and also dispose the object every single time.Uses the new
RandomNumberGenerator
class and its staticGetBytes
method — introduced in .NET 6 — to generate the salt. TheRNGCryptoServiceProvider
class used in the accepted answer is obsolete.Uses the
CryptographicOperations.FixedTimeEquals
method (introduced in .NET Core 2.1) for comparing the key bytes in theVerify
method, instead of doing the comparison by hand — like the accepted answer is doing. This, in addition to removing a lot of noisy boilerplate, also nullifies timing attacks.Uses SHA-256 instead of the default SHA-1 as the underlying algorithm, just to be on the safe side, as the latter is a more robust and reliable algorithm.
The returned string from the
Hash
method has the following structure:[key]:[salt]:[iterations]:[algorithm]
This is the most important advantage of this solution, it means we're basically including metadata about the configurations used to create the hash in the final string. This allows us to change the settings (such as the number of iterations, salt/key size, etc.) in our hasher class in the future without breaking previous hashes created with the old settings. This is something that the accepted answer, for example, doesn't take into account, as it's relying on the "current" configuration values to verify hashes.
Other points:
- I'm using the hexadecimal representation of the key and the salt in the returned hash string. You can instead use base64 if you prefer, simply by changing every occurrence of
Convert.ToHexString
andConvert.FromHexString
toConvert.ToBase64
andConvert.FromBase64
respectively. The rest of the logic remains exactly the same. - The often recommended salt size is 64 bits or above. I've set it to 128 bits.
- The key size should normally be the same as the natural output size of your chosen algorithm — see this comment. In our case, as I mentioned earlier, the underlying algorithm is SHA-256, whose output size is 256 bits, which is precisely what we're setting our key size to.
- If you plan to use this for storing user passwords, it's usually recommended to use at least 10,000 iterations or more. I've set the default value to 50,000, which you can of course change as you see fit.
The code:
public static class SecretHasher
{
private const int _saltSize = 16; // 128 bits
private const int _keySize = 32; // 256 bits
private const int _iterations = 50000;
private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;
private const char segmentDelimiter = ':';
public static string Hash(string input)
{
byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
_iterations,
_algorithm,
_keySize
);
return string.Join(
segmentDelimiter,
Convert.ToHexString(hash),
Convert.ToHexString(salt),
_iterations,
_algorithm
);
}
public static bool Verify(string input, string hashString)
{
string[] segments = hashString.Split(segmentDelimiter);
byte[] hash = Convert.FromHexString(segments[0]);
byte[] salt = Convert.FromHexString(segments[1]);
int iterations = int.Parse(segments[2]);
HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);
byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
iterations,
algorithm,
hash.Length
);
return CryptographicOperations.FixedTimeEquals(inputHash, hash);
}
}
Usage:
// Hash:
string password = "...";
string hashed = SecretHasher.Hash(password);
// Verify:
string enteredPassword = "...";
bool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);

- 8,607
- 10
- 51
- 71
-
1
-
@Scottie It should actually be `_algorithm`, I fixed it. Thanks for catching that. – Arad Alvand Aug 25 '22 at 13:26
-
3
@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);

- 1,137
- 1
- 12
- 19
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.

- 9,564
- 146
- 81
- 122
-
1One more to Cons: `
` as fake parameter. And it is part of ASP.NET Core Identity, that means in future it could have some dependencies of Identity framework. – Klyuch Jul 30 '21 at 16:04
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);
}
-
18-1 for using plain SHA-1, which is fast. Use a slow key derivation function, such as PBKDF2, bcrypt or scrypt. – CodesInChaos May 11 '12 at 21:22
- Create a salt,
- Create a hash password with salt
- Save both hash and salt
- decrypt with password and salt... so developers cant decrypt password
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);
}
}

- 524
- 4
- 7
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".

- 438
- 1
- 7
- 15
-
1BTW, the previous answer https://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! – Albert Lyubarsky Oct 05 '19 at 17:31
-
you 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. – John Ranger Oct 27 '20 at 22:49
-
@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 Oct 30 '20 at 11:22
-
One 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. – Albert Lyubarsky Oct 30 '20 at 11:22
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();
}
}

- 183
- 2
- 7
var sha1 = SHA1.Create();
byte[] bytes = Encoding.UTF8.GetBytes("Your String Data");
byte[] hash = sha1.ComputeHash(bytes);
var sBuilder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sBuilder.Append(hash[i].ToString("X"));
}
Console.WriteLine(sBuilder.ToString()); //Hash value in Hex

- 2,072
- 22
- 11