是否有 OpenSSL 可互操作的 AES 加密標準?
許多 AES 加密的東西(文件、字元串、數據庫條目等)以
"Salted__"
("U2FsdGVkX1"
在 base64 中)開頭。我聽說這是某種 OpenSSL 互操作性的東西:a b c。某處是否有一些標準參考(可能是 RFC?)來解釋這種 OpenSSL 可互操作的 AES 加密的東西是如何產生並隨後解密的?
理想情況下,答案將連結到整個過程的標準參考,或者可能是步驟的簡短摘要列表,其中包含指向每個步驟的標準參考的連結,例如:
要以已知密碼開頭
"U2FsdGVkX1"
並使用已知密碼解密這樣的東西,
- 首先進行
base64
解碼 - 參見Wikipedia: base64。結果將以"Salted__"
. 注意不要使用 C 字元串,因為結果可能包含幾個0x00
字節。- …
- (我猜想一些關於pickle和靜脈注射的東西?)
- (我猜這裡有關於 CBC 或 CTR 的內容?) - 參見Wikipedia: block cipher mode of operation。
- (也許這裡有關於消息身份驗證的內容?)
- …
- 使用 AES 解密每個塊
decrypt_one_AES_block( key, block_of_128_bits )
- 請參閱維基百科:AES 文章和高級加密標準 (AES) 簡筆圖指南。- 保存 each 的結果
decrypt_one_AES_block()
,將它們連接在一起,這就是你的明文。如果原始文件是人類可讀的文本或 HTML 文件,則可以將結果儲存在 C 字元串中;但其他種類的東西可能包括幾個0x00
與 C 字元串不兼容的字節。這聽起來像是我打算自己編寫一個實現。我向您保證,我計劃使用幾個可用庫之一——只是在查看這些庫時,我想知道它們應該做什麼。OpenSSL 庫應該做什麼?
是否有 OpenSSL 可互操作的 AES 加密標準?
使用以下 openssl 命令作為此答案的基礎:
echo -n 'Hello World!' | openssl enc aes-256-cbc -e -a -salt -pbkdf2 -iter 10000
此命令加密明文“Hello World!” 使用 aes-256-cbc。密鑰是使用 pbkdf2 使用密碼和隨機鹽生成的,具有 10,000 次 sha256 散列迭代。當提示輸入密碼時,我輸入了密碼“p4$$w0rd”。該命令產生的密文輸出為:
U2FsdGVkX1/Kf8Yo6JjBh+qELWhirAXr78+bbPQjlxE=
現在,要回答提出的問題,’要解密以“U2FsdGVkX1”開頭並使用已知密碼的東西’:
請執行下列操作:
- base64 解碼 openssl 的輸出,utf-8 解碼密碼,這樣我們就有了這兩個的底層字節。
- salt 是 base64 解碼的 openssl 輸出的字節 8-15。
- 給定密碼字節和鹽以及 10,000 次 sha256 散列迭代,使用 pbkdf2 派生一個 48 字節的密鑰。
- key 是派生密鑰的 0-31 字節,iv 是派生密鑰的 32-47 字節。
- 密文是經過 base64 解碼的 openssl 輸出末尾的第 16 個字節。
- 使用 aes-256-cbc、給定密鑰、iv 和密文解密密文。
- 從純文字中刪除 PKCS#7 填充。明文的最後一個字節表示附加到明文末尾的填充字節數。這是要刪除的字節數。
下面是上述過程的python3實現:
import base64 import hashlib from Crypto.Cipher import AES #requires pycrypto #inputs openssloutputb64='U2FsdGVkX1/Kf8Yo6JjBh+qELWhirAXr78+bbPQjlxE=' password='p4$$w0rd' pbkdf2iterations=10000 #convert inputs to bytes openssloutputbytes=base64.b64decode(openssloutputb64) passwordbytes=password.encode('utf-8') #salt is bytes 8 through 15 of openssloutputbytes salt=openssloutputbytes[8:16] #derive a 48-byte key using pbkdf2 given the password and salt with 10,000 iterations of sha256 hashing derivedkey=hashlib.pbkdf2_hmac('sha256', passwordbytes, salt, pbkdf2iterations, 48) #key is bytes 0-31 of derivedkey, iv is bytes 32-47 of derivedkey key=derivedkey[0:32] iv=derivedkey[32:48] #ciphertext is bytes 16-end of openssloutputbytes ciphertext=openssloutputbytes[16:] #decrypt ciphertext using aes-cbc, given key, iv, and ciphertext decryptor=AES.new(key, AES.MODE_CBC, iv) plaintext=decryptor.decrypt(ciphertext) #remove PKCS#7 padding. #Last byte of plaintext indicates the number of padding bytes appended to end of plaintext. This is the number of bytes to be removed. plaintext = plaintext[:-plaintext[-1]] #output results print('openssloutputb64:', openssloutputb64) print('password:', password) print('salt:', salt.hex()) print ('key:', key.hex()) print ('iv:', iv.hex()) print ('ciphertext:', ciphertext.hex()) print ('plaintext:', plaintext.decode('utf-8'))
正如預期的那樣,上面的 python3 腳本產生以下內容:
openssloutputb64: U2FsdGVkX1/Kf8Yo6JjBh+qELWhirAXr78+bbPQjlxE= password: p4$$w0rd salt: ca7fc628e898c187 key: 444ab886d5721fc87e58f86f3e7734659007bea7fbe790541d9e73c481d9d983 iv: 7f4597a18096715d7f9830f0125be8fd ciphertext: ea842d6862ac05ebefcf9b6cf4239711 plaintext: Hello World!
注意:可以在https://github.com/meixler/web-browser-based-file-encryption-decryption找到javascript 中的等效/兼容實現(使用web crypto api ) 。
-pbkdf2
(2020 年 12 月 25 日添加)此答案適用於支持該選項的 openssl v1.1.1 。舊版本的 openssl(或帶有該-md md5
選項的新版本 openssl)使用基於 md5 雜湊函式的弱密鑰派生方法,如dave_thompson_085在下面的回答中和Thomas Pornin在他的回答中所解釋的。有關如何實現此密鑰派生方法的詳細資訊,請參閱https://security.stackexchange.com/questions/29106/openssl-recover-key-and-iv-by-passphrase/242567#242567