如何在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中的散列过程,但我没有运气。

我向你提问:

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

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

谢谢!

对于迟到的回复很抱歉,但是我尝试复制Enterprise Library的Cryptography Block中使用的SHA1哈希逻辑时遇到了类似的情况,但是使用了Java。

回答你的每个问题:

  1. 如何使用公钥?

    上面配置块中的PublicKeyToken用于标识签名的,强名称的.net程序集。 这是公钥的64位哈希,对应于用于对程序集进行签名的私钥。 注意:此密钥与您对哈希数据的实现完全没有关系。

  2. 如何使用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(盐+值))

  1. 盐是如何产生的? (例如,当我说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)重载时,会发生以下情况:

  1. 使用UTF16将字符串转换为字节(使用Encoding.Unicode.GetBytes())。
  2. 生成随机的16字节盐。
  3. 盐被附加到转换后的字符串并进行散列。
  4. salt附加到哈希。
  5. 使用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; } 

就在我认为我必须重置所有客户的密码之前…我希望有一天这也会让别人安全!