I've been reading this article from MSDN on Rfc2898DeriveBytes. Here is the sample encryption code they provide.
string pwd1 = passwordargs[0];
// Create a byte array to hold the random value.
byte[] salt1 = new byte[8];
using (RNGCryptoServiceProvider rngCsp = ne RNGCryptoServiceProvider())
{
// Fill the array with a random value.
rngCsp.GetBytes(salt1);
}
//data1 can be a string or contents of a file.
string data1 = "Some test data";
//The default iteration count is 1000 so the two methods use the same iteration count.
int myIterations = 1000;
try
{
Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pwd1,salt1,myIterations);
Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(pwd1, salt1);
// Encrypt the data.
TripleDES encAlg = TripleDES.Create();
encAlg.Key = k1.GetBytes(16);
MemoryStream encryptionStream = new MemoryStream();
CryptoStream encrypt = newCryptoStream(encryptionStream, encAlg.CreateEncryptor(), CryptoStreamMode.Write);
byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(data1);
encrypt.Write(utfD1, 0, utfD1.Length);
encrypt.FlushFinalBlock();
encrypt.Close();
byte[] edata1 = encryptionStream.ToArray();
k1.Reset();
My question is, how would I properly Read/Write the hashed data to/from a text file?
My main goal is to do what this developer is doing. I need to store a password locally. When my application prompts the user for the password, the user will enter the password, then my application will read from the text file and verify if the password that the user entered is indeed correct. How would I go about doing it?
You typically store the hash of the password, then when user enters password, you compute hash over the entered password and compare it with the hash which was stored - that said, just hashing is usually not enough (from security point of view) and you should use a function such as PKBDF2 (Password-Based Key Derivation Function 2) instead. Here is article covering all that information in more elaborate way as well as sample code (bottom of the page): http://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right
Here is a link to codereview, which I guess refers to the same implementation as above article.
How to properly store password locally
Just don't do it. No really don't do it.
...But if you really really have to, never just implement it yourself. I would recommend reviewing how ASP.NET Identity hashes passwords. Version 3 is pretty rock solid at the moment:
note that the following is taken from github.com and may be changed at any time. For the latest, please refer to the previous link.
private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested)
{
// Produce a version 3 (see comment above) text hash.
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;
}
You should store the password as a one-way hash and the salt used to create that password. This way you are absolutely sure that the password for the user can never be DECRYPTED. Never use any two-way encryption for this particular task, as you risk exposing user information to would-be attackers.
void Main()
{
string phrase, salt, result;
phrase = "test";
result = Sha256Hash(phrase, out salt);
Sha256Compare(phrase, result, salt);
}
public string Sha256Hash(string phrase, out string salt)
{
salt = Create256BitSalt();
string saltAndPwd = String.Concat(phrase, salt);
Encoding encoder = Encoding.Default;
SHA256Managed sha256hasher = new SHA256Managed();
byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(saltAndPwd));
string hashedPwd = Encoding.Default.GetString(hashedDataBytes);
return hashedPwd;
}
public bool Sha256Compare(string phrase, string hash, string salt)
{
string saltAndPwd = String.Concat(phrase, salt);
Encoding encoder = Encoding.Default;
SHA256Managed sha256hasher = new SHA256Managed();
byte[] hashedDataBytes = sha256hasher.ComputeHash(encoder.GetBytes(saltAndPwd));
string hashedPwd = Encoding.Default.GetString(hashedDataBytes);
return string.Compare(hash, hashedPwd, false) == 0;
}
public string Create256BitSalt()
{
int _saltSize = 32;
byte[] ba = new byte[_saltSize];
RNGCryptoServiceProvider.Create().GetBytes(ba);
return Encoding.Default.GetString(ba);
}
You could also figure out another method for obtaining the salt, but I have made mine to that it computes 2048 bits worth of random data. You could just use a random long you generate but that would be a lot less secure. You won't be able to use SecureString because SecureString isn't Serializable. Which the whole point of DPAPI. There are ways to get the data out but you end up having to jump a few hurdles to do it.
FWIW, PBKDF2 (Password-Based Key Derivation Function 2) is basically the same thing as SHA256 except slower (a good thing). On its own both are very secure. If you combined PBKDF2 with an SHA256 as your salt then you'd have a very secure system.
Related
I'm trying to learn cyber security and this is the very first thing I've done on it. I'm using this MSDN document ( https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes?redirectedfrom=MSDN&view=netcore-3.1 ) and it partly works. I assume it's encrypting it fine as when it comes to the decryption some of the original data is there but some is lost. The data that is being encrypted is a class that has been formatted into a JSON string( I don't think this is relevant as it's still a string being encrypted).
But once it's been encrypted and decrypted it turns out like this:
I've ran this code and compared the results 5+ times and it's always: the start is wrong, username is partly right, password is always right and loginkey is partly right. So the error is recurring and always in the same spot.
Information you should know, the data get's encrypted and saved to a .txt file. The programme will run again and it will try and decrypted it. The Salt and password are saved on another file and those are read and used in the decryption.
There is a similar question on stackoverflow but the answer just says to use Rijndael(so not really an answer), this code is for me to learn and want an answer that isn't 4 lines long.
Code if curious(but it's basically the same as the MSDN document):
Encryption:
static void EncryptFile()
{
string pwd1 = SteamID;//steamID is referring to account ID on Valve Steam
using (RNGCryptoServiceProvider rngCsp = new
RNGCryptoServiceProvider())
{
rngCsp.GetBytes(salt1); //salt1 is a programme variable and will get saved to a file
}
SecureData File = new SecureData(_UserName,_PassWord,_LoginKey);
string JsonFile = JsonConvert.SerializeObject(File); //puts the class into Json format
int myIterations = 1000; //not needed
try
{
Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pwd1, salt1,
myIterations);
Aes encAlg = Aes.Create(); // This might be the issue as AES will be different when you decrypt
encAlg.Key = k1.GetBytes(16);
MemoryStream encryptionStream = new MemoryStream();
CryptoStream encrypt = new CryptoStream(encryptionStream,
encAlg.CreateEncryptor(), CryptoStreamMode.Write);
byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(
JsonFile); //encrypt Data
encrypt.Write(utfD1, 0, utfD1.Length);
encrypt.FlushFinalBlock();
encrypt.Close();
byte[] edata1 = encryptionStream.ToArray();
k1.Reset();
System.IO.File.WriteAllBytes(SecureFile, edata1); //writes encrypted data to file
}
catch (Exception e)
{
Console.WriteLine("Error: ", e);
}
}
Decryption:
static void DecryptFile()
{
string pwd1 = SteamID;
byte[] edata1;
try
{
edata1 = System.IO.File.ReadAllBytes(SecureFile); //reads the file with encrypted data on it
Aes encAlg = Aes.Create(); //I think this is the problem as the keyvalue changes when you create a new programme
Rfc2898DeriveBytes k2 = new Rfc2898DeriveBytes(pwd1, salt1); //inputs from last time carry over
Aes decAlg = Aes.Create();
decAlg.Key = k2.GetBytes(16);
decAlg.IV = encAlg.IV;
MemoryStream decryptionStreamBacking = new MemoryStream();
CryptoStream decrypt = new CryptoStream(
decryptionStreamBacking, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
decrypt.Write(edata1, 0, edata1.Length);
decrypt.Flush();
decrypt.Close();
k2.Reset();
string data2 = new UTF8Encoding(false).GetString(
decryptionStreamBacking.ToArray());//decrypted data
SecureData items = JsonConvert.DeserializeObject<SecureData>(data2); //reformat it out of JSon(Crashes as format isn't accepted)
_UserName = items.S_UserName;
_PassWord = items.S_Password;
_LoginKey = items.S_LoginKey;
}
catch (Exception e)
{
Console.WriteLine("Error: ", e);
NewLogin();
}
}
Class Struct:
class SecureData
{
public string S_UserName { get; set; } //These are variables that are apart of Valve steam Login process
public string S_Password { get; set; }
public string S_LoginKey { get; set; }
public SecureData(string z, string x, string y)
{
S_UserName = z;
S_Password = x;
S_LoginKey = y;
}
}
The problem is caused by different IVs for encryption and decryption. For a successful decryption the IV from the encryption must be used.
Why are different IVs applied? When an AES instance is created, a random IV is implicitly generated. Two different AES instances therefore mean two different IVs. In the posted code, different AES instances are used for encryption and decryption. Although the reference encAlg used in the decryption has the same name as that of the encryption, the referenced instance is a different one (namely an instance newly created during decryption). This is different in the Microsoft example. There, the IV of the encryption is used in the decryption: decAlg.IV = encAlg.IV, where encAlg is the AES instance with which the encryption was performed.
The solution is to store the IV from the encryption in the file so that it can be used in the decryption. The IV is not secret and is usually placed before the ciphertext:
Necessary changes in EncryptFile:
...
byte[] utfD1 = new System.Text.UTF8Encoding(false).GetBytes(JsonFile);
encryptionStream.Write(encAlg.IV, 0, encAlg.IV.Length); // Write the IV
encryptionStream.Flush();
encrypt.Write(utfD1, 0, utfD1.Length);
...
Necessary changes in DecryptFile:
...
edata1 = System.IO.File.ReadAllBytes(SecureFile);
byte[] iv = new byte[16]; // Separate IV and ciphertext
byte[] ciphertext = new byte[edata1.Length - iv.Length];
Array.Copy(edata1, 0, iv, 0, iv.Length);
Array.Copy(edata1, iv.Length, ciphertext, 0, ciphertext.Length);
...
Aes encAlg = Aes.Create(); // Remove this line
...
decAlg.IV = iv; // Use the separated IV
...
decrypt.Write(ciphertext, 0, ciphertext.Length); // Use the separated ciphertext
A few remarks:
For each encryption a new, random salt should be generated and concatenated with the ciphertext analogous to the IV. During decryption, the salt can then be determined analogous to IV. Consider additionally RFC8018, sec 4.1.
The iteration count slows down the derivation of the key, which should make an attack by repeated attempts more difficult. Therefore the value should be as large as possible. Consider additionally RFC8018, sec 4.2.
Authentication data (i.e. passwords) are not encrypted, but hashed, here.
I'm attempting to replace PasswordDerivedBytes with Rfc2898DerivedBytes but I'm having a problem with the latter when getting back a unicode encoded result.
Take this code for example:
[TestMethod]
public void DerivedBytesTest()
{
string encrypted = "y4Ijqo9Ga/mHlFbLHDdDUkYZlyu7CHF4PVXGLnb8by7FAVtCgPLhFSiA9Et6hDac";
string key = "{00B3403A-3C29-4f26-A9CC-14C411EA8547}";
string salt = "gT5M07XB9hHl3l1s";
string expected = "4552065703414505";
string decrypted;
decrypted = Decrypt(encrypted, key, salt, true);
Assert.IsTrue(decrypted == expected); // Works
decrypted = Decrypt(encrypted, key, salt, false);
Assert.IsTrue(decrypted == expected); // Doesn't work, get wrong unicode characters in 24 character string
}
private string Decrypt(string encrypted, string key, string salt, bool legacy = false)
{
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] encryptedDataBytes = Convert.FromBase64String(encrypted);
byte[] saltBytes = encoding.GetBytes(salt);
RijndaelManaged encryption = new RijndaelManaged();
DeriveBytes secretKey;
if (legacy)
{
secretKey = new PasswordDeriveBytes(key, saltBytes) {IterationCount = 100};
encryption.Padding = PaddingMode.PKCS7;
}
else
{
secretKey = new Rfc2898DeriveBytes(key, saltBytes, 100);
encryption.Padding = PaddingMode.Zeros; // This is the only one that doesn't throw the "Padding is invalid and cannot be removed" exception, but gives me a non-ASCII result
}
ICryptoTransform decryptor = encryption.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));
string decryptedText = "";
using (MemoryStream memoryStream = new MemoryStream(encryptedDataBytes))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
byte[] bytes = new byte[encryptedDataBytes.Length];
int decryptedCount = cryptoStream.Read(bytes, 0, bytes.Length);
decryptedText = encoding.GetString(bytes, 0, decryptedCount);
if (!legacy)
{
// Something more to do with result?
}
}
}
return decryptedText;
}
I wonder if anyone can advise where I'm going wrong?
PasswordDeriveBytes is a badly implemented extension of PBKDF1, while Rfc2898DeriveBytes is the implementation of PBKDF2. Both derive a key from a password, but they are two different algorithms and therefore they derive two different results. As they are using cryptographically secure hashes underneath, there is no way to convert one to another.
If you can spare a few bytes of storage you could still derive the key using PKBDF1 and then encrypt that key using the result of PBKDF2. If the output size is identical you could even use XOR encryption for that (a one-time-pad) but AES would of course also work. So then the decryption becomes: calculate PBKDF2 result, decrypt data key, use data key to decrypt ciphertext.
Otherwise you will have to decrypt and then re-encrypt the result.
If you want to compare the decryption result then compare the resulting bytes; do not first convert it into a string. Using authenticated encryption or a MAC is highly advised so that a authentication tag can be validated instead. Just ignoring padding exceptions by using Zero Padding is not the way to go. These padding errors occur because the key is wrong.
Generic notes:
PasswordDeriveBytes should not be used for any amount of bytes > 20 bytes as the Mickeysoft extension of PBKDF1 is horribly insecure, even repeating bytes in the output (!). If you do the same for PBKDF2 then any adversary will have to do half the work that you have to do so that's not a good idea either.
The iteration count in the question is very low, but as you seem to use a highly random UID instead of a password that should be OK.
I am using a Java based configuration management tool called Zuul which supports encrypting sensitive configuration information using various encryption schemes.
I have configured it to use below scheme for my data
AES (Bouncy Castle)
Name: PBEWITHSHA256AND128BITAES-CBC-BC
Requirements: Bouncy Castle API and JCE Unlimited Strength Policy Files
Hashing Algorithm: SHA256
Hashing Iterations: 1000
Now when reading my configuration data back, I need to decrypt the information before I can use it and the documentation provides below information around this topic.
The encrypted values produced by Jasypt (and thus Zuul) are are prefixed with the salt (usually 8 or 16 bytes depending on the algorithm requirements). They are then Base64 encoded. Decrypting the results goes something like this:
Convert the Base64 string to bytes
Strip off the first 8 or 16 bytes as the salt
Keep the remaining bytes for the encrypted payload
Invoke the KDF function with the salt, iteration count and the password to create the secret key.
Use the secret key to decrypt the encrypted payload
More details here: Zull Encryption wiki
Based on above details, I have written below code (and my knowledge around security is very limited)
public static string Decrypt(string cipher, string password)
{
const int saltLength = 16;
const int iterations = 1000;
byte[] cipherBytes = Convert.FromBase64String(cipher);
byte[] saltBytes = cipherBytes.Take(saltLength).ToArray();
byte[] encryptedBytes = cipherBytes.Skip(saltLength).ToArray();
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, saltBytes, iterations);
byte[] keyBytes = key.GetBytes(16);
AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider();
aesAlg.KeySize = 256;
aesAlg.BlockSize = 128;
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
MemoryStream msDecrypt = new MemoryStream(encryptedBytes);
CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read);
StreamReader srDecrypt = new StreamReader(csDecrypt);
return srDecrypt.ReadToEnd();
}
I configured Zuul to use below password for the encryption
SimplePassword
And now I have an encrypted string given to me by Zuul and I need to decrypt it
p8C9hAHaoo0F25rMueT0+u0O6xYVpGIkjHmWqFJmTOvpV8+cipoDFIUnaOFF5ElQ
When I try to decrypt this string using above code, I get below exception
System.Security.Cryptography.CryptographicException : Padding is invalid and cannot be removed.
As I mentioned earlier, my knowledge around this topic is limited and I am not able to figure out if the information provided in the documentation is not enough, if I am doing something wrong while writing the decryption routine or should I be using bouncy castle for decryption as well.
Any help with this will be much appreciated.
According to Zuul documentation they are deriving both key and iv from the password/salt.
So you should derive 256+128 bits (i.e. 48 bytes), and use first 32 bytes as the key, and next 16 bytes as IV.
And this should be done in one operation, not as consequent calls to key.DeriveBytes.
I resorted to Bouncy Castle for decryption instead since that is used by Zuul as well.
Here is the code that works
public static string Decrypt(string cipher, string password)
{
const int saltLength = 16;
const int iterations = 1000;
const string algSpec = "AES/CBC/NoPadding";
const string algName = "PBEWITHSHA256AND128BITAES-CBC-BC";
byte[] cipherBytes = Convert.FromBase64String(cipher);
byte[] saltBytes = cipherBytes.Take(saltLength).ToArray();
byte[] encryptedBytes = cipherBytes.Skip(saltLength).ToArray();
char[] passwordChars = password.ToCharArray();
Asn1Encodable defParams = PbeUtilities.GenerateAlgorithmParameters(algName, saltBytes, iterations);
IWrapper wrapper = WrapperUtilities.GetWrapper(algSpec);
ICipherParameters parameters = PbeUtilities.GenerateCipherParameters(algName, passwordChars, defParams);
wrapper.Init(false, parameters);
byte[] keyText = wrapper.Unwrap(encryptedBytes, 0, encryptedBytes.Length);
return Encoding.Default.GetString(keyText);
}
I'm having trouble with WS-Security, and creating a nonce and password digest that is correct.
I am successfully using SoapUI to send data to an Oracle system. So I'm able to intercept SoapUI's call (change proxy to 127.0.0.1 port 8888 to use Fiddler where it fails because it's over SSL) - intercepting is important because these values can only be used once. I can then grab the nonce, created timestamp and password digest put them into my code (I've only got 30 seconds to do this as the values don't last!) and I get a success.
So I know it's nothing else - just the Password Digest.
The values I use are the following:
Nonce: UIYifr1SPoNlrmmKGSVOug==
Created Timestamp: 2009-12-03T16:14:49Z
Password: test8
Required Password Digest: yf2yatQzoaNaC8BflCMatVch/B8=
I know the algorithm for creating the Digest is:
Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )
using the following code (from Rick Strahl's post)
protected string GetSHA1String(string phrase)
{
SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
return Convert.ToBase64String(hashedDataBytes);
}
I get:
GetSHA1String("UIYifr1SPoNlrmmKGSVOug==" + "2009-12-03T16:14:49Z" + "test8") = "YoQKI3ERlMDGEXHlztIelsgL50M="
I have tried various SHA1 methods, all return the same results (which is a good thing I guess!):
SHA1 sha1 = SHA1.Create();
SHA1 sha1 = SHA1Managed.Create();
// Bouncy Castle:
protected string GetSHA1usingBouncyCastle(string phrase)
{
IDigest digest = new Sha1Digest();
byte[] resBuf = new byte[digest.GetDigestSize()];
byte[] bytes = Encoding.UTF8.GetBytes(phrase);
digest.BlockUpdate(bytes, 0, bytes.Length);
digest.DoFinal(resBuf, 0);
return Convert.ToBase64String(resBuf);
}
Any ideas on how to get the correct hash?
The problem was the nonce.
I was trying to use a nonce that had already been Base64 encoded. If you want to use a Nonce that is in the form "UIYifr1SPoNlrmmKGSVOug==" then you need to decode it.
Convert.FromBase64String("UIYifr1SPoNlrmmKGSVOug==")
which is a byte array.
So we need a new method:
public string CreatePasswordDigest(byte[] nonce, string createdTime, string password)
{
// combine three byte arrays into one
byte[] time = Encoding.UTF8.GetBytes(createdTime);
byte[] pwd = Encoding.UTF8.GetBytes(password);
byte[] operand = new byte[nonce.Length + time.Length + pwd.Length];
Array.Copy(nonce, operand, nonce.Length);
Array.Copy(time, 0, operand, nonce.Length, time.Length);
Array.Copy(pwd, 0, operand, nonce.Length + time.Length, pwd.Length);
// create the hash
var sha1Hasher = new SHA1CryptoServiceProvider();
byte[] hashedDataBytes = sha1Hasher.ComputeHash(operand);
return Convert.ToBase64String(hashedDataBytes);
}
CreatePasswordDigest(Convert.FromBase64String("UIYifr1SPoNlrmmKGSVOug=="), "2009-12-03T16:14:49Z", "test8")
which returns yf2yatQzoaNaC8BflCMatVch/B8= as we want.
Remember to use the same createdTime in the digest as you put in the XML, this might sound obvious, but some people include milliseconds on their timestamps and some don't - it doesn't matter, it just needs to be consistent.
Also the Id field in the UsernameToken XML doesn't matter - it doesn't need to change.
Here's a method to create a Nonce like the one above, if you don't want to use GUIDs like Rick uses:
private byte[] CreateNonce()
{
var Rand = new RNGCryptoServiceProvider();
//make random octets
byte[] buf = new byte[0x10];
Rand.GetBytes(buf);
return buf;
}
I hope that helps someone - it took me lots of frustration, trial and error, searching web pages, and general head/wall banging.
I looked up msdn and other resources on how to do this but i came up with no clear solutions. This is the best i found http://blogs.msdn.com/b/shawnfa/archive/2004/04/14/generating-a-key-from-a-password.aspx?Redirected=true
I would like to hash passwords in C# using either bcrypt or PBKDF2 (which appears to be bcrypt related). I like to experiment with how many rounds it takes for my computer to hash a password. However everything seems to be about encrypting while everyone talks about hashing. I can't figure it out. How do i hash a password? It looks more like PBKDF2 (Rfc2898?) is a random number generator and i use GetBytes(amount) to choose how big my hash size is.
I'm confused. How exactly do i hash a password with bcrypt/PBKDF?
PBKDF2
You were really close actually. The link you have given shows you how you can call the Rfc2898DeriveBytes function to get PBKDF2 hash results. However, you were thrown off by the fact that the example was using the derived key for encryption purposes (the original motivation for PBKDF1 and 2 was to create "key" derivation functions suitable for using as encryption keys). Of course, we don't want to use the output for encryption but as a hash on its own.
You can try the SimpleCrypto.Net library written for exactly this purpose if you want PBKDF2. If you look at the implementation, you can see that it is actually just a thin wrapper around (you guessed it) Rfc2898DeriveBytes.
BCrypt
You can try the C# implementation named (what else) BCrypt.NET if you want to experiment with this variant.
Disclaimer: I have not used or tested any of the libraries that I have linked to... YMMV
First of all, I urge everyone to use a cryptographically verified reference algorithm included with the platform itself.
Do not use 3rd party packages and non-verified OSS components or any other code you just copy-pasted from the Internet.
For .NET use PBKDF2 and not bCrypt because there's no certified implementation of bCrypt for .NET
I don't mean any disrespect for any noble open-source devs (being one myself), but you can never be sure their website won't be hacked in 10 years and you end up getting a malware package from Nuget/npm or other package managers.
More info about verification can be found in this SO answer
Now, back to PBKDF2, here's the simple code
public static byte[] PBKDF2Hash(string input, byte[] salt)
{
// Generate the hash
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(input, salt, iterations: 5000);
return pbkdf2.GetBytes(20); //20 bytes length is 160 bits
}
If you need a string representation of the hash (not byte-array) - you can use this superfast conversion class from this answer http://stackoverflow.com/a/624379/714733
It took me forever (days it took days) to find what to actually code to get hashed passwords to work!! so I put it here for convenience.
You do need to read the documentation and theory1 theory2 and then some or you could be open to security loopholes. Security is a very big topic! Buyer Beware!
Add the NuGet Package BCrypt.Net to the solution
const int WorkFactor = 14;
var HashedPassword = BCrypt.Net.BCrypt.HashPassword(Password, WorkFactor);
You should adjust the WorkFactor to what is appropriate see discussions. Its a log2 function
"The number is log2, so every time computers double in speed, add 1 to the default number."
Then you store the hashed password in your db as passwordFromLocalDB and to test an incoming password like this:
if (BCrypt.Net.BCrypt.Verify(password, passwordFromLocalDB) == true)
Good Luck!
EDIT: As pointed out by #MikeT, the original link and code I posted is no longer considered best practice for hashing passwords to be stored in a datastore.
This article by Scott Brady illustrates how to use PasswordHasher, and also how to use the interface to develop stronger hashing implementations.
So applying the byte-level hashing function yourself is not required any more, but it you want to see how it's done this article explores the PasswordHasher<T> implementation:
Exploring the ASP.NET Core Identity PasswordHasher
A relevant piece of code from the article is shown below:
private static byte[] HashPasswordV2(string password, RandomNumberGenerator rng)
{
const KeyDerivationPrf Pbkdf2Prf = KeyDerivationPrf.HMACSHA1; // default for Rfc2898DeriveBytes
const int Pbkdf2IterCount = 1000; // default for Rfc2898DeriveBytes
const int Pbkdf2SubkeyLength = 256 / 8; // 256 bits
const int SaltSize = 128 / 8; // 128 bits
// Produce a version 2 text hash.
byte[] salt = new byte[SaltSize];
rng.GetBytes(salt);
byte[] subkey = KeyDerivation.Pbkdf2(password, salt, Pbkdf2Prf, Pbkdf2IterCount, Pbkdf2SubkeyLength);
var outputBytes = new byte[1 + SaltSize + Pbkdf2SubkeyLength];
outputBytes[0] = 0x00; // format marker
Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, Pbkdf2SubkeyLength);
return outputBytes;
}
Original answer:
Microsoft has a page up with sample code using PBKDF2 for anyone using .Net Core:
Hash passwords in ASP.NET Core
From the article:
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=
*/
Earlier this year I was looking into the same thing for creating hashes for our ASP.NET Web Forms project, I wanted to do it the same way MVC projects do it out of the box.
I stumbled upon this question => ASP.NET Identity default Password Hasher, how does it work and is it secure?
Then I found the source with the ByteArraysEqual method here => http://www.symbolsource.org/MyGet/Metadata/aspnetwebstacknightly/Project/Microsoft.AspNet.Identity.Core/2.0.0-rtm-140327/Release/Default/Microsoft.AspNet.Identity.Core/Microsoft.AspNet.Identity.Core/Crypto.cs?ImageName=Microsoft.AspNet.Identity.Core
i was interested in an answers that didn't involve any libraries.
I read this article https://crackstation.net/hashing-security.htm which links an implementation in different languages C# among them which i will link here too
https://github.com/defuse/password-hashing/blob/master/PasswordStorage.cs
interestingly it uses Rfc2898DeriveBytes as mentioned a few times here.
private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes){
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt)) {
pbkdf2.IterationCount = iterations;
return pbkdf2.GetBytes(outputBytes);
}
}
For PBKDF2, you might be able to use System.Security.Cryptography.Rfc2898DeriveBytes.
See MSDN here: http://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes.aspx
PBKDF2 uses HMACSHA1, if you would like a more modern and customisable solution you should look at this API using HMACSHA256 or 512 with key stretching just like PBKDF2
https://sourceforge.net/projects/pwdtknet/
Sample GUI included in source code demonstrated how to get a hash from a password including the creation of crypto random salt.....enjoy :)
PBKDF2
In the example in http://msdn.microsoft.com/en-us/library/system.security.cryptography.rfc2898derivebytes.aspx, when you get to the line "Rfc2898DeriveBytes k1 = new Rfc2898DeriveBytes(pwd1, salt1,
myIterations);", k1 is the hash. The reason the example is for encryption is that Rfc2898DeriveBytes was originally designed to create encryption keys.
If you do not provide a salt, Rfc2898DeriveBytes will create it's own, but I do not know whether RNGCryptoServiceProvider does a better job of being cryptographically random.
According to OWASP (https://www.owasp.org/index.php/Using_Rfc2898DeriveBytes_for_PBKDF2), the underlying use of SHA1 by Rfc2898DeriveBytes means it's only good for hashes up to 160 bits in length. If you create a longer hash, an attacker still only has to worry about the first 160 bits, but you have made password hashing/authentication more expensive for yourself with no gain.
Here's some example code for Rfc2898DeriveBytes password hashing (store the hash, salt and iterations in the DB):
public class Rfc2898PasswordEncoder
{
private int _byteLength = 160 / 8; // 160 bit hash length
public class EncodedPassword
{
public byte[] Hash { get; set; }
public byte[] Salt { get; set; }
public int Iterations { get; set; }
}
public EncodedPassword EncodePassword(string password, int iterations)
{
var populatedPassword = new EncodedPassword
{
Salt = CreateSalt(),
Iterations = iterations
};
// Add Hash
populatedPassword.Hash = CreateHash(password, populatedPassword.Salt, iterations);
return populatedPassword;
}
public bool ValidatePassword(string password, EncodedPassword encodedPassword)
{
// Create Hash
var testHash = CreateHash(password, encodedPassword.Salt, encodedPassword.Iterations);
return testHash == encodedPassword.Hash;
}
public byte[] CreateSalt()
{
var salt = new byte[_byteLength]; // Salt should be same length as hash
using (var saltGenerator = new RNGCryptoServiceProvider())
{
saltGenerator.GetBytes(salt);
}
return salt;
}
private byte[] CreateHash(string password, byte[] salt, long iterations)
{
byte[] hash;
using (var hashGenerator = new Rfc2898DeriveBytes(password, salt, (int)iterations))
{
hash = hashGenerator.GetBytes(_byteLength);
}
return hash;
}
}