bcrypt - 對所選鹽和工作因子的抗碰撞性?
會不會很難找到
(cost, salt, input, password0, password1)
這樣的bcrypt(cost, salt, password0, input) = bcrypt(workfactor, salt, password1, input)
和
password0 != password1
哪裡
cost
不算小?(如果不是,那麼這個答案中描述的 bcrypt 與 PAKE 的組合將是不安全的。)
原始問題的答案是:是
workfactor
的,要展示,salt
,password0
,password1
這樣是非常困難的bcrypt(workfactor, salt, password0) = bcrypt(workfactor, salt, password1)
;但即使這是可行的,也沒什麼大不了的,因為在正常使用中,只有在某些協議中,對手才知道至少一個密碼是未知的,在這種協議中,它可以允許一次嘗試排除兩個密碼。至於現在措辭的問題:不,展示
workfactor
,salt
,password0
,不會太困難;但這只是因為它具有延展性,而不是設置為! 在任何合理的使用中, ,被建構為三個不同的64 位常量(或至少值在問題的程度上不可延展)。password1``input``bcrypt(workfactor, salt, password0, input) = bcrypt(workfactor, salt, password1, input)``input``"OrpheanBeholderScryDoubt"``bcrypt``input
bcrypt的原始參考資料如下:
bcrypt(cost, salt, pwd) state := EksBlowshSetup(cost, salt, key) ctext := "OrpheanBeholderScryDoubt" repeat (64) ctext := EncryptECB (state, ctext) return Concatenate(cost, salt, ctext)
周圍的文字表明
pwd
輸入映射到key
(具體如何未指定),並且 24 個字元"OrpheanBeholderScryDoubt"
映射到三個 64 位塊,構成ctext
. 可以預見的是,實現在從pwd
到的映射上有所不同key
,包括以引入嚴重弱點的方式。也許 的初始值ctext
也有所不同。此外,Concatenate
輸出的格式如何變化(包括努力對其餘部分中使用的變化進行編碼)。如果Blowfish是一個很好的密碼,那麼 Blowfish 的 64 次迭代也是一個很好的密碼(在某些指標上更好,在循環結構上可能稍微差一些),因此對於任何三個不同的常量 64 位明文,每個 Blowfish 64的密文應該是三個與隨機無法區分的值,除了不同。通過 Blowfish 和 bcrypt 的設計,發現與隨機的碰撞
salt
並且key
應該預期需要超過 $ 2^{96} $ 的評估EksBlowshSetup
,因此 $ 2^{97+\mathtt{cost}} $ExpandKey
操作,這是不可行的。但是無論出於何種原因,當我閱讀時,bcrypt 的維基百科條目現在指出 bcrypt從給定的輸入計算雜湊值如下:
bcrypt(cost, salt, key, input) state := EksBlowfishSetup(cost, salt, key) ctext := input repeat (64) ctext := EncryptECB(state, ctext) //encrypt using standard Blowfish in ECB mode return Concatenate(cost, salt, ctext)
請注意第四個參數
input
以及密碼是否輸入key
(原始意圖)或的不確定性input
。在下文中,我假設這個 4 參數定義,password
在問題中映射到key
.關鍵是問題的目前陳述(不是最初預期的和可能通常的河豚設置)留下了 的選擇
input
,包括重複三次的 64 位值,這使得碰撞搜尋可行:
設置
cost
為可能的最低值;設置
salt
為一些短的可接受常數;
j
從_ $ 0 $ 到 $ 2^{20}-1 $ ,計算並儲存state[j] := EksBlowfishSetup(cost, salt, key)
對應key
於password
獲得的j
轉換為可接受password
的 5 個十六進制數字和適當後綴的位置;這將需要合理的 4 GiB RAM 和可承受的 CPU 時間。重複
i
增加,從 $ 0 $ :
從
j
0 到 $ 2^{20}-1 $ :
設置
ctext
為i
轉換為 64 位位串;重複 64 次:
- 設置
ctext := EncryptECB(state[j], ctext)
;加密在單個塊上;如果
ctext
是之前獲得的相同i
和更早的j
,password0
則從之前的 j 導出password1
,從目前的導出j
,並設置input
為由 3 次i
轉換為 64 位位串形成的 192 位位串;我們發現了碰撞!否則,請記住一些容易搜尋的表中
ctext
的對應j
(這將只需要以前使用的 RAM 的一小部分);沒有運氣
i
,清空桌子,繼續下一步i
。對於每個
i
測試,我們正在尋找之間的碰撞 $ 2^{20} $ 類似隨機的 64 位值,並且機率約為 $ 2^{20+20-1-64}=2^{-25} $ . 因此,我們預計在大約 $ 2^{25} $ 循環i
,即 $ 2^{45} $ 循環i
和j
,即 $ 2^{51} $ 加密,這是相當可觀但可行的。兩倍的記憶體使機器的效率幾乎提高了一倍,並且在某種程度上可以用硬碟或SSD代替用於儲存的 RAM 部分,但state[j]
有一些小的複雜性。此外,這可以簡單地分佈,每台機器使用盡可能多的可用記憶體。更新:我檢查了 bcrypt 的兩個實現。
jBCrypt符合原始 bcrypt 定義,但有兩個問題:因為它是用 Java 編寫的(至少在最常見的實踐中沒有完全編譯,並且缺少用於無符號 8 位變數的內置習語),因此速度可能比原生的東西要差得多,在特定的上下文中意味著不那麼安全;此外,至少在那個版本中,31 的成本是可以接受的,但會退化到比最低成本 4 更低的安全性,因為 1<<31 在 Java 中是負數。哦等等,我不是第一個踩到它的,這裡有一個更正的版本。
在 PHP 中,公認的做法是將 bcrypt 密碼散列委託給
crypt
(參見這個Bcrypt.php
或那個高度讚揚的答案)的本機實現,這是解決速度問題的好方法。有人告訴我crypt
去crypt.c
那時crypt_blowfish.c
。我現在已經找到"OrpheanBeholderScryDoubt"
了,並且引用它的程式碼是按照原始 bcrypt 定義的。速度可能相當不錯。我喜歡在執行時使用快速已知答案測試檢查程式碼的想法,這也很有可能覆蓋敏感值;以及減少其他洩漏源的一些適度但可能有用的嘗試。