这是交易:我正在将.NET网站迁移到Python。我有一个使用System.Security.Cryptography.SHA1Managed实用程序密码哈希的数据库。
我正在使用以下代码在.NET中创建哈希:
string hashedPassword = Cryptographer.CreateHash("MYHasher", userInfo.Password);
MYHasher区块如下所示:
<add algorithmType="System.Security.Cryptography.SHA1Managed, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=blahblahblah"
saltEnabled="true" type="Microsoft.Practices.EnterpriseLibrary.Security.Cryptography.HashAlgorithmProvider, Microsoft.Practices.EnterpriseLibrary.Security.Cryptography, Version=3.0.0.0, Culture=neutral, PublicKeyToken=daahblahdahdah"
name="MYHasher" />
因此,对于给定的密码,我会回来并在数据库中存储一个48字节的盐渍sha1。我假设最后8个字节是盐。我试图通过执行sha1(salt +密码)和sha1(密码+ salt)来重现python中的散列过程,但我没有运气。
我向你提问:
我需要不仅仅引用其他.NET库的具体细节,我正在寻找黑盒中发生的实际操作逻辑。
谢谢!
答案 0 :(得分:4)
对于迟到的回复感到很抱歉,但是在尝试复制企业库的加密块中使用的SHA1散列逻辑时,我遇到了类似的情况,但是使用了Java。
回答你的每个问题:
如何使用公钥?
上面配置块中的PublicKeyToken用于标识签名的,强名称的.net程序集。这是公钥的64位哈希,对应于用于对程序集进行签名的私钥。 注意:此密钥与您对哈希数据的实现完全没有关系。
如何使用salt重新设置密码。
使用salt创建散列密码的事件序列如下:
致电Cryptographer.CreateHash("MYHasher",value);
其中"MYHasher"
是配置块中指定的已配置System.Security.Cryptography.SHA1Managed
实例提供程序的名称,value
是要进行哈希处理的字符串。
上述方法调用了CreateHash(IHashProvider provider, string plaintext)
,其中提供了已解析的IHashProvider
。在此方法中,运行以下代码:
byte[] bytes = Encoding.Unicode.GetBytes(plaintext);
byte[] hash = provider.CreateHash(bytes);
CryptographyUtility.GetRandomBytes(bytes);
return Convert.ToBase64String(hash);
使用Unicode编码将在开头传递的value
参数(现在是plaintext
参数)转换为字节数组。
接下来,使用上面创建的字节数组调用SHA1哈希提供程序的CreateHash(bytes)
方法。在此方法中,将执行以下步骤:
this.CreateHashWithSalt(plaintext, (byte[]) null);
,其中plaintext
是一个字节数组,包含作为字符串在堆栈顶部传入的原始value
。第二个参数是salt字节数组(为null)。在此方法中,将调用以下代码:
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)
是关于如何提供所提供文本的第一条线索。在此方法中,运行以下代码:
if (!this.saltEnabled)
return;
if (salt == null)
salt = CryptographyUtility.GetRandomBytes(16);
plaintext = CryptographyUtility.CombineBytes(salt, plaintext);
this.saltEnabled
变量由配置块中的saltEnabled="true"
初始化。如果为true,并且如果您没有提供salt,则将为您生成16个随机字节的字节数组(通过调用外部C API)。plaintext
变量的盐前置。例如:[盐] [明文] 这一点非常重要!
然后通过调用plaintext
对SHA和this.HashCryptographer.ComputeHash(plaintext);
的组合进行SHA1散列。这将生成一个20字节长的数组。
然后,通过调用this.AddSaltToHash(salt, ref hash);
,将再次重新加载到先前创建的20字节数组中,以获得一个36字节长的数组。
返回堆栈最终会引导您进入return Convert.ToBase64String(hash);
方法中的CreateHash()
来电。这将返回所提供的SHA1盐渍散列值+盐的Base64字符串表示。
公式:Base64(盐+ SHA1(盐+值))
如何创造盐? (例如,当我说saltEnabled =“true”时,会发生什么额外的魔法?)
问题2回答了这个问题,特别是对最终调用C库的CryptographyUtility.GetRandomBytes(16);
的调用:
[DllImport("QCall", CharSet = CharSet.Unicode)]
private static extern void GetBytes(SafeProvHandle hProv, byte[] randomBytes, int count);
希望这在某种程度上有所帮助!
答案 1 :(得分:1)
根据this previous thread,这应该是sha1(密码+盐)+盐。 SHA-1输出是20个字节,因此对于48个字节,这应该是28字节的盐,而不是8字节的盐,除非使用某种编码。
答案 2 :(得分:1)
使用string CreateHash(string, string)
重载时,会发生以下情况:
答案 3 :(得分:0)
感谢Gareth Stephenson!您的答案包含了我需要的所有答案。我对此完全迷失了。我需要升级使用此企业库的旧模块,但是编译时有很多问题,我无法调试代码。保持代码开放带来了无数其他问题,包括依赖性和公钥令牌不匹配/版本。 因此,我根据Gareth的回答重新编写了所需的功能。我最终发现配置文件中使用了加密。可以在app.config(以我为例),web.config或其他配置中:
<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>
我写的代码是:
//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);
}
并检查密码的有效性:
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;
}
那在我以为必须重置所有客户的密码之前……我希望这一天也能保护其他人!
答案 4 :(得分:0)
感谢@Leo Muler,您的csharp代码帮助我将其转换为nodejs。
这是代码:
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('');