如何在Python中复现System.Security.Cryptography.SHA1Managed的结果
事情是这样的:我正在把一个 .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 字节是盐。我尝试在 Python 中重现这个哈希过程,方法是用 sha1(盐 + 密码) 和 sha1(密码 + 盐),但都没有成功。
我想问你几个问题:
- 公钥是怎么使用的?
- 密码是如何用盐重新哈希的?
- 盐是怎么生成的?(比如,当我设置 saltEnabled="true" 时,发生了什么额外的魔法?)
我需要一些具体的细节,不希望只是提到其他 .NET 库,我想了解在这个黑箱中实际发生的操作逻辑。
谢谢!
5 个回答
当你使用 string CreateHash(string, string)
这个版本时,会发生以下几件事:
- 首先,这个字符串会被转换成字节,使用的是UTF16编码(也就是用Encoding.Unicode.GetBytes()这个方法)。
- 接着,会生成一个随机的16字节的盐值。
- 然后,这个盐值会被加到刚才转换的字符串上,并进行哈希处理。
- 接下来,盐值会被加到哈希值上。
- 最后,哈希值和盐值会一起被转换回字符串,使用的是base64编码(也就是用Convert.ToBase64String()这个方法)。
根据之前的讨论,这个过程应该是像这样:先把密码和盐值(salt)结合在一起,然后用SHA-1算法处理,最后再加上盐值。SHA-1算法的输出是20个字节,所以如果总共需要48个字节的话,盐值应该是28个字节,而不是8个字节,除非使用了某种编码方式。
抱歉回复得晚了,但我刚刚遇到了一个类似的情况,我在尝试用Java实现企业库中的SHA1哈希逻辑。
下面我来回答你的每一个问题:
公钥是怎么用的?
上面配置块中的PublicKeyToken是用来识别一个经过签名的、强名称的.NET程序集的。这是一个64位的哈希值,它对应于用来签署该程序集的私钥的公钥。注意:这个密钥和你实现数据哈希没有任何关系。
密码是如何用盐重新哈希的?
创建带盐的哈希密码的步骤如下:
调用
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);
传入的
value
参数(现在是plaintext
参数)会使用Unicode编码转换成一个字节数组。接下来,SHA1哈希提供者的
CreateHash(bytes)
方法会被调用,传入上面创建的字节数组。在这个方法内部,会发生以下步骤:调用
this.CreateHashWithSalt(plaintext, (byte[]) null);
,其中plaintext
是一个包含原始value
的字节数组,第二个参数是盐的字节数组(这里是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"
初始化的。如果为真,并且你没有提供盐,那么会为你生成一个包含16个随机字节的字节数组(通过调用外部C API)。然后,盐会被添加到
plaintext
的前面。例如:[salt][plaintext]
这点非常重要!
盐和
plaintext
的组合会通过调用this.HashCryptographer.ComputeHash(plaintext);
进行SHA1哈希。这会生成一个20字节长的数组。然后,盐会再次添加到之前生成的20字节数组前面,通过调用
this.AddSaltToHash(salt, ref hash);
,最终得到一个36字节长的数组。回到上面的调用链,最终会到达
return Convert.ToBase64String(hash);
这个调用,这会返回SHA1加盐哈希值加盐的Base64字符串表示。
公式:Base64(salt + SHA1(salt + value))
盐是怎么生成的?(例如,当我说saltEnabled="true"时,发生了什么额外的操作?)
这个在问题2中已经回答了,特别是调用
CryptographyUtility.GetRandomBytes(16);
,它最终会调用一个C库:
[DllImport("QCall", CharSet = CharSet.Unicode)]
private static extern void GetBytes(SafeProvHandle hProv, byte[] randomBytes, int count);
希望这些信息能对你有所帮助!