Identity - Password hasher dosen't generate the same hashes [duplicate] - c#

I am wondering wether the Password Hasher that is default implemented in the UserManager that comes with MVC 5 and ASP.NET Identity Framework, is secure enough? And if so, if you could explain to me how it works?
IPasswordHasher interface looks like this:
public interface IPasswordHasher
{
string HashPassword(string password);
PasswordVerificationResult VerifyHashedPassword(string hashedPassword,
string providedPassword);
}
As you can see, it doesn't take a salt, but it is mentioned in this thread: "Asp.net Identity password hashing"
that it does infact salt it behind the scenes. So I am wondering how does it do this? And where does this salt come from?
My concern is that the salt is static, rendering it quite insecure.

Here is how the default implementation (ASP.NET Framework or ASP.NET Core) works. It uses a Key Derivation Function with random salt to produce the hash. The salt is included as part of the output of the KDF. Thus, each time you "hash" the same password you will get different hashes. To verify the hash the output is split back to the salt and the rest, and the KDF is run again on the password with the specified salt. If the result matches to the rest of the initial output the hash is verified.
Hashing:
public static string HashPassword(string password)
{
byte[] salt;
byte[] buffer2;
if (password == null)
{
throw new ArgumentNullException("password");
}
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
{
salt = bytes.Salt;
buffer2 = bytes.GetBytes(0x20);
}
byte[] dst = new byte[0x31];
Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
return Convert.ToBase64String(dst);
}
Verifying:
public static bool VerifyHashedPassword(string hashedPassword, string password)
{
byte[] buffer4;
if (hashedPassword == null)
{
return false;
}
if (password == null)
{
throw new ArgumentNullException("password");
}
byte[] src = Convert.FromBase64String(hashedPassword);
if ((src.Length != 0x31) || (src[0] != 0))
{
return false;
}
byte[] dst = new byte[0x10];
Buffer.BlockCopy(src, 1, dst, 0, 0x10);
byte[] buffer3 = new byte[0x20];
Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
{
buffer4 = bytes.GetBytes(0x20);
}
return ByteArraysEqual(buffer3, buffer4);
}

Because these days ASP.NET is open source, you can find it on GitHub:
AspNet.Identity 3.0 and AspNet.Identity 2.0.
From the comments:
/* =======================
* HASHED PASSWORD FORMATS
* =======================
*
* Version 2:
* PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
* (See also: SDL crypto guidelines v5.1, Part III)
* Format: { 0x00, salt, subkey }
*
* Version 3:
* PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
* Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
* (All UInt32s are stored big-endian.)
*/

I understand the accepted answer, and have up-voted it but thought I'd dump my laymen's answer here...
Creating a hash
The salt is randomly generated using the function
Rfc2898DeriveBytes which generates a hash and a salt. Inputs to Rfc2898DeriveBytes are the password, the size of the salt to generate and the number of hashing iterations to perform.
https://msdn.microsoft.com/en-us/library/h83s4e12(v=vs.110).aspx
The salt and the hash are then mashed together(salt first followed
by the hash) and encoded as a string (so the salt is encoded in the
hash). This encoded hash (which contains the salt and hash) is then
stored (typically) in the database against the user.
Checking a password against a hash
To check a password that a user inputs.
The salt is extracted from the stored hashed password.
The salt is used to hash the users input password using an overload of Rfc2898DeriveBytes which takes a salt instead of generating one. https://msdn.microsoft.com/en-us/library/yx129kfs(v=vs.110).aspx
The stored hash and the test hash are then compared.
The Hash
Under the covers the hash is generated using the SHA1 hash function (https://en.wikipedia.org/wiki/SHA-1).
This function is iteratively called 1000 times (In the default Identity implementation)
Why is this secure
Random salts means that an attacker can’t use a pre-generated table
of hashs to try and break passwords. They would need to generate a
hash table for every salt. (Assuming here that the hacker has also compromised your salt)
If 2 passwords are identical they will
have different hashes. (meaning attackers can’t infer ‘common’
passwords)
Iteratively calling SHA1 1000 times means that the
attacker also needs to do this. The idea being that unless they have
time on a supercomputer they won’t have enough resource to brute
force the password from the hash. It would massively slow down the time to generate a hash table for a given salt.

For those like me who are brand new to this, here is code with const and an actual way to compare the byte[]'s. I got all of this code from stackoverflow but defined consts so values could be changed and also
// 24 = 192 bits
private const int SaltByteSize = 24;
private const int HashByteSize = 24;
private const int HasingIterationsCount = 10101;
public static string HashPassword(string password)
{
// http://stackoverflow.com/questions/19957176/asp-net-identity-password-hashing
byte[] salt;
byte[] buffer2;
if (password == null)
{
throw new ArgumentNullException("password");
}
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, SaltByteSize, HasingIterationsCount))
{
salt = bytes.Salt;
buffer2 = bytes.GetBytes(HashByteSize);
}
byte[] dst = new byte[(SaltByteSize + HashByteSize) + 1];
Buffer.BlockCopy(salt, 0, dst, 1, SaltByteSize);
Buffer.BlockCopy(buffer2, 0, dst, SaltByteSize + 1, HashByteSize);
return Convert.ToBase64String(dst);
}
public static bool VerifyHashedPassword(string hashedPassword, string password)
{
byte[] _passwordHashBytes;
int _arrayLen = (SaltByteSize + HashByteSize) + 1;
if (hashedPassword == null)
{
return false;
}
if (password == null)
{
throw new ArgumentNullException("password");
}
byte[] src = Convert.FromBase64String(hashedPassword);
if ((src.Length != _arrayLen) || (src[0] != 0))
{
return false;
}
byte[] _currentSaltBytes = new byte[SaltByteSize];
Buffer.BlockCopy(src, 1, _currentSaltBytes, 0, SaltByteSize);
byte[] _currentHashBytes = new byte[HashByteSize];
Buffer.BlockCopy(src, SaltByteSize + 1, _currentHashBytes, 0, HashByteSize);
using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, _currentSaltBytes, HasingIterationsCount))
{
_passwordHashBytes = bytes.GetBytes(SaltByteSize);
}
return AreHashesEqual(_currentHashBytes, _passwordHashBytes);
}
private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash)
{
int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length;
var xor = firstHash.Length ^ secondHash.Length;
for (int i = 0; i < _minHashLength; i++)
xor |= firstHash[i] ^ secondHash[i];
return 0 == xor;
}
In in your custom ApplicationUserManager, you set the PasswordHasher property the name of the class which contains the above code.

I write my class PasswordHasher based on .net6 PasswordHasher docs latest version (V3)
https://github.com/dotnet/aspnetcore/blob/b56bb17db3ae73ce5a8664a2023a9b9af89499dd/src/Identity/Extensions.Core/src/PasswordHasher.cs
namespace Utilities;
public class PasswordHasher
{
public const int Pbkdf2Iterations = 1000;
public static string HashPasswordV3(string password)
{
return Convert.ToBase64String(HashPasswordV3(password, RandomNumberGenerator.Create()
, prf: KeyDerivationPrf.HMACSHA512, iterCount: Pbkdf2Iterations, saltSize: 128 / 8
, numBytesRequested: 256 / 8));
}
public static bool VerifyHashedPasswordV3(string hashedPasswordStr, string password)
{
byte[] hashedPassword = Convert.FromBase64String(hashedPasswordStr);
var iterCount = default(int);
var prf = default(KeyDerivationPrf);
try
{
// Read header information
prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 1);
iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5);
int saltLength = (int)ReadNetworkByteOrder(hashedPassword, 9);
// Read the salt: must be >= 128 bits
if (saltLength < 128 / 8)
{
return false;
}
byte[] salt = new byte[saltLength];
Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length);
// Read the subkey (the rest of the payload): must be >= 128 bits
int subkeyLength = hashedPassword.Length - 13 - salt.Length;
if (subkeyLength < 128 / 8)
{
return false;
}
byte[] expectedSubkey = new byte[subkeyLength];
Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);
// Hash the incoming password and verify it
byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength);
#if NETSTANDARD2_0 || NETFRAMEWORK
return ByteArraysEqual(actualSubkey, expectedSubkey);
#elif NETCOREAPP
return CryptographicOperations.FixedTimeEquals(actualSubkey, expectedSubkey);
#else
#error Update target frameworks
#endif
}
catch
{
// This should never occur except in the case of a malformed payload, where
// we might go off the end of the array. Regardless, a malformed payload
// implies verification failed.
return false;
}
}
// privates
private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
{
byte[] salt = new byte[saltSize];
rng.GetBytes(salt);
byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);
var outputBytes = new byte[13 + salt.Length + subkey.Length];
outputBytes[0] = 0x01; // format marker
WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount);
WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize);
Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
return outputBytes;
}
private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
buffer[offset + 0] = (byte)(value >> 24);
buffer[offset + 1] = (byte)(value >> 16);
buffer[offset + 2] = (byte)(value >> 8);
buffer[offset + 3] = (byte)(value >> 0);
}
private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
return ((uint)(buffer[offset + 0]) << 24)
| ((uint)(buffer[offset + 1]) << 16)
| ((uint)(buffer[offset + 2]) << 8)
| ((uint)(buffer[offset + 3]));
}
}
Use in UserController :
namespace WebApi.Controllers.UserController;
[Route("api/[controller]/[action]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
[HttpPost]
public async Task<IActionResult> Register(VmRegister model)
{
var user = new User
{
UserName = model.UserName,
PasswordHash = PasswordHasher.HashPasswordV3(model.Password),
FirstName = model.FirstName,
LastName = model.LastName,
Mobile = model.Mobile,
Email = model.Email,
};
await _userService.Add(user);
return StatusCode(201, user.Id);
}
[HttpPost]
public async Task<IActionResult> Login(VmLogin model)
{
var user = await _userService.GetByUserName(model.UserName);
if (user is null || !PasswordHasher.VerifyHashedPasswordV3(user.PasswordHash, model.Password))
throw new Exception("The UserName or Password is wrong.");
// generate token
return Ok();
}
}
https://github.com/mammadkoma/WebApi/tree/master/WebApi

After following the answer from Andrew Savinykh
I've made the following changes.
I'm using Dapper with an existing DB which was configured with AspNet Identity.
Please note that PasswordHasherCompatibilityMode.IdentityV2 works
great if you're using AspNet Identity. Not tested yet for AspNetCore
Identity.
Here is the GitHub Gist for complete class.

Related

PBKDF2 function with 128 Bit Key Length and 1,024 iterations of SHA256 in .Net Framework 4.5

I am trying to work with an api in .Net framework 4.5 that supposed to provide me cryptocurrencies wallet. in a part of it's documentations it says:
Pass Pin Code through the PBKDF2 function with 128 Bit Key Length and
1,024 iterations of SHA256
i could not find the Specify method in C# to do that. in documentations they have input "be9d3a4f1220495a96c38d36d8558365" as pin code and the out put is "4369cb0560d54f55d0c03564fbd983c4".
it seems that i should use Rfc2898DeriveBytes Method, and i used it like code below but i didnot get the same result.
string output = Convert.ToBase64String((new Rfc2898DeriveBytes("e24546d6643137a310968566cf1cd42b",16, 1024)).GetBytes(32));
output ==> 'x10zclBJY2eeZqjMyPfQm4ljyMFPvWbxF72Om2DCzHE='
It's probably best to implement your own version of PBKDF2. PBKDF2 is the actual algorithm implemented by the badly named Rfc2898DeriveBytes class.
As .NET 4.5 doesn't include the functionality to use PBKDF2 with a different hash. .NET version 4.7.2 does include the functionality but it doesn't allow the salt to be zero bytes.
So therefore it is best to implement your own version. The .NET version of Microsoft has specific copyright notices that do not seem compatible. One way to go around this is to implement PBKDF2 from Mono, but the later versions of Mono do not implement this class (it seems) and they do not implement the version where the hash can be chosen.
Fortunately bartonjs has indicated a version that has the permissive MIT license, which can be used, leading to the following solution:
using System;
using System.Security.Cryptography;
using System.Text;
namespace StackOverflow
{
public class Rfc2898DeriveBytes : DeriveBytes
{
private const string DEFAULT_HASH_ALGORITHM = "SHA-1";
private const int MinimumSaltSize = 0;
private readonly byte[] _password;
private byte[] _salt;
private uint _iterations;
private HMAC _hmac;
private int _blockSize;
private byte[] _buffer;
private uint _block;
private int _startIndex;
private int _endIndex;
public string HashAlgorithm { get; }
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations, string hashAlgorithm)
{
if (salt == null)
throw new ArgumentNullException(nameof(salt));
if (salt.Length < MinimumSaltSize)
throw new ArgumentException(nameof(salt));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
if (password == null)
throw new NullReferenceException(); // This "should" be ArgumentNullException but for compat, we throw NullReferenceException.
_salt = (byte[])salt.Clone();
_iterations = (uint)iterations;
_password = (byte[])password.Clone();
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public Rfc2898DeriveBytes(string password, byte[] salt)
: this(password, salt, 1000)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations)
: this(password, salt, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, byte[] salt, int iterations, string hashAlgorithm)
: this(Encoding.UTF8.GetBytes(password), salt, iterations, hashAlgorithm)
{
}
public Rfc2898DeriveBytes(string password, int saltSize)
: this(password, saltSize, 1000)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
: this(password, saltSize, iterations, DEFAULT_HASH_ALGORITHM)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations, string hashAlgorithm)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException(nameof(saltSize));
if (saltSize < MinimumSaltSize)
throw new ArgumentException(nameof(saltSize));
if (iterations <= 0)
throw new ArgumentOutOfRangeException(nameof(iterations));
_salt = new byte[saltSize];
RandomNumberGenerator.Create().GetBytes(_salt);
_iterations = (uint)iterations;
_password = Encoding.UTF8.GetBytes(password);
HashAlgorithm = hashAlgorithm;
_hmac = OpenHmac();
// _blockSize is in bytes, HashSize is in bits.
_blockSize = _hmac.HashSize >> 3;
Initialize();
}
public int IterationCount
{
get
{
return (int)_iterations;
}
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get
{
return (byte[])_salt.Clone();
}
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.Length < MinimumSaltSize)
throw new ArgumentException("Too few bytes for salt");
_salt = (byte[])value.Clone();
Initialize();
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_hmac != null)
{
_hmac.Dispose();
_hmac = null;
}
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
if (_password != null)
Array.Clear(_password, 0, _password.Length);
if (_salt != null)
Array.Clear(_salt, 0, _salt.Length);
}
base.Dispose(disposing);
}
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException(nameof(cb));
byte[] password = new byte[cb];
int offset = 0;
int size = _endIndex - _startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, size);
_startIndex = _endIndex = 0;
offset += size;
}
else
{
Buffer.BlockCopy(_buffer, _startIndex, password, 0, cb);
_startIndex += cb;
return password;
}
}
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > _blockSize)
{
Buffer.BlockCopy(T_block, 0, password, offset, _blockSize);
offset += _blockSize;
}
else
{
Buffer.BlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.BlockCopy(T_block, remainder, _buffer, _startIndex, _blockSize - remainder);
_endIndex += (_blockSize - remainder);
return password;
}
}
return password;
}
public byte[] CryptDeriveKey(string algname, string alghashname, int keySize, byte[] rgbIV)
{
// If this were to be implemented here, CAPI would need to be used (not CNG) because of
// unfortunate differences between the two. Using CNG would break compatibility. Since this
// assembly currently doesn't use CAPI it would require non-trivial additions.
// In addition, if implemented here, only Windows would be supported as it is intended as
// a thin wrapper over the corresponding native API.
// Note that this method is implemented in PasswordDeriveBytes (in the Csp assembly) using CAPI.
throw new PlatformNotSupportedException();
}
public override void Reset()
{
Initialize();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "HMACSHA1 is needed for compat. (https://github.com/dotnet/corefx/issues/9438)")]
private HMAC OpenHmac()
{
String hashAlgorithm = HashAlgorithm;
if (string.IsNullOrEmpty(hashAlgorithm))
throw new CryptographicException("HashAlgorithm name not present");
if (hashAlgorithm.Equals("SHA-1", StringComparison.OrdinalIgnoreCase))
return new HMACSHA1(_password);
if (hashAlgorithm.Equals("SHA-256", StringComparison.OrdinalIgnoreCase))
return new HMACSHA256(_password);
if (hashAlgorithm.Equals("SHA-384", StringComparison.OrdinalIgnoreCase))
return new HMACSHA384(_password);
if (hashAlgorithm.Equals("SHA-512", StringComparison.OrdinalIgnoreCase))
return new HMACSHA512(_password);
throw new CryptographicException("MAC algorithm " + hashAlgorithm + " not available");
}
private void Initialize()
{
if (_buffer != null)
Array.Clear(_buffer, 0, _buffer.Length);
_buffer = new byte[_blockSize];
_block = 1;
_startIndex = _endIndex = 0;
}
// This function is defined as follows:
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] temp = new byte[_salt.Length + sizeof(uint)];
Buffer.BlockCopy(_salt, 0, temp, 0, _salt.Length);
WriteInt(_block, temp, _salt.Length);
temp = _hmac.ComputeHash(temp);
byte[] ret = temp;
for (int i = 2; i <= _iterations; i++)
{
temp = _hmac.ComputeHash(temp);
for (int j = 0; j < _blockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
_block++;
return ret;
}
private void WriteInt(uint i, byte[] buf, int bufOff)
{
buf[bufOff++] = (byte)(i >> 24);
buf[bufOff++] = (byte)(i >> 16);
buf[bufOff++] = (byte)(i >> 8);
buf[bufOff] = (byte)i;
}
}
}
this is a class where more specific exceptions have been rewritten, some specialized cloning is replaced, and the random salt generation is generalized. The minimum salt size has also been set to 0. Otherwise it is the same code in a different name space.
It is possible to use it like this:
string pw = "be9d3a4f1220495a96c38d36d8558365";
byte[] salt = new byte[0];
int iterations = 1024;
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(pw, salt, iterations, "SHA-256");
byte[] key = pbkdf2.GetBytes(16);
Note that the PIN is hexadecimals encoded as UTF-8, the default encoding for PBKDF2 (not the default encoding for .NET!). The result is a key that, when represented as hexadecimals equals 4369cb0560d54f55d0c03564fbd983c4.
I've converted to a 4.5 compatible class using a string to indicate the hash function, for the one with an enum HashAlgorithm (4.6 or something similar) take a look at the revision history.

C# Key wrap/unwrap PBEWITHSHA256AND256BITAES CBC

I have the following code in Java doing key unwrap using bouncy castle provider:
private static byte[] unwrapKey(byte[] toUnwrap, String key) throws Exception {
byte[] decoded = Base64.decode(toUnwrap);
if (decoded == null || decoded.length <= 16) {
throw new RuntimeException("Bad input data.");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.length - 16];
System.arraycopy(decoded, 0, salt, 0, 16);
System.arraycopy(decoded, 16, wrappedKey, 0, decoded.length - 16);
PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
SecretKey wrapperKey = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC").generateSecret(pbeKeySpec);
PBEParameterSpec parameterSpec = new PBEParameterSpec(salt, 10);
Cipher decCipher = Cipher.getInstance("AES/GCM/NoPadding", bcProvider);
decCipher.init(Cipher.UNWRAP_MODE, wrapperKey, parameterSpec);
return decCipher.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY).getEncoded();
}
Now, I need to do the same in C#. The problem is that even though there's a port of BC to C#, I still can not get it working. Tried different things, and always get some exceptions.
For example, this code throws "pad block corrupted" exception at the second last line:
byte[] decoded = Convert.FromBase64String(toUnwrap);
if (decoded == null || decoded.Length <= 16) {
throw new System.ArgumentException("Bad input data", "toUnwrap");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.Length - 16];
Array.Copy(decoded, 0, salt, 0, 16);
Array.Copy(decoded, 16, wrappedKey, 0, decoded.Length - 16);
int iterationCount = 10;
String alg = "PBEWithSHA256And256BitAES-CBC-BC";
Asn1Encodable defParams = PbeUtilities.GenerateAlgorithmParameters(alg, salt, iterationCount);
char[] password = key.ToCharArray();
IWrapper wrapper = WrapperUtilities.GetWrapper(alg);
ICipherParameters parameters = PbeUtilities.GenerateCipherParameters(alg, password, defParams);
wrapper.Init(false, parameters);
byte[] pText = wrapper.Unwrap(wrappedKey, 0, wrappedKey.Length);
return pText.ToString();
I suspect that C# uses different type of padding by default, but no idea how to force "NoPadding" as in Java code.
I'm not sure, if JAVA code use rfc3994 is this case or not, because in RFC you need to provide IV, while here there's a salt, but no IV.
I wonder if anyone did it before and if so, what would be c# analogy.
I finally figured that out:
public static String unwrapKey(String toUnwrap, String key)
{
byte[] decoded = Convert.FromBase64String(toUnwrap);
if (decoded == null || decoded.Length <= 16)
{
throw new System.ArgumentException("Bad input data", "toUnwrap");
}
byte[] salt = new byte[16];
byte[] wrappedKey = new byte[decoded.Length - 16];
Array.Copy(decoded, 0, salt, 0, 16);
Array.Copy(decoded, 16, wrappedKey, 0, decoded.Length - 16);
int iterationCount = 10;
String algSpec = "AES/GCM/NoPadding";
String algName = "PBEWithSHA256And256BitAES-CBC-BC";
Asn1Encodable defParams = PbeUtilities.GenerateAlgorithmParameters(algName, salt, iterationCount);
char[] password = key.ToCharArray();
IWrapper wrapper = WrapperUtilities.GetWrapper(algSpec);
ICipherParameters parameters = PbeUtilities.GenerateCipherParameters(algName, password, defParams);
wrapper.Init(false, parameters);
byte[] keyText = wrapper.Unwrap(wrappedKey, 0, wrappedKey.Length);
return Convert.ToBase64String(keyText);
}
This will do exactly the same as the JAVA code above.

Rfc2898 / PBKDF2 with SHA256 as digest in c#

I want to use Rfc2898 in c# to derive a key. I also need to use SHA256 as Digest for Rfc2898. I found the class Rfc2898DeriveBytes, but it uses SHA-1 and I don't see a way to make it use a different digest.
Is there a way to use Rfc2898 in c# with SHA256 as digest (short of implementing it from scratch)?
.NET Core has a new implementation of Rfc2898DeriveBytes.
The CoreFX version no longer has the the hashing algorithm hard-coded
The code is available on Github. It was merged to master on March 2017 and has been shipped with .NET Core 2.0.
For those who need it, .NET Framework 4.7.2 includes an overload of Rfc2898DeriveBytes that allows the hashing algorithm to be specified:
byte[] bytes;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
{
bytes = deriveBytes.GetBytes(PBKDF2SubkeyLength);
}
The HashAlgorithmName options at the moment are:
MD5
SHA1
SHA256
SHA384
SHA512
See Bruno Garcia's answer.
At the time I started this answer, Rfc2898DeriveBytes was not configurable to use a different hash function. In the meantime, though, it has been improved; see Bruno Garcia's answer. The following function can be used to generate a hashed version of a user-provided password to store in a database for authentication purposes.
For users of older .NET frameworks, this is still useful:
// NOTE: The iteration count should
// be as high as possible without causing
// unreasonable delay. Note also that the password
// and salt are byte arrays, not strings. After use,
// the password and salt should be cleared (with Array.Clear)
public static byte[] PBKDF2Sha256GetBytes(int dklen, byte[] password, byte[] salt, int iterationCount){
using(var hmac=new System.Security.Cryptography.HMACSHA256(password)){
int hashLength=hmac.HashSize/8;
if((hmac.HashSize&7)!=0)
hashLength++;
int keyLength=dklen/hashLength;
if((long)dklen>(0xFFFFFFFFL*hashLength) || dklen<0)
throw new ArgumentOutOfRangeException("dklen");
if(dklen%hashLength!=0)
keyLength++;
byte[] extendedkey=new byte[salt.Length+4];
Buffer.BlockCopy(salt,0,extendedkey,0,salt.Length);
using(var ms=new System.IO.MemoryStream()){
for(int i=0;i<keyLength;i++){
extendedkey[salt.Length]=(byte)(((i+1)>>24)&0xFF);
extendedkey[salt.Length+1]=(byte)(((i+1)>>16)&0xFF);
extendedkey[salt.Length+2]=(byte)(((i+1)>>8)&0xFF);
extendedkey[salt.Length+3]=(byte)(((i+1))&0xFF);
byte[] u=hmac.ComputeHash(extendedkey);
Array.Clear(extendedkey,salt.Length,4);
byte[] f=u;
for(int j=1;j<iterationCount;j++){
u=hmac.ComputeHash(u);
for(int k=0;k<f.Length;k++){
f[k]^=u[k];
}
}
ms.Write(f,0,f.Length);
Array.Clear(u,0,u.Length);
Array.Clear(f,0,f.Length);
}
byte[] dk=new byte[dklen];
ms.Position=0;
ms.Read(dk,0,dklen);
ms.Position=0;
for(long i=0;i<ms.Length;i++){
ms.WriteByte(0);
}
Array.Clear(extendedkey,0,extendedkey.Length);
return dk;
}
}
The BCL Rfc2898DeriveBytes is hardcoded to use sha-1.
KeyDerivation.Pbkdf2 allows for exactly the same output, but it also allows HMAC SHA-256 and HMAC SHA-512. It's faster too; on my machine by around 5 times - and that's good for security, because it allows for more rounds, which makes life for crackers harder (incidentally sha-512 is a lot less gpu-friendly than sha-256 or sha1). And the api is simpler, to boot:
byte[] salt = ...
string password = ...
var rounds = 50000; // pick something bearable
var num_bytes_requested = 16; // 128 bits is fine
var prf = KeyDerivationPrf.HMACSHA512; // or sha256, or sha1
byte[] hashed = KeyDerivation.Pbkdf2(password, salt, prf, rounds, num_bytes_requested);
It's from the nuget package Microsoft.AspNetCore.Cryptography.KeyDerivation which does not depend on asp.net core; it runs on .net 4.5.1 or .net standard 1.3 or higher.
You could use Bouncy Castle. The C# specification lists the algorithm "PBEwithHmacSHA-256", which can only be PBKDF2 with SHA-256.
I know this is an old question, but for anyone that comes across it, you can now use KeyDerivation.Pbkdf2 from the Microsoft.AspNetCore.Cryptography.KeyDerivation nuget package. It is what is used in asp.net core.
Unfortunately it will add a ton of references that aren't really needed. You could just copy the code and paste it into your own project (although you will now have to maintain cryto code which is a PITA)
For what it's worth, here's a copy of Microsoft's implementation but with SHA-1 replaced with SHA512:
namespace System.Security.Cryptography
{
using System.Globalization;
using System.IO;
using System.Text;
[System.Runtime.InteropServices.ComVisible(true)]
public class Rfc2898DeriveBytes_HMACSHA512 : DeriveBytes
{
private byte[] m_buffer;
private byte[] m_salt;
private HMACSHA512 m_HMACSHA512; // The pseudo-random generator function used in PBKDF2
private uint m_iterations;
private uint m_block;
private int m_startIndex;
private int m_endIndex;
private static RNGCryptoServiceProvider _rng;
private static RNGCryptoServiceProvider StaticRandomNumberGenerator
{
get
{
if (_rng == null)
{
_rng = new RNGCryptoServiceProvider();
}
return _rng;
}
}
private const int BlockSize = 20;
//
// public constructors
//
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize) : this(password, saltSize, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, int saltSize, int iterations)
{
if (saltSize < 0)
throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] salt = new byte[saltSize];
StaticRandomNumberGenerator.GetBytes(salt);
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(new UTF8Encoding(false).GetBytes(password));
Initialize();
}
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt) : this(password, salt, 1000) { }
public Rfc2898DeriveBytes_HMACSHA512(string password, byte[] salt, int iterations) : this(new UTF8Encoding(false).GetBytes(password), salt, iterations) { }
public Rfc2898DeriveBytes_HMACSHA512(byte[] password, byte[] salt, int iterations)
{
Salt = salt;
IterationCount = iterations;
m_HMACSHA512 = new HMACSHA512(password);
Initialize();
}
//
// public properties
//
public int IterationCount
{
get { return (int)m_iterations; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
m_iterations = (uint)value;
Initialize();
}
}
public byte[] Salt
{
get { return (byte[])m_salt.Clone(); }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (value.Length < 8)
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, Environment.GetResourceString("Cryptography_PasswordDerivedBytes_FewBytesSalt")));
m_salt = (byte[])value.Clone();
Initialize();
}
}
//
// public methods
//
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
byte[] password = new byte[cb];
int offset = 0;
int size = m_endIndex - m_startIndex;
if (size > 0)
{
if (cb >= size)
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, size);
m_startIndex = m_endIndex = 0;
offset += size;
}
else
{
Buffer.InternalBlockCopy(m_buffer, m_startIndex, password, 0, cb);
m_startIndex += cb;
return password;
}
}
//BCLDebug.Assert(m_startIndex == 0 && m_endIndex == 0, "Invalid start or end index in the internal buffer.");
while (offset < cb)
{
byte[] T_block = Func();
int remainder = cb - offset;
if (remainder > BlockSize)
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, BlockSize);
offset += BlockSize;
}
else
{
Buffer.InternalBlockCopy(T_block, 0, password, offset, remainder);
offset += remainder;
Buffer.InternalBlockCopy(T_block, remainder, m_buffer, m_startIndex, BlockSize - remainder);
m_endIndex += (BlockSize - remainder);
return password;
}
}
return password;
}
public override void Reset()
{
Initialize();
}
private void Initialize()
{
if (m_buffer != null)
Array.Clear(m_buffer, 0, m_buffer.Length);
m_buffer = new byte[BlockSize];
m_block = 1;
m_startIndex = m_endIndex = 0;
}
internal static byte[] Int(uint i)
{
byte[] b = BitConverter.GetBytes(i);
byte[] littleEndianBytes = { b[3], b[2], b[1], b[0] };
return BitConverter.IsLittleEndian ? littleEndianBytes : b;
}
// This function is defined as follow :
// Func (S, i) = HMAC(S || i) | HMAC2(S || i) | ... | HMAC(iterations) (S || i)
// where i is the block number.
private byte[] Func()
{
byte[] INT_block = Int(m_block);
m_HMACSHA512.TransformBlock(m_salt, 0, m_salt.Length, m_salt, 0);
m_HMACSHA512.TransformFinalBlock(INT_block, 0, INT_block.Length);
byte[] temp = m_HMACSHA512.Hash;
m_HMACSHA512.Initialize();
byte[] ret = temp;
for (int i = 2; i <= m_iterations; i++)
{
temp = m_HMACSHA512.ComputeHash(temp);
for (int j = 0; j < BlockSize; j++)
{
ret[j] ^= temp[j];
}
}
// increment the block count.
m_block++;
return ret;
}
}
}
In addition to replacing HMACSHA1 with HMACSHA512, you need to add a StaticRandomNumberGenerator property because Utils.StaticRandomNumberGenerator is internal in the microsoft assembly, and you need to add the static byte[] Int(uint i) method because microsoft's Utils.Int is also internal. Other than that, the code works.
Although this is an old question, since I added reference to this question in my Question Configurable Rfc2898DeriveBytes where I asked whether a generic implementation of the Rfc2898DeriveBytes algorithm was correct.
I have now tested and validated that it generates the exact same hash values if HMACSHA1 is provided for TAlgorithm as the .NET implementation of Rfc2898DeriveBytes
In order to use the class, one must provide the constructor for the HMAC algorithm requiring a byte array as the first argument.
e.g.:
var rfcGenSha1 = new Rfc2898DeriveBytes<HMACSHA1>(b => new HMACSHA1(b), key, ...)
var rfcGenSha256 = new Rfc2898DeriveBytes<HMACSHA256>(b => new HMACSHA256(b), key, ...)
This requires the algorithm to inherit HMAC at this point, I'm believe one might be able to Reduce the restriction to require inheritance from KeyedHashAlgorithm instead of HMAC, as long as the constructor of the algorithm accepts an array of bytes to the constructor.

Am I supposed to save the salt somewhere when hashing a password?

class Program
{
static void Main(string[] args)
{
Console.WriteLine("Sergio");
Console.WriteLine(HashString("Sergio"));
Console.WriteLine(HashString("Sergio"));
Console.WriteLine(HashString("Sergio"));
Console.ReadKey();
}
public static string HashString(string value)
{
int minSaltSize = 4;
int maxSaltSize = 8;
Random random = new Random();
int saltSize = random.Next(minSaltSize, maxSaltSize);
byte[] saltBytes = new byte[saltSize];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(saltBytes);
//Convert the string value into a byte array.
UTF8Encoding utf8Encoder = new UTF8Encoding();
byte[] plainTextBytes = utf8Encoder.GetBytes(value);
//Allocate an array to hold the text bytes and the salt bytes.
byte[] plainTextWithSaltBytes = new Byte[saltBytes.Length + plainTextBytes.Length];
for (int i = 0; i < plainTextBytes.Length; i++)
{
plainTextWithSaltBytes[i] = plainTextBytes[i];
}
for (int i = 0; i < saltBytes.Length; i++)
{
plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];
}
HashAlgorithm hash = new SHA256Managed();
byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);
string hashedValue = Convert.ToBase64String(hashBytes);
return hashedValue;
}
}
The result of my code is that hashed "Sergio" is different everytime. This is caused by the random factor included in the salt. My question is, am I supposed to save the salt somewhere? Or do I define a constant salt in my code?
Thank you!
If you are using different salts then you will want to store both the hashed password and the salt in the db. You can also chose to store the salt in an app.config / web.config file if it is the same salt.
You should save the salt someplace because you need to use the same salt when computing the hash again. However, you don't want to use a constant because that reduces the effectiveness of the salt.
You can use this class to create the salt/hash and combine the two values together:
public sealed class PasswordHash
{
const int SaltSize = 16, HashSize = 20, HashIter = 10000;
readonly byte[] _salt, _hash;
public PasswordHash(string password)
{
new RNGCryptoServiceProvider().GetBytes(_salt = new byte[SaltSize]);
_hash = new Rfc2898DeriveBytes(password, _salt, HashIter).GetBytes(HashSize);
}
public PasswordHash(byte[] hashBytes)
{
Array.Copy(hashBytes, 0, _salt = new byte[SaltSize], 0, SaltSize);
Array.Copy(hashBytes, SaltSize, _hash = new byte[HashSize], 0, HashSize);
}
public PasswordHash(byte[] salt, byte[] hash)
{
Array.Copy(salt, 0, _salt = new byte[SaltSize], 0, SaltSize);
Array.Copy(hash, 0, _hash = new byte[HashSize], 0, HashSize);
}
public byte[] ToArray()
{
byte[] hashBytes = new byte[SaltSize + HashSize];
Array.Copy(_salt, 0, hashBytes, 0, SaltSize);
Array.Copy(_hash, 0, hashBytes, SaltSize, HashSize);
return hashBytes;
}
public byte[] Salt { get { return (byte[])_salt.Clone(); } }
public byte[] Hash { get { return (byte[])_hash.Clone(); } }
public bool Verify(string password)
{
byte[] test = new Rfc2898DeriveBytes(password, _salt, HashIter).GetBytes(HashSize);
for (int i = 0; i < HashSize; i++)
if (test[i] != _hash[i])
return false;
return true;
}
}
http://csharptest.net/?p=470
I've been a fan of GUIDs for the salt and SHA512 for the hash. CPU time isn't an issue since it's only during authentication.
but yes, you need to store the hash so you can append it to the incoming password.
storedpassword = hash(salt+password)
if you forget the salt, you can't compare the values.
Absolutly. Use always a new salt per hashed password and not a constant. Having the same salt for all passwords would be unsafe (defense against rainbow tables).
Save it somewhere, e.g. in a database in an extra field or even in the same field with the hashed password together (bcrypt for example does it similar like that) if you can't add an extra field to your database logic.
But always use a new random salt per password.

How does RFC2898DeriveBytes generate an AES key?

I saw some code like
string password = "11111111";
byte[] salt = Encoding.ASCII.GetBytes("22222222");
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt);
RijndaelAlg.Key = key.GetBytes(RijndaelAlg.KeySize / 8);
I can see the key is generated by Rfc2898DeriveBytes with passphrase and salt. Then AES retrieves the key by GetBytes.
But the question is, what does RFC2898DeriveBytes do and what key.GetBytes(cb) do? Could anyone elaborate this? I couldn't get it from the documentation.
RFC2898 refers to a password-based cryptography specification published in September 2000. Effectively, Rfc2898DeriveBytes takes a password and salt to generate keys. The method it uses is known as PBKDF2 (Password Based Key Derivation Function #2) and is defined in section 5.2 of RFC2898. From section 5.2:
PBKDF2 applies a pseudorandom function (see Appendix B.1 for an example) to derive keys. The length of the derived key is essentially unbounded. (However, the maximum effective search space for the derived key may be limited by the structure of the underlying pseudorandom function. See Appendix B.1 for further discussion.) PBKDF2 is recommended for new applications.
For further details, see RFC2898.
As for what Rfc2898DeriveBytes.GetBytes does, it returns a different key on each invocation; it effectively just applies PBKDF2 repeatedly with the same password and salt but also an iteration count.
This is outlined in RFC doc where PBKDF2 is defined as
PBKDF2 (P, S, c, dkLen)
where P is the password, S is the salt, c is the iteration count and dkLen is the length of the desired key.
The RFCs are in general very interesting and historically quite important. RFC 1149 is quite important, as is RFC 2324.
From looking at the implementation in Reflector:
public Rfc2898DeriveBytes(string password, byte[] salt) : this(password, salt, 0x3e8)
{
}
public Rfc2898DeriveBytes(string password, int saltSize, int iterations)
{
if (saltSize < 0)
{
throw new ArgumentOutOfRangeException("saltSize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
byte[] data = new byte[saltSize];
Utils.StaticRandomNumberGenerator.GetBytes(data);
this.Salt = data;
this.IterationCount = iterations;
this.m_hmacsha1 = new HMACSHA1(new UTF8Encoding(false).GetBytes(password));
this.Initialize();
}
public override byte[] GetBytes(int cb)
{
if (cb <= 0)
{
throw new ArgumentOutOfRangeException("cb", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
}
byte[] dst = new byte[cb];
int dstOffset = 0;
int count = this.m_endIndex - this.m_startIndex;
if (count > 0)
{
if (cb < count)
{
Buffer.InternalBlockCopy(this.m_buffer, this.m_startIndex, dst, 0, cb);
this.m_startIndex += cb;
return dst;
}
Buffer.InternalBlockCopy(this.m_buffer, this.m_startIndex, dst, 0, count);
this.m_startIndex = this.m_endIndex = 0;
dstOffset += count;
}
while (dstOffset < cb)
{
byte[] src = this.Func();
int num3 = cb - dstOffset;
if (num3 > 20)
{
Buffer.InternalBlockCopy(src, 0, dst, dstOffset, 20);
dstOffset += 20;
}
else
{
Buffer.InternalBlockCopy(src, 0, dst, dstOffset, num3);
dstOffset += num3;
Buffer.InternalBlockCopy(src, num3, this.m_buffer, this.m_startIndex, 20 - num3);
this.m_endIndex += 20 - num3;
return dst;
}
}
return dst;
}

Categories

Resources