Here\'s the deal: I\'m moving a .NET website to Python. I have a database with passwords hashed using the System.Security.Cryptography.SHA1Managed utility.
I\'m cre
Thanks Gareth Stephenson! your answer had all the answers I needed. I was getting completely lost with this. I needed to upgrade a legacy module that was using this enterprise library, but there were so many problems with compiling I couldn't debug the code. Keeping the code opened a zillion other problems with dependencies and public key token mismatches / versions. So I re-wrote the functions needed based on Gareth's answer. I eventually found the encryption used in the config file. This can be in the app.config (in my case), web.config or other config somewhere:
<securityCryptographyConfiguration>
<hashProviders>
<add algorithmType="System.Security.Cryptography.SHA1Managed, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
saltEnabled="true" type="Microsoft.Practices.EnterpriseLibrary.Security.Cryptography.HashAlgorithmProvider, Microsoft.Practices.EnterpriseLibrary.Security.Cryptography, Version=2.0.0.0, Culture=neutral, PublicKeyToken=06300324c959bce8"
name="ABC" />
</hashProviders>
The code I wrote is:
//Because of the random salt added, each time you hash a password it will create a new result.
public static string GetHashedValue(string password)
{
//this will create a new hash?
//Hashed Password Formula: Base64(salt + Sha1(salt + value))
var crypto = new SHA1CryptoServiceProvider();
byte[] saltBytes = new byte[16];
RandomNumberGenerator.Create().GetBytes(saltBytes);
byte[] checkPasswordBytes = Encoding.Unicode.GetBytes(password);
byte[] tempResult = crypto.ComputeHash(saltBytes.Concat(checkPasswordBytes).ToArray()); //ComputeHash(salt + value)
byte[] resultBytes = saltBytes.Concat(tempResult).ToArray(); //salt + ComputeHash(salt + value)
return Convert.ToBase64String(resultBytes);
}
and to check the validity of a password:
public static bool IsPasswordValid(string passwordToCheck, string savedPassword)
{
bool retVal = false;
var crypto = new SHA1CryptoServiceProvider();
//get the salt, which is part of the saved password. These are the first 16 bytes.
byte[] storedPasswordBytes = Convert.FromBase64String(savedPassword);
byte[] saltBytes = new byte[16];
Array.Copy(storedPasswordBytes, saltBytes, 16);
//hash the password that you want to check with the same salt and the same algoritm:
byte[] checkPasswordBytes = Encoding.Unicode.GetBytes(passwordToCheck);
byte[] tempResult = crypto.ComputeHash(saltBytes.Concat(checkPasswordBytes).ToArray()); //ComputeHash(salt + value)
byte[] resultBytes = saltBytes.Concat(tempResult).ToArray(); //salt + ComputeHash(salt + value)
string resultString = Convert.ToBase64String(resultBytes);
if (savedPassword == resultString)
{
retVal = true;
}
return retVal;
}
And that just before I thought I would have to reset all my customers' passwords... I hope this will safe someone else as well one day!
According to this previous thread, this should be something like sha1(password+salt)+salt. SHA-1 output is twenty bytes, so for 48 bytes this should be a 28-byte salt, not an 8-byte salt, unless some sort of encoding was used.
Sorry for the late reply, but I've just come across a similar situation while trying to replicate the SHA1 hashing logic used in the Enterprise Library's Cryptography Block, but with using Java.
To answer each of your questions:
How are the public keys being used?
The PublicKeyToken in the configuration block above is used to identify a signed, strong-named .net assembly. This is a 64-bit hash of the public key that corresponds to the private key used to sign the assembly. NOTE: This key has absolutely no bearing on your implementation to hash data.
How is the password rehashed using the salt.
The sequence of events to create the hashed password with the salt is as follows:
Call Cryptographer.CreateHash("MYHasher",value);
where "MYHasher"
is the name of the configured System.Security.Cryptography.SHA1Managed
instance provider specified in your configuration block, and value
is the string to be hashed.
The above method makes a call to CreateHash(IHashProvider provider, string plaintext)
, where a resolved IHashProvider
is supplied. Inside this method, the following code is run:
byte[] bytes = Encoding.Unicode.GetBytes(plaintext);
byte[] hash = provider.CreateHash(bytes);
CryptographyUtility.GetRandomBytes(bytes);
return Convert.ToBase64String(hash);
The value
argument that was passed right at the beginning (which is now the plaintext
argument) is converted into a byte array, using Unicode encoding.
Next, the SHA1 hash provider's CreateHash(bytes)
method is called with the byte array created above. Inside this method, the following steps occur:
this.CreateHashWithSalt(plaintext, (byte[]) null);
is called, where plaintext
is a byte array containing the original value
passed in at the top of the stack as a string. The second argument is the salt byte array (which is null). Inside this method, the following code is called:
this.AddSaltToPlainText(ref salt, ref plaintext);
byte[] hash = this.HashCryptographer.ComputeHash(plaintext);
this.AddSaltToHash(salt, ref hash);
return hash;
this.AddSaltToPlainText(ref salt, ref plaintext)
is the first clue as to how the supplied text is salted. Inside this method, the following code runs:
if (!this.saltEnabled)
return;
if (salt == null)
salt = CryptographyUtility.GetRandomBytes(16);
plaintext = CryptographyUtility.CombineBytes(salt, plaintext);
this.saltEnabled
variable is initialised by the saltEnabled="true"
in your configuration block. If true, and if you haven't supplied a salt, a byte array of 16 random bytes will be generated for you (via calling an external C API).plaintext
variable then has the salt prepended to it. e.g.: [salt][plaintext]This is very important to note!
The combination of the salt and plaintext
are then SHA1-hashed by calling this.HashCryptographer.ComputeHash(plaintext);
. This will produce a 20 byte long array.
Then, the salt is prepended again to the 20 byte array created previously, via the call this.AddSaltToHash(salt, ref hash);
, to give you a 36 byte long array.
Going back up the stack will eventually lead you to the call return Convert.ToBase64String(hash);
inside the CreateHash()
method. This will return the Base64 string representation of the SHA1 salted hashed value + salt that was supplied.
Formula: Base64(salt + SHA1(salt + value))
How is the salt created? (e.g., When I say saltEnabled="true", what extra magic happens?)
This was answered in question 2, specifically the call to CryptographyUtility.GetRandomBytes(16);
which eventually calls a C library:
[DllImport("QCall", CharSet = CharSet.Unicode)]
private static extern void GetBytes(SafeProvHandle hProv, byte[] randomBytes, int count);
Hope this helps in some way!
Thanks @Leo Muler , your csharp code helped me a lot to translate it into nodejs.
Here is the code :
const saltLength = 16;
const cryptedPwd = 'm2gFufL1WYJEcjdgnu4Eo0qXHM8+whC75AMnYxCS+uRbiS4OBy5+4TKNQbiSJyTG';
const pwd = 'myPassword';
let binaryPwd = Buffer.from(cryptedPwd, 'base64');
let salt = binaryPwd.slice(0, saltLength);
let saltBuffer = [...salt];
let bytePwd = Buffer.from(pwd, 'utf16le');
let pwdBuffer = [...bytePwd];
let saltAndPwd = saltBuffer.concat(pwdBuffer);
let saltAndPwdBinary = Buffer.from(saltAndPwd).toString('utf16le');
let cryptedBuffer = Array.from(crypto.createHash('sha256').update(saltAndPwdBinary, 'utf16le').digest());
let concatCryptedBuffer = saltBuffer.concat(cryptedBuffer);
let cryptedString = Buffer.from(concatCryptedBuffer).toString('base64');
console.log('cryptedString : ' + cryptedString);
console.log('same : ' + (cryptedString == cryptedPwd));
console.log('');
When you use the string CreateHash(string, string)
overload, the following occurs: