Hash
奇怪的密碼散列
我知道 PBKDF2、bcrypt、scrypt 等,雖然我不是 crypt 專業人士,但我想我理解為什麼它們比“hash(pass+salt)”更好。
但是我的一位同事對他聲稱很好的以下密碼雜湊函式感到驚訝,因為
- CPU 速度很慢(在 i5 中大約需要 2-3 秒)
- 在 GPU 上它會更慢,因為需要隨機訪問大量記憶體(為什麼?GPU 有很多千兆字節)
- 它不能定時,任何基於時間的攻擊都會失敗,即使底層雜湊函式很容易受到攻擊(至少對於 SHA512,雜湊計算時間不是常數嗎?)。
- 由於虛假操作,無法通過音頻攻擊破解(那是什麼?)
因此,雖然我一般不喜歡密碼學中的自行車,但論點聽起來很合理,我只想知道自己的真相,首先並主張將這段程式碼重寫為某種標準。
編輯
所以這裡有一些具體的問題,正如@B-Con 在評論中所建議的那樣:
- 為什麼這個(需要大量記憶體)散列會變慢 GPU?
- SHA512和RIPEMD160計算時間不是常數嗎?
- 與真實數據並行的虛假數據處理真的有助於抵禦音頻攻擊嗎?
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。甚至在查看內部循環之前,還有許多其他方法可以找到衝突。