I'm trying to generate ECDSA self-signed certificate as described in generate certificate using ECDSA. Putting all pieces from bartonjs's answer together and using Net.Framework 4.7 (or Net.Core 2.0) following code seems to be working although there are some ambiguities (at least one) left:
I'm not sure how to properly convert private key ('D' parameter) from BC-BigInteger to MS-byte[]. Using BigInteger.ToByteArray() throws exception:
CryptographicException: The specified key parameters are not valid.
Q.X and Q.Y are required fields. Q.X, Q.Y must be the same length. If
D is specified it must be the same length as Q.X and Q.Y for named
curves or the same length as Order for explicit curves.
while validating ECParameters (method ECParameters.Validate()). Using BigInteger.ToByteArrayUnsigned() provides much better results (one failure on several hundred generated key-pairs), but still...
When using ToByteArray() converted 'D' is usually one byte longer ('D' has 33 bytes vs D.X and D.Y has 32 bytes). Using ToByteArrayUnsigned() the 'D' is sometimes one byte shorter.
So my question is whether is is ok to use ToByteArrayUnsigned().
private const string NCryptExportPolicyProperty = "Export Policy";
private const string SignatureAlgorithm = "Sha256WithECDSA";
private static readonly ECCurve MsCurve = ECCurve.NamedCurves.nistP256;
private static readonly DerObjectIdentifier BcCurve = SecObjectIdentifiers.SecP256r1; // must correspond with MsCurve
public static X509Certificate2 Create()
{
// 1. generate keys:
IAsymmetricCipherKeyPairGenerator bcKeyGen = GeneratorUtilities.GetKeyPairGenerator("ECDSA");
bcKeyGen.Init(new ECKeyGenerationParameters(BcCurve, new SecureRandom()));
ECPrivateKeyParameters bcPrivKey;
ECPublicKeyParameters bcPublKey;
bool validated;
ECParameters msEcp;
do
{
AsymmetricCipherKeyPair bcKeyPair = bcKeyGen.GenerateKeyPair();
bcPrivKey = (ECPrivateKeyParameters)bcKeyPair.Private;
bcPublKey = (ECPublicKeyParameters)bcKeyPair.Public;
// 2. ensure generated bc-keys can be translated to cng (see exception below)
msEcp = new ECParameters();
msEcp.Curve = MsCurve;
msEcp.D = bcPrivKey.D.ToByteArrayUnsigned(); // or bcPrivKey.D.ToByteArray() ??
msEcp.Q.X = bcPublKey.Q.XCoord.GetEncoded();
msEcp.Q.Y = bcPublKey.Q.YCoord.GetEncoded();
try
{
msEcp.Validate();
validated = true;
}
catch (Exception e)
{
// Validate() occasionally throws CryptographicException:
// The specified key parameters are not valid. Q.X and Q.Y are required fields. Q.X, Q.Y must be the same length. If D is specified it must be the same length as Q.X and Q.Y for named curves or the same length as Order for explicit curves.
// e.g.: D = 31, Q.X = 32, Q.Y = 32.
validated = false;
Console.WriteLine("D = {0}, Q.X = {1}, Q.Y = {2}. {3}: {4}", msEcp.D.Length, msEcp.Q.X.Length, msEcp.Q.Y.Length, e.GetType().Name, e.Message);
}
} while (!validated);
// 3. create x509 certificate:
X509V3CertificateGenerator bcCertGen = new X509V3CertificateGenerator();
bcCertGen.SetPublicKey(bcPublKey);
// .. set subject, validity period etc
ISignatureFactory sigFac = new Asn1SignatureFactory(SignatureAlgorithm, bcPrivKey);
Org.BouncyCastle.X509.X509Certificate bcX509Cert = bcCertGen.Generate(sigFac);
byte[] x509CertEncoded = bcX509Cert.GetEncoded();
X509Certificate2 msNewCert;
// 4. use translated (and validated) parameters:
using (ECDsaCng msEcdsa = new ECDsaCng())
{
msEcdsa.ImportParameters(msEcp);
CngKey msPrivateKey = msEcdsa.Key;
// 5. make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
msPrivateKey.SetProperty(pty);
// 6. tie keys together:
using (X509Certificate2 msPubCertOnly = new X509Certificate2(x509CertEncoded))
{
msNewCert = MateECDsaPrivateKey(msPubCertOnly, msPrivateKey); // method from bartonjs's answer
}
}
return msNewCert;
}
Thank you in advance
When you are getting too many bytes (33 in this case) the first byte should be 0x00, and you need to remove it. When you are getting too few (technically speaking D=1 is valid) you need to insert zeros to fill the array out.
The reason is that .NET's structure expects D to look like it does to the underlying Windows CNG import API, which means that D is a fixed-with unsigned big endian big integer. BouncyCastle is giving you the BER INTEGER encoding, which requires inserting a 0x00 byte when the high bit of the most significant byte (bytes[0], big endian) is set in a number that should be considered positive.
BER also has a rule that the minimum number of bytes be used, which is why sometimes BouncyCastle gives a number that's too small.
Q.X and Q.Y are okay because the ECPoint encoding rules specify a fixed size big endian integer whose size is determined by the curve; which is why BouncyCastle has the GetEncoded method instead of just ToByteArrayUnsigned.
private static byte[] FixSize(byte[] input, int expectedSize)
{
if (input.Length == expectedSize)
{
return input;
}
byte[] tmp;
if (input.Length < expectedSize)
{
tmp = new byte[expectedSize];
Buffer.BlockCopy(input, 0, tmp, expectedSize - input.Length, input.Length);
return tmp;
}
if (input.Length > expectedSize + 1 || input[0] != 0)
{
throw new InvalidOperationException();
}
tmp = new byte[expectedSize];
Buffer.BlockCopy(input, 1, tmp, 0, expectedSize);
return tmp;
}
...
msEcp = new ECParameters();
msEcp.Curve = MsCurve;
msEcp.Q.X = bcPublKey.Q.XCoord.GetEncoded();
msEcp.Q.Y = bcPublKey.Q.YCoord.GetEncoded();
msEcp.D = FixSize(bcPrivKey.D.ToByteArrayUnsigned(), msEcp.Q.X.Length);
The following code will help you, you can generate algorithm using bouncy castle library:
private static ECDsa GetEllipticCurveAlgorithm(string privateKey)
{
var keyParams = (ECPrivateKeyParameters)PrivateKeyFactory
.CreateKey(Convert.FromBase64String(privateKey));
var normalizedECPoint = keyParams.Parameters.G.Multiply(keyParams.D).Normalize();
return ECDsa.Create(new ECParameters
{
Curve = ECCurve.CreateFromValue(keyParams.PublicKeyParamSet.Id),
D = keyParams.D.ToByteArrayUnsigned(),
Q =
{
X = normalizedECPoint.XCoord.GetEncoded(),
Y = normalizedECPoint.YCoord.GetEncoded()
}
});
}
and generate the token in the following way:
var signatureAlgorithm = GetEllipticCurveAlgorithm(privateKey);
ECDsaSecurityKey eCDsaSecurityKey = new ECDsaSecurityKey(signatureAlgorithm)
{
KeyId = settings.Apple.KeyId
};
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(
issuer: iss,
audience: AUD,
subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
expires: DateTime.UtcNow.AddMinutes(5),
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(eCDsaSecurityKey, SecurityAlgorithms.EcdsaSha256));
Related
I have a server (python) and a client (c#), I need to communicate between them temporarily using assymetric rsa cryptography.
When I connect to the server as the client, I send him my public key and he send me his. I use at the server the rsa library and I get there the server's public key parameters {n,e} I send those and receive them with a space between them. I seperate them and convert the modulus into a BigInteger using this function:
public static BigInteger GetBigInteger(string number)
{
BigInteger bigNum = new BigInteger();
for (int i = number.Length; i > 0; i--)
{
bigNum *= 10;
bigNum += (int) number[number.Length-i];
}
return bigNum;
}
public static void Connect(IPAddress ipAddress, int port)
{
try
{
string[] message;
byte[] data = new byte[1024];
srvr.Receive(data); //Recieve the server's public key.
int length = int.Parse(Encoding.ASCII.GetString(data.Take(4).ToArray()));
message = Encoding.ASCII.GetString(data.Skip(4).Take(length).ToArray()).Split(' ') ;
RSACryptoServiceProvider RSAserver = new RSACryptoServiceProvider(1024);
RSAParameters par = new RSAParameters();
par.Modulus = GetBigInteger(message[0]).ToByteArray(); // Saves the server's public key.
par.Exponent = new byte[] { 1, 0, 1 }; // Saves the server's public key.
RSAserver.ImportParameters(par);
addresseeKey = RSAserver.ToXmlString(false);
...
}
...
}
An exception is thrown on the ImportParameters line that says: "The parameter is incorrect" .
What's wrong?
BigInteger.ToByteArray() exports the data in little-endian order. RSAParameters wants all of its data in big-endian order.
There's also an issue that the modulus value should have its most significant bit set, and therefore BigInteger.ToByteArray() needs to add in a padding byte to prevent the number from being reinterpreted as being negative, so you'll need to trim that off.
Assuming that your python export is representing the modulus as a base-10 positive integer, your code will work (though BigInteger.Parse(string) would be more efficient) once you align the data properly.
byte[] tmp = GetBigInteger(message[0]).ToByteArray();
// Array.Resize (to a smaller number) trims off the high index values,
// so it's slightly more compact code to do this before the resize.
if (tmp.Length > 0 && tmp[tmp.Length-1] == 0)
Array.Resize(ref tmp, tmp.Length - 1);
Array.Reverse(tmp);
par.Modulus = tmp;
I'm trying to simulate a Meet-in-the-Middle attack on Double DES which works as follows:
So I'm performing the MitM attack by first encrypting a known plaintext m and with all possible values of k1, and then subsequently I decrypt a known ciphertext c with all possible values of k2. Then there should be a match inbetween which gives me k1, and k2. I'm using a cut-down key size of 20 bits instead of 56bit (or 64bit which is what the DES implementation actually wants as input). I just pad with zeroes after 20bits.
I've implemented what I think is a right solution but not getting any matches.
My two hashtables:
Dictionary<string, string> hashTable = new Dictionary<string, string>();
Dictionary<string, string> matches = new Dictionary<string, string>();
Encrypting:
Since I'm using a reduced 20bit key, there can be 220 different combinations of 20bits. So for each iteration, I take the counter i, convert it to a binary string representation, then I pad 0s until it makes a 64bit binary value. Then I convert this string to a byte array and encrypt the plaintext with the key. I'm storing the output (intermediary cipher) as the key in the hashTable and the actual key used to get that cipher, as the value.
//First generate all possible intermediary values
for(int i=0; i< Math.Pow(2,20); i++)
{
string key1 = ToBin(i, 20);
string paddedKey1 = ToBin(i, 20).PadRight(64, '0');
//First encryption of plaintext1 using key1
string intermediaryCipher =
DESWrapper.DES_Encrypt(plaintext1, ConvertBinaryStringToByteArray(paddedKey1));
hashTable.Add(intermediaryCipher, key1);
//Show the current iteration in binary
Console.WriteLine(ToBin(i, 20));
}
DESWrapper.DES_Encrypt method:
public static string DES_Encrypt(string input, byte[] key)
{
DESCryptoServiceProvider desCryptoService = new DESCryptoServiceProvider();
//Reflection necessary otherwise it complains about a weak key, so bypassing that check
MethodInfo mi = desCryptoService.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
object[] Par = { key, desCryptoService.Mode, key, desCryptoService.FeedbackSize, 0 };
ICryptoTransform trans = mi.Invoke(desCryptoService, Par) as ICryptoTransform;
byte[] resultArray = trans.TransformFinalBlock(Encoding.Default.GetBytes(input), 0, Encoding.Default.GetBytes(input).Length);
desCryptoService.Clear();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
After the encryption I have a hastable with 220 entries. The next thing I do is to make the decryption:
Decrypting:
When I'm decrypting ciphertext1 with the current padded key, the result, if it is the correct key, will be an intermediary cipher which already exists in the hashTable as a Key. So I perform this lookup. If it exists I save the two keys to another hashtable matches. If it doesn't exist I move on.
for (int i = 0; i < Math.Pow(2, 20); i++)
{
string key2 = ToBin(i, 20);
string paddedKey2 = ToBin(i, 20).PadRight(64, '0');
//Decrypting ciphertext1 with key2 (64bit padded)
string intermediaryCipher =
DESWrapper.DES_Decrypt(ciphertext1, ConvertBinaryStringToByteArray(paddedKey2));
var temp = hashTable.FirstOrDefault(x => x.Key == intermediaryCipher);
if(temp.Key != null)
{
matches.Add(temp.Value, key2);
Console.WriteLine("Found match!");
Console.ReadKey();
}
//Show the current iteration in binary
Console.WriteLine(ToBin(i, 20));
}
DESWrapper.DES_Decrypt:
public static string DES_Decrypt(string input, byte[] key)
{
DESCryptoServiceProvider desCryptoService = new DESCryptoServiceProvider();
//Again have to use reflection..
MethodInfo mi = desCryptoService.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance);
object[] Par = { key, desCryptoService.Mode, key, desCryptoService.FeedbackSize, 0 };
ICryptoTransform trans = mi.Invoke(desCryptoService, Par) as ICryptoTransform;
byte[] resultArray = trans.TransformFinalBlock(Encoding.Default.GetBytes(input), 0, Encoding.Default.GetBytes(input).Length);
desCryptoService.Clear();
return Convert.ToBase64String(resultArray);
}
The problem:
I never get a match, so the hashtable lookup always returns an empty key-value pair. I don't understand why. Eventually it should match but it doesn't.
Could the problem be in the way I'm trying to look up the values in the hashTable?
Other information:
To encrypt the intial plaintext and ciphertext I use a fabricated key which the 17th bit set to 1 and all other 63 bits set to 0. This is done for both keys.
In this way I should get the match quite fast when I'm doing the decryption, but I'm not sure if the problem is here. Including the code anyway:
private static void GeneratePlaintextCiphertextPairs(out string plainText1, out string plainText2, out string cipherText1, out string cipherText2)
{
Random rnd = new Random(Guid.NewGuid().GetHashCode());
//Generate two random keys of 20 bits padded with 0s to reach 64 bits
//We need 64 bits because the implementation of DES requires it. Internally,
//it will only use 56 bits
byte[] key1 = GenerateRandom64BitPaddedKey(rnd);
byte[] key2 = GenerateRandom64BitPaddedKey(rnd);
plainText1 = "Hello Dear World";
//Perform double DES encryption
cipherText1 = DESWrapper.DES_Encrypt(
DESWrapper.DES_Encrypt(plainText1, key1),
key2);
plainText2 = "Hello Evil World";
//Perform double DES encryption
cipherText2 = DESWrapper.DES_Encrypt(
DESWrapper.DES_Encrypt(plainText2, key1),
key2);
}
private static byte[] GenerateRandom64BitPaddedKey(Random rnd)
{
short keySize = 64;
//The first 20bits are of interest, the rest is padded with 0s
BitArray bitArray = new BitArray(keySize);
for (int i=0; i<keySize; i++)
{
//if(i < 20) { bitArray[i] = rnd.NextDouble() > 0.5; }
//else { bitArray[i] = false; }
if (i == 17) { bitArray[i] = true; }
else { bitArray[i] = false; }
}
//Console.WriteLine("In Binary: " + ToDigitString(bitArray));
byte[] key = new byte[8];
ReverseBitArray(ref bitArray);
bitArray.CopyTo(key, 0);
return key;
}
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.
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.
I've been trying to encrypt a password with a public RSA key that is sent to me by the server.
var csp = new CspParameters(1, "Microsoft Strong Cryptographic Provider");
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1280, csp);
byte[] key = ByteUtils.HexToBytes(client.RSAKey);
RSA.ImportCspBlob(key);
byte[] encrypted = RSA.Encrypt(Encoding.ASCII.GetBytes(password), true);
The hex key is provided in such format:
string key = "30819D300D06092A864886F70D010101050003818B0030818702818100C7BD672D8C634D443840AD809790852770D3A2E99F456D6516329E0205D0645C23FD001D4D070CEE368A20526FEB2402358C915D7E86102B1659AA8651C449C344599F72BE904B8E338E7002E9978453C5BBCCA51AC165AA265069E0EAB1411D11A2FFDD35E5A8296A6A2AF238945874E8206979B0A16E2E4260A161CAB5C905020111";
As the string is 320-bytes long in hex format, I assume the key is 160 bytes (RSA 1280)
Using this method, the provider keeps saying "Bad Version of provider.\r\n".
I've tried several methods, convert it to Base64, simply import it as ASCII / Unicode. Nothing worked so far.
EDIT: My HexToBytes function (which works afaik, it returns me correct 160-b array):
public static byte[] HexToBytes(string pValue)
{
// FIRST. Use StringBuilder.
StringBuilder builder = new StringBuilder();
// SECOND... USE STRINGBUILDER!... and LINQ.
foreach (char c in pValue.Where(IsHexDigit).Select(Char.ToUpper))
{
builder.Append(c);
}
// THIRD. If you have an odd number of characters, something is very wrong.
string hexString = builder.ToString();
if (hexString.Length % 2 == 1)
{
//throw new InvalidOperationException("There is an odd number of hexadecimal digits in this string.");
// I will just add a zero to the end, who cares (0 padding)
Log.WriteLine(LogLevel.Debug, "Hexstring had an odd number of hexadecimal digits.");
hexString += '0';
}
byte[] bytes = new byte[hexString.Length / 2];
// FOURTH. Use the for-loop like a pro :D
for (int i = 0, j = 0; i < bytes.Length; i++, j += 2)
{
string byteString = String.Concat(hexString[j], hexString[j + 1]);
bytes[i] = HexToByte(byteString);
}
return bytes;
}
Your public key is not in the correct format. It is not a CSP blob. It is a DER encoded SubjectPublicKeyInfo structure. You can find source code to parse it or you can write your own. Here is one example of such code.