我可以使用 PBKDF2 作為流密碼嗎?
不幸的是,我必須實現一些天真的自製對稱加密,因為普通的 AES CTR 沒有在目標平台上的可用 API 中實現。
我要做的是,給定加密密碼和明文字節,通過 PBKDF2/RFC 2898 從密碼生成具有明文長度的“加密密鑰”,然後將這個“伽瑪”與明文字節進行異或。
您能否批評這種方法並列舉漏洞和問題?
TL;DR:是的,您可以使用 PBKDF2 作為流密碼。 但是,您不應同時將其用於此目的和預期目的(即基於密碼的密鑰派生)。相反,如果您需要兩者都做,請呼叫兩次。
PBKDF2是一個基於密碼的密鑰派生函式——或者,更確切地說,是一種從可變密鑰長度PRF構造這樣一個函式的方案,而這通常是通過採用加密散列函式並將其包裝在HMAC中來實現的建造。
作為一種通用的密鑰派生函式,PBKDF2 被設計成能夠生成任意長*的偽隨機位串。為了防止暴力破解密碼猜測,它還設計為採用故意減慢它的迭代計數。
然而,當用於生成比底層 PRF/散列函式產生的輸出更多的輸出時,PBKDF2 具有如今通常被認為是設計缺陷:它的執行時間大致與迭代次數和請求的輸出長度的乘積成正比(而,在許多現實世界的場景中,攻擊者測試一個猜測密碼的時間仍然只取決於迭代次數)。因此,您真的不想呼叫具有高迭代次數和高輸出長度的 PBKDF2。
相反,對於需要使用 PBKDF2 生成大量密鑰材料的應用程序,我的一般建議**是首先使用具有大量迭代計數的 PBKDF2 來生成一個“主密鑰”,其大小等於底層雜湊的輸出大小(應該最好選擇使其盡可能大,例如 SHA-512 用於 512 位輸出),然後將此主密鑰提供給非迭代 KDF(例如RFC 5896中的 HKDF-Expand ,甚至 PBKDF2 本身迭代計數設置為 1) 以將其擴展為所需的完整長度。
使用這種方案,應用於主密鑰的 PBKDF2 的輸出,具有唯一的每條消息鹽和 1 的迭代計數,甚至應該可以安全地直接用作密鑰流來異或消息。在內部,PBKDF2 只是在計數器模式下使用(加鹽和可選迭代的)HMAC,只要底層雜湊函式滿足 HMAC 安全證明的條件(所有現代雜湊,甚至一些舊的不安全),它就會產生安全的流密碼。像 SHA-1 甚至 MD5 這樣安全的算法被認為可以做到)。
您確實需要確保 PBKDF2 的鹽輸入對於每條消息都是唯一的;否則,您最終會為兩條消息重用相同的密鑰流,這會破壞任何基於 XOR 的流密碼的安全性。
另外,請注意,與任何 XOR 流密碼一樣,這種結構不保護消息完整性,實際上具有高度可塑性。為了防止主動攻擊者,您需要將加密方案與消息驗證碼結合起來。幸運的是,您已經有了一個可用的工具,即 HMAC。因此,只需再次通過 HMAC 執行 XORed 密文(最好使用從主密鑰派生的單獨密鑰),將結果附加到消息中,並在解密之前對其進行驗證,一切就緒。
事實上,如果你真的不能訪問底層的 HMAC 函式,即使是 PBKDF2 本身(實際上只是 HMAC 的一個薄包裝器)也可以用作 MAC,方法是將要驗證的消息作為 salt 參數傳遞並請求一個雜湊輸出塊. 雖然這不會產生與原始 HMAC 完全相同的輸出(因為即使將迭代計數設置為 1,PBKDF2 仍將塊計數器附加到 salt),但安全證明很簡單。
如果您想變得花哨,您甚至可以在 SIV 構造中使用 HMAC(或 PBKDF2)作為 MAC / nonce 生成器和加密原語。因此,儘管看起來很反常,但以下虛擬碼應該使用 PBKDF2 來實現安全的防誤用AEAD方案:
// a variable-length PRF, implemented using PBKDF2 with an iteration count of 1 function prf(key, data, len): return PBKDF2(key, data, 1, len) // SIV authenticator / IV length in bytes (32 bytes = 256 bits) constant token_size = 32 // internal MAC output length, may be larger than token_size constant internal_mac_size = max(token_size, PBKDF2_hash_block_size) function SIV_PBKDF2_encrypt(key, plaintext, assoc_data, nonce): macP = prf(key, "macP" + plaintext, internal_mac_size) macA = prf(key, "macA" + assoc_data, internal_mac_size) token = prf(key, "auth" + macP + macA + nonce, token_size) ciphertext = plaintext XOR prf(key, "encr" + token, length(plaintext)) return (token, ciphertext) function SIV_PBKDF2_decrypt(key, token, ciphertext, assoc_data, nonce): plaintext = ciphertext XOR prf(key, "encr" + token, length(ciphertext)) macP = prf(key, "macP" + plaintext, internal_mac_size) macA = prf(key, "macA" + assoc_data, internal_mac_size) if token != prf(key, "auth" + macP + macA + nonce, token_size): return ERROR else: return plaintext
在上面的程式碼中,所有變數都被假定為任意長度的字節串,並且可能包含空字節。
+
運算符始終表示字元串連接。請注意,上述左側參數的長度+
始終是固定的,從而使連接的結果明確。該
ERROR
常數代表解密失敗的一些明確指示,可能是由於惡意輸入;代替特殊的返回值,在這種情況下也可以拋出異常。重要的是,無論它是如何實現的,這樣的失敗都不應該向呼叫者透露有關無效plaintext
字元串的任何資訊,因為該字元串的內容可能被攻擊者操縱。如果為每個加密消息提供了不同的隨機數,則此程式碼應實現(以任何可能的實現錯誤為模)IND-CCA 安全認證加密方案。即使隨機數被重複或省略,它仍然應該實現“確定性認證加密”方案,在 Rogaway 和 Shrimpton 的意義上,本質上意味著它洩漏的唯一資訊(除了消息長度,所有任意長度的加密方案都顯示在某種程度上)是兩個加密消息是否相同。
輸入的長度
key
是任意的,但應該足以抵抗暴力猜測攻擊(即至少 128 位,最好更多)。它不應該是使用者提供的密碼——您需要首先通過 PBKDF2(或其他一些基於密碼的 KDF)以高迭代次數執行這些密碼。作為一種優化,
macA
如果預先知道相關數據,則可以預先計算內部變數;其他類似的優化,如 Rogaway 的原始 SIV 方案,也是可能的。*) 從技術上講,PBKDF2 的輸出長度限制為 $ 2^{32}-1 $ 乘以底層 PRF / 雜湊的輸出長度。
**) 假設不能或不會切換到更現代的基於密碼的 KDF,例如 scrypt、Argon2、Catena 或 Balloon 散列。與 PBKDF2 不同,所有這些 KDF 都被設計為消耗大量記憶體,這使得它們更能抵抗使用 GPU、FPGA 或 ASIC 進行的大規模並行暴力攻擊。作為副作用,它們本質上還支持高效生成大量輸出密鑰材料。