簡體   English   中英

如何在 Python 中重現 System.Security.Cryptography.SHA1Managed 結果

[英]How to reproduce System.Security.Cryptography.SHA1Managed result in Python

交易如下:我正在將一個 .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. 如何使用鹽重新哈希密碼。
  3. 鹽是如何產生的? (例如,當我說 saltEnabled="true" 時,會發生什么額外的魔法?)

我需要不只是引用其他 .NET 庫的特定細節,我正在尋找發生在黑匣子中的實際操作邏輯。

謝謝!

抱歉回復晚了,但我在嘗試復制 Enterprise Library 的 Cryptography Block 中使用的 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"初始化。 如果為 true,並且您沒有提供 salt,則會為您生成一個包含 16 個隨機字節的字節數組(通過調用外部 C API)。
    • plaintext變量然后在它前面加上鹽。 例如:[鹽][明文]

這一點很重要!

  • 然后通過調用this.HashCryptographer.ComputeHash(plaintext);對鹽和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 加鹽散列值 + 鹽的 Base64 字符串表示。

公式:Base64(salt + SHA1(salt + value))

  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);

希望這在某種程度上有所幫助!

根據這個以前的線程,這應該類似於 sha1(password+salt)+salt。 SHA-1 輸出是 20 個字節,因此對於 48 個字節,這應該是 28 字節的鹽,而不是 8 字節的鹽,除非使用了某種編碼。

當您使用string CreateHash(string, string)重載時,會發生以下情況:

  1. 使用 UTF16(使用 Encoding.Unicode.GetBytes())將字符串轉換為字節。
  2. 生成一個隨機的 16 字節鹽。
  3. 鹽被附加到轉換后的字符串並散列。
  4. 鹽被附加到哈希。
  5. 使用 base64(使用 Convert.ToBase64String())將 hash+salt 轉換回字符串。

謝謝加雷斯·斯蒂芬森! 你的回答有我需要的所有答案。 我完全迷失了這個。 我需要升級一個使用這個企業庫的遺留模塊,但是編譯時有很多問題,我無法調試代碼。 保持代碼打開了無數其他問題,包括依賴項和公鑰令牌不匹配/版本。 所以我根據 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;
    }

就在我以為我必須重置所有客戶的密碼之前……我希望有一天這也能保護其他人!

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM