如何在Python中重现System.Security.Cryptography.SHA1Managed结果
这是交易:我正在将.NET网站迁移到Python。 我有一个使用System.Security.Cryptography.SHA1Managed实用程序密码哈希的数据库。
我正在使用以下代码在.NET中创建哈希:
string hashedPassword = Cryptographer.CreateHash("MYHasher", userInfo.Password);
MYHasher块看起来像这样:
因此,对于给定的密码,我回来并在数据库中存储一个48字节的盐渍sha1。 我假设最后8个字节是盐。 我试图通过执行sha1(salt +密码)和sha1(密码+ salt)来重现python中的散列过程,但我没有运气。
我向你提问:
- 如何使用公钥?
- 如何使用salt重新密码。
- 盐是如何产生的? (例如,当我说saltEnabled =“true”时,会发生什么额外的魔法?)
我需要不仅仅引用其他.NET库的具体细节,我正在寻找黑盒中发生的实际操作逻辑。
谢谢!
对于迟到的回复很抱歉,但是我尝试复制Enterprise Library的Cryptography Block中使用的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
变量将盐加在其前面 。 例如:[salt] [plaintext]
-
这一点非常重要!
-
然后通过调用
this.HashCryptographer.ComputeHash(plaintext);
来对SHA和plaintext
的组合进行SHA1散列this.HashCryptographer.ComputeHash(plaintext);
。 这将产生一个20字节长的数组。 -
然后,通过调用
this.AddSaltToHash(salt, ref hash);
,将盐再次添加到先前创建的20字节数组中this.AddSaltToHash(salt, ref hash);
,给你一个36字节长的数组。 -
回到堆栈最终将引导您
return Convert.ToBase64String(hash);
在CreateHash()
方法中。 这将返回所提供的SHA1盐渍散列值+ salt的Base64字符串表示forms。
公式:Base64(盐+ SHA1(盐+值))
-
盐是如何产生的? (例如,当我说saltEnabled =“true”时,会发生什么额外的魔法?)
这在问题2中被回答,特别是对
CryptographyUtility.GetRandomBytes(16);
的调用CryptographyUtility.GetRandomBytes(16);
最终调用C库:
[DllImport("QCall", CharSet = CharSet.Unicode)] private static extern void GetBytes(SafeProvHandle hProv, byte[] randomBytes, int count);
希望这在某种程度上有所帮助!
根据之前的post ,这应该是sha1(密码+盐)+盐。 SHA-1输出是20个字节,因此对于48个字节,这应该是28字节的盐,而不是8字节的盐,除非使用某种编码。
当您使用string CreateHash(string, string)
重载时,会发生以下情况:
- 使用UTF16将字符串转换为字节(使用Encoding.Unicode.GetBytes())。
- 生成随机的16字节盐。
- 盐被附加到转换后的字符串并进行散列。
- salt附加到哈希。
- 使用base64(使用Convert.ToBase64String())将hash + salt转换回字符串。
谢谢Gareth Stephenson! 你的答案有我需要的所有答案。 我完全迷失了。 我需要升级使用此企业库的旧模块,但编译时遇到很多问题我无法调试代码。 保持代码在依赖关系和公钥令牌不匹配/版本方面打开了许多其他问题。 所以我根据Gareth的回答重新编写了所需的function。 我最终找到了配置文件中使用的加密。 这可以在app.config(在我的情况下),web.config或其他配置中:
我写的代码是:
//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; }
就在我认为我必须重置所有客户的密码之前…我希望有一天这也会让别人安全!