Hash

奇怪的密碼散列

  • February 25, 2014

我知道 PBKDF2、bcrypt、scrypt 等,雖然我不是 crypt 專業人士,但我想我理解為什麼它們比“hash(pass+salt)”更好。

但是我的一位同事對他聲稱很好的以下密碼雜湊函式感到驚訝,因為

  1. CPU 速度很慢(在 i5 中大約需要 2-3 秒)
  2. 在 GPU 上它會更慢,因為需要隨機訪問大量記憶體(為什麼?GPU 有很多千兆字節)
  3. 它不能定時,任何基於時間的攻擊都會失敗,即使底層雜湊函式很容易受到攻擊(至少對於 SHA512,雜湊計算時間不是常數嗎?)。
  4. 由於虛假操作,無法通過音頻攻擊破解(那是什麼?)

因此,雖然我一般不喜歡密碼學中的自行車,但論點聽起來很合理,我只想知道自己的真相,首先並主張將這段程式碼重寫為某種標準。

編輯

所以這裡有一些具體的問題,正如@B-Con 在評論中所建議的那樣:

  1. 為什麼這個(需要大量記憶體)散列會變慢 GPU?
  2. SHA512和RIPEMD160計算時間不是常數嗎?
  3. 與真實數據並行的虛假數據處理真的有助於抵禦音頻攻擊嗎?
private static readonly RNGCryptoServiceProvider _random = new RNGCryptoServiceProvider();
private const int BlockCount = 1024;
private const int IterationCount = 65536;

private static byte[] GetHashInternal(byte[] data, byte[] salt)
{
   var sha512 = new SHA512Managed();
   var ripemd = new RIPEMD160Managed();
   var hashLength = (sha512.HashSize / 8) + (ripemd.HashSize / 8);
   var realHash = new byte[BlockCount * hashLength];
   var fakeHash = new byte[BlockCount * hashLength];
   var realData = new byte[hashLength];
   var realSalt = new byte[hashLength];
   var fakeData = new byte[hashLength];
   var fakeSalt = new byte[hashLength];

   for (var index = 0; index < data.Length; index++)
   {
       realData[index % (hashLength - 1)] ^= data[index];
   }

   for (var index = 0; index < salt.Length; index++)
   {
       realSalt[index % (hashLength - 1)] ^= salt[index];
   }

   _random.GetBytes(fakeData);
   _random.GetBytes(fakeSalt);

   var length1 = sha512.HashSize / 8;
   var length2 = ripemd.HashSize / 8;
   var offset = 0;

   for (var step = 0; step < IterationCount; step++)
   {
       for (var index = 0; index < hashLength; index++)
       {
           realHash[(offset + index) % realHash.Length] ^= realData[index];
       }

       for (var index = 0; index < hashLength; index++)
       {
           fakeHash[(offset + index) % realHash.Length] ^= fakeData[index];
       }

       Array.Copy(sha512.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + (hashLength / 2)) % (realHash.Length - length1), length1);
       Array.Copy(sha512.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + (hashLength / 2)) % (fakeHash.Length - length1), length1);
       Array.Copy(ripemd.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + length1 + (hashLength / 2)) % (realHash.Length - length2), length2);
       Array.Copy(ripemd.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + length1 + (hashLength / 2)) % (fakeHash.Length - length2), length2);

       for (var index = 0; index < hashLength; index++)
       {
           realHash[(offset + index) % realHash.Length] ^= realSalt[index];
       }

       for (var index = 0; index < hashLength; index++)
       {
           fakeHash[(offset + index) % realHash.Length] ^= fakeSalt[index];
       }

       Array.Copy(ripemd.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + length1 + (hashLength / 2)) % (realHash.Length - length2), length2);
       Array.Copy(ripemd.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + length1 + (hashLength / 2)) % (fakeHash.Length - length2), length2);
       Array.Copy(sha512.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + (hashLength / 2)) % (realHash.Length - length1), length1);
       Array.Copy(sha512.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + (hashLength / 2)) % (fakeHash.Length - length1), length1);

       offset = (offset + 13) % (realHash.Length - hashLength);
   }

   return sha512.ComputeHash(realHash);
}

密碼儲存的威脅模型是伺服器入侵,攻擊者可以訪問數據庫和伺服器程式碼。然後,攻擊者可以執行程式碼來測試候選密碼,可能進行修改、移植到更快的平台等。

攻擊者不會費心計算假雜湊和假鹽。所以這個方案對於真實伺服器的速度是攻擊者的兩倍。

for (var index = 0; index < data.Length; index++)
{
   realData[index % (hashLength - 1)] ^= data[index];
}

這部分非常有趣,打破了單向雜湊函式的設計者精心設計的許多東西。

realData數組初始化為零,如果數據長度小於,則在循環結束時,並非所有數組hashLength都將被實際數據填充。任何僅在尾隨零數量上不同的輸入對都將散列到相同的事物。(當然,可能無法輸入\x00有效的密碼字元,但仍然如此)。

然後,我認為有一個一次性錯誤,它應該只是realData[index % hashLength]. 按原樣,realData[hashlength - 1]永遠不會設置。

此外,較長的字元串會發生衝突,只要找到對同一事物進行異或的字節對就足夠了。對於最簡單的範例,相同重複字元的 2 * hashLength 的字元串將與另一個重複字元的 2 * hashLength 的任何其他字元串散列到相同的事物。當索引循環結束時,字元將與自身進行異或運算,產生 0。甚至在查看內部循環之前,還有許多其他方法可以找到衝突。

引用自:https://crypto.stackexchange.com/questions/14685