PBKDF2 為登錄密鑰,scrypt 為加密密鑰
我需要從單個密碼客戶端派生兩個密鑰。一個用於瀏覽器內登錄,另一個用於加密。
以下安全嗎?
login key = pbkdf2(password, salt1, lowN) encryption key = scrypt(password, salt2, highN)
解釋:
- 伺服器不應該知道普通密碼
salt1
登錄時從伺服器檢索- 使用者使用 登錄
login key
,下載數據 +salt2
,然後使用encryption key
客戶端解密。- 我很想只使用 scrypt(更強),但我發現的唯一經過審查的密鑰派生 JS 庫是 SJCL 的 pbkdf2。另外,我需要使用者能夠從移動設備登錄網站。
伺服器不應該知道普通密碼
如果使用者在瀏覽器的網頁中輸入密碼,則託管該網頁的伺服器始終有機會查看輸入的密碼。沒有加密機制可以防止這種情況(此時)。
為了解決這個限制,您需要建構一個可下載的應用程序(例如,您可以使用NW.js和Cordova將 HTML/JS/CSS 打包到桌面/移動應用程序中)。如果您不這樣做,那麼嘗試從伺服器隱藏密碼基本上沒有意義。 更多關於這篇文章的內容。
如果你已經這樣做了(同樣,如果你沒有繼續的話,沒有意義),如果可能的話,我建議不要使用 PBKDF2 或 scrypt 的 JS 實現。任何一個的 JS 實現都會更慢,並迫使您使用較低的迭代次數(因為使用者只會等待很長時間)。攻擊者將可以訪問高度優化的 C 實現,因此這將為您的攻擊者帶來顯著優勢。請注意,大多數語言都有用於 PBKDF2 或 scrypt 的 C 實現的包裝庫(例如node-scrypt)。
要派生密鑰,我建議在客戶端上採用以下方法:
- 使用 scrypt 派生一個 256 位主密鑰(也可以接受 PBKDF2)。使用盡可能高的安全參數,但請記住,移動設備可能會遇到困難。我建議為每個使用者使用獨特的鹽。
- 使用HKDF-SHA256派生登錄密鑰。HKDF 允許您安全地從主密鑰派生密鑰,這樣就無法從派生密鑰中確定主密鑰(或其他密鑰)。在
context
orinfo
參數中,使用字元串"server-login-key"
。128 位的輸出應該足夠了。- 再次使用 HKDF 導出加密密鑰。確保從主密鑰而不是登錄密鑰派生加密密鑰。在
context
/info
參數中,使用字元串"client-encryption-key"
。生成一個足夠長的密鑰以匹配您計劃使用的任何加密算法(例如 AES256 的 256 位)。在伺服器上儲存登錄密鑰時,請務必將它們視為密碼(僅儲存加鹽雜湊)。您可以在這裡再次使用 PBKDF2/bcrypt/scrypt,但這並不重要,因為密鑰已經被拉伸。額外的拉伸是一件好事,但如果伺服器實際上是惡意的,這將無濟於事。
這與您的設計之間的一個重要區別是它只呼叫 PBKDF2/scrypt 一次,而不是兩次。這允許您將安全參數加倍(即,一次呼叫具有 2N 次迭代,而不是兩次呼叫,每次呼叫具有 N 次迭代)。
如果使用者沒有強密碼,那麼伺服器將能夠根據登錄密鑰破解密碼(字典攻擊),所以也要慎重考慮。
注意:在這種情況下,HKDF 的“提取”步驟是可選的,因為 PBKDF2 和 scrypt 已經輸出了加密強密鑰,但它不會受到傷害。