如何在Python中复现System.Security.Cryptography.SHA1Managed的结果

0 投票
5 回答
3129 浏览
提问于 2025-04-15 20:43

事情是这样的:我正在把一个 .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(密码 + 盐),但都没有成功。

我想问你几个问题:

  1. 公钥是怎么使用的?
  2. 密码是如何用盐重新哈希的?
  3. 盐是怎么生成的?(比如,当我设置 saltEnabled="true" 时,发生了什么额外的魔法?)

我需要一些具体的细节,不希望只是提到其他 .NET 库,我想了解在这个黑箱中实际发生的操作逻辑。

谢谢!

5 个回答

1

当你使用 string CreateHash(string, string) 这个版本时,会发生以下几件事:

  1. 首先,这个字符串会被转换成字节,使用的是UTF16编码(也就是用Encoding.Unicode.GetBytes()这个方法)。
  2. 接着,会生成一个随机的16字节的盐值。
  3. 然后,这个盐值会被加到刚才转换的字符串上,并进行哈希处理。
  4. 接下来,盐值会被加到哈希值上。
  5. 最后,哈希值和盐值会一起被转换回字符串,使用的是base64编码(也就是用Convert.ToBase64String()这个方法)。
1

根据之前的讨论,这个过程应该是像这样:先把密码和盐值(salt)结合在一起,然后用SHA-1算法处理,最后再加上盐值。SHA-1算法的输出是20个字节,所以如果总共需要48个字节的话,盐值应该是28个字节,而不是8个字节,除非使用了某种编码方式。

4

抱歉回复得晚了,但我刚刚遇到了一个类似的情况,我在尝试用Java实现企业库中的SHA1哈希逻辑。

下面我来回答你的每一个问题:

  1. 公钥是怎么用的?

    上面配置块中的PublicKeyToken是用来识别一个经过签名的、强名称的.NET程序集的。这是一个64位的哈希值,它对应于用来签署该程序集的私钥的公钥。注意:这个密钥和你实现数据哈希没有任何关系。

  2. 密码是如何用盐重新哈希的?

    创建带盐的哈希密码的步骤如下:

    • 调用 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))

  1. 盐是怎么生成的?(例如,当我说saltEnabled="true"时,发生了什么额外的操作?)

    这个在问题2中已经回答了,特别是调用 CryptographyUtility.GetRandomBytes(16);,它最终会调用一个C库:

[DllImport("QCall", CharSet = CharSet.Unicode)] private static extern void GetBytes(SafeProvHandle hProv, byte[] randomBytes, int count);

希望这些信息能对你有所帮助!

撰写回答