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

时间:2010-03-22 19:58:14

标签: c# .net python cryptography

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

我向你提问:

  1. 如何使用公钥?
  2. 如何使用salt重新密码。
  3. 盐是如何产生的? (例如,当我说saltEnabled =“true”时,会发生什么额外的魔法?)
  4. 我需要不仅仅引用其他.NET库的具体细节,我正在寻找黑盒中发生的实际操作逻辑。

    谢谢!

5 个答案:

答案 0 :(得分:4)

对于迟到的回复感到很抱歉,但是在尝试复制企业库的加密块中使用的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变量的盐前置。例如:[盐] [明文]
  3. 这一点非常重要!

    • 然后通过调用plaintext对SHA和this.HashCryptographer.ComputeHash(plaintext);的组合进行SHA1散列。这将生成一个20字节长的数组。

    • 然后,通过调用this.AddSaltToHash(salt, ref hash);,将再次重新加载到先前创建的20字节数组中,以获得一个36字节长的数组。

    • 返回堆栈最终会引导您进入return Convert.ToBase64String(hash);方法中的CreateHash()来电。这将返回所提供的SHA1盐渍散列值+盐的Base64字符串表示。

    公式:Base64(盐+ SHA1(盐+值))

    1. 如何创造盐? (例如,当我说saltEnabled =“true”时,会发生什么额外的魔法?)

      问题2回答了这个问题,特别是对最终调用C库的CryptographyUtility.GetRandomBytes(16);的调用:

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:0)

感谢Gareth Stephenson!您的答案包含了我需要的所有答案。我对此完全迷失了。我需要升级使用此企业库的旧模块,但是编译时有很多问题,我无法调试代码。保持代码开放带来了无数其他问题,包括依赖性和公钥令牌不匹配/版本。 因此,我根据Gareth的回答重新编写了所需的功能。我最终发现配置文件中使用了加密。可以在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;
    }

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

答案 4 :(得分:0)

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