When a User registers, I run the password they input through CreateHash() - see below.
I then get passed back a Dictionary<string, string> with the Hash and the Salt in it which I then store in the SQL database each as varchar(256).
When a User attempts to log in, I then retrieve the salt from the DB and pass it as well as the inputted password into GetHash() - see below
The hash that I am getting back does not match what is in the database.
What might I be doing wrong?
public class EncryptionHelper
{
public const int SALT_SIZE = 128;
public const int HASH_SIZE = 128;
public const int ITERATIONS = 100000;
// On declaring a new password for example
public static Dictionary<string, string> CreateHash(string input)
{
Dictionary<string, string> hashAndSalt = new Dictionary<string, string>();
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
byte[] salt = new byte[SALT_SIZE];
provider.GetBytes(salt);
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(input, salt, ITERATIONS);
hashAndSalt.Add("Hash", Encoding.Default.GetString(pbkdf2.GetBytes(HASH_SIZE)));
hashAndSalt.Add("Salt", Encoding.Default.GetString(salt));
return hashAndSalt;
}
// To check if Users password input is correct for example
public static string GetHash(string saltString, string passwordString)
{
byte[] salt = Encoding.ASCII.GetBytes(saltString);
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS);
return Encoding.Default.GetString(pbkdf2.GetBytes(HASH_SIZE));
}
}
I think you have two problems here.
As the documentation for Encoding.Default states, the encoding this returns can vary between computers or even on a single computer. This probably isn't your issue here, but you should be deliberate when you choose an encoding.
Most text encodings are not what I'd call "round trippable" for anything other than the text they are designed to encode.
The second option is likely your problem here. Let's assume that Encoding.UTF8 is being used.
Imagine that provider.GetBytes(salt) generates the following bytes (represented as hexadecimal):
78 73 AB 7A 4F 61 E9 3E 8A 96
We can convert it to a string and back using this code:
byte[] salt = new byte[10];
provider.GetBytes(salt);
string saltText = Encoding.UTF8.GetString(salt);
salt = Encoding.UTF8.GetBytes(saltText);
Now let's look at the output as hexadecimal:
78 73 EF BF BD 7A 4F 61 EF BF BD 3E EF BF BD EF BF BD
What's happened? Why haven't we got the same thing out as we put in?
Well, we've tried to interpret the bytes as UTF8, but some of it doesn't decode correctly into a string (because it's not UTF8-encoded text). This means that the string we get doesn't represent the binary data. We then try to convert the string back to binary data, and get a completely different result.
To solve this, you need to use an encoding intended to accurately represent binary data. There are a couple of common options:
Hexadecimal (as I used above).
Base64
Since the base64 option is built-in, let's use that.
public static Dictionary<string, string> CreateHash(string input)
{
Dictionary<string, string> hashAndSalt = new Dictionary<string, string>();
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
byte[] salt = new byte[SALT_SIZE];
provider.GetBytes(salt);
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(input, salt, ITERATIONS);
hashAndSalt.Add("Hash", Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE)));
hashAndSalt.Add("Salt", Convert.ToBase64String(salt));
return hashAndSalt;
}
// To check if Users password input is correct for example
public static string GetHash(string saltString, string passwordString)
{
byte[] salt = Convert.FromBase64String(saltString);
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS);
return Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE));
}
And now, because we're using base64 rather than string encoding that can't handle the binary data, we get the same hash!
If you want hexadecimal you can:
Use Convert.ToHexString and Convert.FromHexString, assuming you're using .NET 5 or newer.
Refer to these answers to implement methods to convert to and from hex if you're using an older version.
My recommendation would be to store these values as binary data in the database instead of strings. You don't need to convert them to string and it makes more sense to keep them as binary.
Encoding.Default.GetString() will interpret the byte array as if it already contains string data. This is not right; the array contains random bytes. Instead, you want to get the Base64-encoded version of the bytes. Using the base-64 encoded version will allow you to get back the same byte array for the salt.
I also suggest using a Tuple instead of a Dictionary, which will save a couple allocations:
public static (string salt, string hash) CreateHash(string input)
{
var provider = new RNGCryptoServiceProvider();
byte[] salt = new byte[SALT_SIZE];
provider.GetBytes(salt);
var pbkdf2 = new Rfc2898DeriveBytes(input, salt, ITERATIONS);
var hash = pbkdf2.GetBytes(HASH_SIZE);
var saltString = Convert.ToBase64String(salt);
var hashString = Convert.ToBase64String(hash);
return (saltString, hashString);
}
public static string GetHash(string saltString, string passwordString)
{
byte[] salt = Convert.FromBase64String(saltString);
var pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS);
return Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE));
}
public static bool ValidateLogin(string username, string password)
{
//made up the GetUserCredential() method
(string saltString, string hashString) cred = GetUserCredential(username);
//add code here to return false if credential lookup fails
return GetHash(cred.saltString, password) == cred.hashString;
}
See it work here:
https://dotnetfiddle.net/aiJnS2
Encoding.Default might be an ANSI encoding which is prone to losing data since it is based on ANSI code pages. I'd use Base64 as text representation of the hash.
public static string GetHash(string saltString, string passwordString)
{
var salt = Encoding.ASCII.GetBytes(saltString);
var pbkdf2 = new Rfc2898DeriveBytes(passwordString, salt, ITERATIONS);
return Convert.ToBase64String(pbkdf2.GetBytes(HASH_SIZE));
}
It's because you gave two different salts to the two Rfc2898DeriveBytes instances.
In the CreateHash(), Encoding.Default.GetString(salt) tries to encode bytes to a string. It should be avoided because it may fail or convert bytes in an irreversible way.(In the GetHash(), Encoding.ASCII.GetBytes(saltString) will be different from the salt you generated before at CreateHash().)
Use an encoding such as the base16 or base64. See this question for the first.
How do you convert a byte array to a hexadecimal string, and vice versa?.
Related
So I need to store passwords in a SQL database and it would be insecure to store them in plain text. For a variety of reasons, I chose SHA512 to hash the passwords prior to storage. I, for the life of me, can not identify how to take data from a Secure string gained from user input, and hash it using SHA512 (which also means I haven't been able to look into salting it either).
I have seen online that you call a new instance of SHA512 but that it has to be managed (?) but when I try it shows that it is obsolete. Looking further, the wise internet suggested the create method of SHA512... which is also obsolete.
Any help into how I can hash and salt a secure string would be great.
Here is a basic hash and salt method using SHA512 and a random salt prepended to the hash.
private static string HashAndSalt(string plaintext)
{
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
using var hasher = SHA512.Create();
var random = new Random();
var ciphertextBytes = hasher.ComputeHash(Encoding.UTF8.GetBytes(plaintext));
var ciphertextB64 = Convert.ToBase64String(ciphertextBytes);
var salt = new string(Enumerable.Repeat(chars, 8).Select(s => s[random.Next(s.Length)]).ToArray());
var ciphertext = salt + ':' + ciphertextB64;
return ciphertext;
}
You will need to convert your SecureString to a normal string. Using a SecureString is obsolete and should not be used anymore. As the comments mentioned you should look into a dedicated password hashing algorithm such as:
PBKDF2
Argon2
Bcrypt
Scrypt
Here is an example using Bcrypt - it is not too complicated.
First, grab this NuGet package: BCrypt.Net-Next
private static string BcryptHash(string plaintext)
{
var ciphertext = BCrypt.Net.BCrypt.HashPassword(plaintext, 12);
return ciphertext;
}
You can't however compare the hashes when a user tries to authenticate as you can with SHA512. You need to use Bcrypts 'Verify' function.
private static bool BcryptVerify(string plaintext)
{
return BCrypt.Net.BCrypt.Verify(plaintext, hashedPassword);
}
Where the plaintext is the plaintext password you receive from user input and the hashedPassword being the hash you retrieved from the database where the initial ciphertext was stored.
Hope this helps.
I think this is what you are asking?
public static string GetHash(string s)
{
using var h = SHA512.Create();
return Convert.ToBase64String(h.ComputeHash(Encoding.UTF8.GetBytes(s)));
}
Have a look here for some other inspirations of how to do this:
https://github.com/aspnet/AspNetWebStack/blob/main/src/System.Web.Helpers/Crypto.cs
If I hash the string "password" in C# using SHA256 using the below method I get this as my output:
e201065d0554652615c320c00a1d5bc8edca469d72c2790e24152d0c1e2b6189
But this website(SHA-256 produces a 256-bit (32-byte) hash value) tells me the has is:
5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
I'm clearly having a data format issue or something similar. Any ideas why this C# SHA256Managed method is returning something different? I'm sending the method "password" as the input parameter.
private static string CalculateSHA256Hash(string text)
{
UnicodeEncoding UE = new UnicodeEncoding();
byte[] hashValue;
byte[] message = UE.GetBytes(text);
SHA256Managed hashString = new SHA256Managed();
string hex = "";
hashValue = hashString.ComputeHash(message);
foreach (byte x in hashValue)
{
hex += String.Format("{0:x2}", x);
}
return hex;
}
UnicodeEncoding is UTF-16, guaranteed to inflate all your characters to two to four bytes to represent their Unicode code point (see also the Remarks section on MSDN).
Use (the static) Encoding.UTF8 instead, so most characters will fit in one byte. It depends on which system you need to mimic whether that will work for all strings.
After computing the hash using Encoding.UTF8 as stated on the answer above, I would also return a string as follows:
hex = BitConverter.ToString(hashValue).Replace("-", "");
return hex;
I am hashing a string 3C970E3BF535 using the below method:
internal static string Hash(string content)
{
System.Security.Cryptography.KeyedHashAlgorithm ha = System.Security.Cryptography.KeyedHashAlgorithm.Create();
string asmKey = "Some key";
byte[] hashkey = System.Text.Encoding.ASCII.GetBytes(asmKey);
byte[] data1 = System.Text.Encoding.ASCII.GetBytes(content);
byte[] data2 = System.Text.Encoding.ASCII.GetBytes(content.ToLower());
System.Security.Cryptography.HMACSHA512 hmac = new System.Security.Cryptography.HMACSHA512(hashkey);
byte[] hashmac1 = hmac.ComputeHash(data1);
byte[] hashmac2 = hmac.ComputeHash(data2);
string hashmacencoded1 = System.Text.Encoding.ASCII.GetString(hashmac1);
string hashmacencoded2 = System.Text.Encoding.ASCII.GetString(hashmac2);
return string.Format("{0:X}", hashmacencoded1.GetHashCode());
}
The output I get is 2BED23B1. The length is 8.
If I reverse the string, using this method:
private static string Reverse(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
And then re-hash it, I get 7 characters - 707D653 instead of 8 characters of the non-reversed hash, though the fact that it's reversed or non-reversed shouldn't matter at all, since hashing should return same length despite the input.
I have switched out the key that we actually use, so if you use the code above it would not reproduce the issue. The issue would ONLY pop up if we use our key. What is going on?
You can't use System.Text.Encoding.ASCII.GetString to represent the hash because the hash is just a (pseudo) random assortment of bytes, not a binary representation of an ASCII string.
Instead you could represent it as a base64 string.
Convert.ToBase64String(hashmac1); // ABCDEF123== for example
You also should not be testing the GetHashCode() of the result - this is a different code that is used internally in the .NET framework for key-based data structures such as Dictionary, HashSet, etc. Just output the variable, not the hash code of the variable - the variable is the hash code.
The result of string.GetHashCode is always a 32bit signed integer.
In this case, your result just happened to have a leading zero, which gets lopped off when you do string.Format("{0:X}", ... )
There are all sorts of other problems with this (why are you getting the hash code of the hash?) but that's the answer to the problem you actually asked about.
I have the below code to hash/store/retrieve data for passwords but my first unit test and it fails.
I beleive its the Encoding causing the problem because when GetBytes is called it returns byte[38], byte[36] when it should be 20 I think.
I have to convert to string as I'm storing it in a database.
Any ideas? Thanks
[Fact]
public void EncryptDecryptPasswordShouldMatch()
{
string password = "password";
string passwordKey = string.Empty;
string passwordSalt = string.Empty;
Helpers.CreatePasswordHash(password, out passwordSalt, out passwordKey);
Assert.True(Helpers.PasswordsMatch(passwordSalt, passwordKey, password));
}
public static bool PasswordsMatch(string passwordSalt, string passwordKey, string password)
{
byte[] salt = Encoding.UTF8.GetBytes(passwordSalt);
byte[] key = Encoding.UTF8.GetBytes(passwordKey);
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
byte[] newKey = deriveBytes.GetBytes(20); // derive a 20-byte key
if (!newKey.SequenceEqual(key))
return false;
}
return true;
}
public static void CreatePasswordHash(string password, out string passwordSalt, out string passwordKey)
{
// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
byte[] salt = deriveBytes.Salt;
byte[] key = deriveBytes.GetBytes(20); // derive a 20-byte key
passwordSalt = Encoding.UTF8.GetString(salt);
passwordKey = Encoding.UTF8.GetString(key);
}
}
Use Base64 to encode binary values to string, it can deal with arbitrary byte sequences. UTF-8 is for transforming between unicode text and bytes and not every valid sequence of bytes is valid for UTF-8. Use Utf-8 to turn the password(which is text) to bytes, but use Base64 for salt and hash.
Convert.ToBase64String and Convert.FromBase64String should do the trick.
Some additional notes:
Your terminology is really weird, don't call the hash key, call it hash.
I'd concatenate the hash and salt in your CreatePasswordHash function, so the caller doesn't have to bother with having two separate values.
Something like return Base64Encode(salt)+"$"+Base64Encode(hash) then use string.Split in the verification function.
It's recommended to use a constant time comparison to verify, but it seems unlikely your timing side-channel can actually be exploited.
Your iteration count is pretty low. I recommend increasing it to 10000.
Modify your code to use the Convert.FromBase64String method:
byte[] salt = Convert.FromBase64String(passwordSalt);
byte[] key = Convert.FromBase64String(passwordKey);
Modify your code to use the Convert.ToBase64String method:
passwordSalt = Convert.ToBase64String(salt);
passwordKey = Convert.ToBase64String(key);
UTF8 is not a way to turn any random bytes into a string. It is for encoding text; not just any bytes are valid UTF8 encoded values. You could use Base64 to and from conversions. Note that base64-encoded strings will take up ~4/3 times the characters of the raw bytes. Here's an example:
byte[] salt = deriveBytes.Salt;
byte[] key = deriveBytes.GetBytes(20); // derive a 20-byte key
passwordSalt = Convert.ToBase64String(salt);
passwordKey = Convert.ToBase64String(key);
And later:
byte[] salt = Convert.FromBase64String(passwordSalt);
byte[] key = Convert.FromBase64String(passwordKey);
I try to hash a string using SHA256, I'm using the following code:
using System;
using System.Security.Cryptography;
using System.Text;
public class Hash
{
public static string getHashSha256(string text)
{
byte[] bytes = Encoding.Unicode.GetBytes(text);
SHA256Managed hashstring = new SHA256Managed();
byte[] hash = hashstring.ComputeHash(bytes);
string hashString = string.Empty;
foreach (byte x in hash)
{
hashString += String.Format("{0:x2}", x);
}
return hashString;
}
}
However, this code gives significantly different results compared to my friends php, as well as online generators (such as This generator)
Does anyone know what the error is? Different bases?
Encoding.Unicode is Microsoft's misleading name for UTF-16 (a double-wide encoding, used in the Windows world for historical reasons but not used by anyone else). http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx
If you inspect your bytes array, you'll see that every second byte is 0x00 (because of the double-wide encoding).
You should be using Encoding.UTF8.GetBytes instead.
But also, you will see different results depending on whether or not you consider the terminating '\0' byte to be part of the data you're hashing. Hashing the two bytes "Hi" will give a different result from hashing the three bytes "Hi". You'll have to decide which you want to do. (Presumably you want to do whichever one your friend's PHP code is doing.)
For ASCII text, Encoding.UTF8 will definitely be suitable. If you're aiming for perfect compatibility with your friend's code, even on non-ASCII inputs, you'd better try a few test cases with non-ASCII characters such as é and 家 and see whether your results still match up. If not, you'll have to figure out what encoding your friend is really using; it might be one of the 8-bit "code pages" that used to be popular before the invention of Unicode. (Again, I think Windows is the main reason that anyone still needs to worry about "code pages".)
I also had this problem with another style of implementation but I forgot where I got it since it was 2 years ago.
static string sha256(string randomString)
{
var crypt = new SHA256Managed();
string hash = String.Empty;
byte[] crypto = crypt.ComputeHash(Encoding.ASCII.GetBytes(randomString));
foreach (byte theByte in crypto)
{
hash += theByte.ToString("x2");
}
return hash;
}
When I input something like abcdefghi2013 for some reason it gives different results and results in errors in my login module.
Then I tried modifying the code the same way as suggested by Quuxplusone and changed the encoding from ASCII to UTF8 then it finally worked!
static string sha256(string randomString)
{
var crypt = new System.Security.Cryptography.SHA256Managed();
var hash = new System.Text.StringBuilder();
byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
foreach (byte theByte in crypto)
{
hash.Append(theByte.ToString("x2"));
}
return hash.ToString();
}
Thanks again Quuxplusone for the wonderful and detailed answer! :)
public static string ComputeSHA256Hash(string text)
{
using (var sha256 = new SHA256Managed())
{
return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
}
}
The reason why you get different results is because you don't use the same string encoding. The link you put for the on-line web site that computes SHA256 uses UTF8 Encoding, while in your example you used Unicode Encoding. They are two different encodings, so you don't get the same result. With the example above you get the same SHA256 hash of the linked web site. You need to use the same encoding also in PHP.
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
public string EncryptPassword(string password, string saltorusername)
{
using (var sha256 = SHA256.Create())
{
var saltedPassword = string.Format("{0}{1}", salt, password);
byte[] saltedPasswordAsBytes = Encoding.UTF8.GetBytes(saltedPassword);
return Convert.ToBase64String(sha256.ComputeHash(saltedPasswordAsBytes));
}
}
New .NET 5+ solution:
If you're using .NET 5 or above, you can use the new Convert.ToHexString method to convert the hash byte array into a hexadecimal string; eliminating the hassle of using string builders and so on.
The following method also uses the using block so that the SHA256 instance gets disposed.
It also turns the password (which is passed in as a string) into a byte array using UTF-8 encoding, which was recommended by the accepted answer. Furthermore, we're also using the new SHA256 class as opposed to the old (now obselete) SHA256Managed class.
public string QuickHash(string secret)
{
using var sha256 = SHA256.Create();
var secretBytes = Encoding.UTF8.GetBytes(secret);
var secretHash = sha256.ComputeHash(secretBytes);
return Convert.ToHexString(secretHash);
}
Note: You should NOT use this method for hashing user passwords. General-purpose hashing functions such as SHA-256 aren't suited for use for passwords anymore, even if you add salts. This is useful for hashing strings that you know have high entropy, such as long randomly generated session tokens and whatnot. For storing passwords, you must look into slower hashing functions that were specifically designed for this purpose, such as Bcrypt, Scrypt, or PBKDF2 (the latter is available natively in .NET — see this)
The shortest and fastest way ever. Only 1 line!
public static string StringSha256Hash(string text) =>
string.IsNullOrEmpty(text) ? string.Empty : BitConverter.ToString(new System.Security.Cryptography.SHA256Managed().ComputeHash(System.Text.Encoding.UTF8.GetBytes(text))).Replace("-", string.Empty);
In the PHP version you can send 'true' in the last parameter, but the default is 'false'. The following algorithm is equivalent to the default PHP's hash function when passing 'sha256' as the first parameter:
public static string GetSha256FromString(string strData)
{
var message = Encoding.ASCII.GetBytes(strData);
SHA256Managed hashString = new SHA256Managed();
string hex = "";
var hashValue = hashString.ComputeHash(message);
foreach (byte x in hashValue)
{
hex += String.Format("{0:x2}", x);
}
return hex;
}
I was looking and testing theses answers, and Visual Studio showed me that SHA256Managed is now Obsolete (here)
So, I used the SHA256 class instead:
Encoding enc = Encoding.UTF8;
var hashBuilder = new StringBuilder();
using var hash = SHA256.Create();
byte[] result = hash.ComputeHash(enc.GetBytes(yourStringToHash));
foreach (var b in result)
hashBuilder.Append(b.ToString("x2"));
string result = hashBuilder.ToString();
This work for me in .NET Core 3.1.
But not in .NET 5 preview 7.
using System;
using System.Security.Cryptography;
using System.Text;
namespace PortalAplicaciones.Shared.Models
{
public class Encriptar
{
public static string EncriptaPassWord(string Password)
{
try
{
SHA256Managed hasher = new SHA256Managed();
byte[] pwdBytes = new UTF8Encoding().GetBytes(Password);
byte[] keyBytes = hasher.ComputeHash(pwdBytes);
hasher.Dispose();
return Convert.ToBase64String(keyBytes);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
}