【问题标题】:How to reproduce System.Security.Cryptography.SHA1Managed result in Python如何在 Python 中重现 System.Security.Cryptography.SHA1Managed 结果
【发布时间】:2010-03-22 19:58:13
【问题描述】:

这是交易:我正在将一个 .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 + password) 和 sha1(password + salt) 在 python 中重现散列过程,但我没有运气。

我的问题:

  1. 如何使用公钥?
  2. 如何使用 salt 重新散列密码。
  3. 盐是如何产生的? (例如,当我说 saltEnabled="true" 时,会发生什么额外的魔法?)

我需要不只是引用其他 .NET 库的具体细节,我正在寻找黑盒中发生的实际操作逻辑。

谢谢!

【问题讨论】:

    标签: c# .net python cryptography


    【解决方案1】:

    很抱歉回复晚了,但我在尝试复制企业库的加密块中使用的 SHA1 哈希逻辑时遇到了类似的情况,但使用的是 Java。

    回答您的每个问题:

    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。第二个参数是盐字节数组(为空)。在该方法内部,调用了以下代码:

      
      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。例如:[盐][明文]

    这一点非常重要!

    • 然后通过调用 this.HashCryptographer.ComputeHash(plaintext); 对 salt 和 plaintext 的组合进行 SHA1 哈希处理。这将产生一个 20 字节长的数组。

    • 然后,通过调用this.AddSaltToHash(salt, ref hash);,将盐再次添加到之前创建的 20 字节数组中,为您提供一个 36 字节长的数组。

      李>
    • 返回堆栈最终将导致您在 CreateHash() 方法中调用 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);

    希望这在某种程度上有所帮助!

    【讨论】:

      【解决方案2】:

      根据this previous thread,这应该类似于 sha1(password+salt)+salt。 SHA-1 输出是 20 字节,因此对于 48 字节,这应该是 28 字节的盐,而不是 8 字节的盐,除非使用了某种编码。

      【讨论】:

        【解决方案3】:

        当您使用string CreateHash(string, string) 重载时,会发生以下情况:

        1. 使用 UTF16 将字符串转换为字节(使用 Encoding.Unicode.GetBytes())。
        2. 会生成一个随机的 16 字节盐。
        3. 盐被附加到转换后的字符串并进行哈希处理。
        4. 盐被附加到哈希中。
        5. 使用 base64 将 hash+salt 转换回字符串(使用 Convert.ToBase64String())。

        【讨论】:

          【解决方案4】:

          感谢加雷斯·斯蒂芬森!你的答案有我需要的所有答案。我完全迷失了这个。我需要升级一个使用这个企业库的遗留模块,但是编译时出现了很多问题,我无法调试代码。保留代码会带来无数其他与依赖项和公钥令牌不匹配/版本有关的问题。 所以我根据加雷斯的回答重新编写了所需的功能。我最终找到了配置文件中使用的加密。这可以在 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;
              }
          

          就在我认为我必须重置所有客户的密码之前......我希望有一天这也能保护其他人!

          【讨论】:

            【解决方案5】:

            感谢@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('');
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-11-10
              • 2016-11-12
              • 2015-12-01
              • 1970-01-01
              • 1970-01-01
              • 2018-03-31
              • 2018-05-06
              相关资源
              最近更新 更多