自定义Key和IV的Perl AES加密:与JS结果不一致问题
JavaScript AES加密与Perl实现不匹配的问题解决
问题场景
我有一段JavaScript的AES加密代码,但在Perl中始终无法得到匹配的加密结果。以下是具体代码及输出:
JavaScript代码
'use strict'; const CryptoJS = require('crypto-js'); const message = 's3cret'; const aesPassword = 'MyPassword'; const salt = CryptoJS.lib.WordArray.random(16); const iv = CryptoJS.lib.WordArray.random(16); const key = CryptoJS.PBKDF2(aesPassword, salt, {keySize: 128/32, iterations: 1000}); const ciphertext = CryptoJS.AES.encrypt(message, key, {iv: iv}).ciphertext.toString(CryptoJS.enc.Base64); const encryptedMessage = Buffer.from(iv + '::' + salt + '::' + ciphertext).toString('base64'); console.log("encryptedMessage:", encryptedMessage);
JavaScript输出
encryptedMessage: NDNjOWNjYmE2MzBmZTFjZDYxZmMyYmRiOTAxMjFjNmY6OjVjNzg4YTQxNTg1MWU5MDlkOWM3OTUxNzE3NzE0MjA0OjpxVDFWdG9kN2loUzJ3dnR3bWxuUG93PT0=
(Base64编码前为92字节。)
Perl原代码
use strict; use warnings; use Crypt::CBC; use Crypt::PBKDF2; use MIME::Base64; my $message = 's3cret'; my $aesPassword = 'MyPassword'; my $salt = Crypt::CBC->random_bytes(16); my $iv = Crypt::CBC->random_bytes(16); my $pbkdf2 = Crypt::PBKDF2->new(output_len=>128/32, iterations => 1000); my $key = $pbkdf2->PBKDF2($aesPassword, $salt); my $cipher = Crypt::CBC->new(-cipher=>'Cipher::AES', -pbkdf=>'pbkdf2', -key=>$key, -iv=>$iv, -header=>'none'); my $ciphertext = encode_base64( $cipher->encrypt($message), "" ); my $encryptedMessage = encode_base64( join('::', $iv, $salt, $ciphertext), "" ); print "encryptedMessage: $encryptedMessage";
Perl原输出
encryptedMessage: tO/jlJ3NkuJG0ZA58EDR0Do6wUf4xHb/MwWm3iyT+ejYWDo6SllMSHB0S0ZuMDVYWDRzS3ZCWlRiZz09Cg==
(Base64编码前为61字节。)
问题分析与修正
1. PBKDF2密钥派生参数错误
CryptoJS中keySize: 128/32表示生成4个32位字的密钥(即128位/16字节),但Perl的Crypt::PBKDF2的output_len参数是字节数,原代码设为128/32(即4)会生成4字节的错误密钥,需改为16。同时显式指定HMAC-SHA1哈希算法,与CryptoJS默认逻辑对齐。
2. Crypt::CBC初始化错误
原代码指定了-pbkdf=>'pbkdf2',会导致Crypt::CBC将传入的-key当作密码再次派生密钥,而非直接使用已生成的密钥。需去掉该参数,并添加-literal_key => 1明确告知直接使用传入的密钥;同时指定PKCS7填充、CBC模式,与CryptoJS默认加密配置匹配。
3. IV与Salt的字符串处理错误
JavaScript中iv + '::' + salt会自动将二进制的IV、Salt转换为十六进制字符串(每个字节对应2个十六进制字符),但原Perl代码直接使用二进制数据拼接,导致格式完全不一致。需将Perl中的二进制IV、Salt转为十六进制字符串后再拼接。
修正后的Perl代码
use strict; use warnings; use Crypt::CBC; use Crypt::PBKDF2; use MIME::Base64; my $message = 's3cret'; my $aesPassword = 'MyPassword'; my $salt = Crypt::CBC->random_bytes(16); my $iv = Crypt::CBC->random_bytes(16); # 修正PBKDF2参数:生成16字节(128位)密钥,显式指定哈希算法 my $pbkdf2 = Crypt::PBKDF2->new( output_len => 16, iterations => 1000, hash_class => 'HMACSHA1' ); my $key = $pbkdf2->PBKDF2($aesPassword, $salt); # 修正Crypt::CBC配置:使用字面密钥、PKCS7填充、CBC模式 my $cipher = Crypt::CBC->new( -cipher => 'OpenSSL::AES', -key => $key, -iv => $iv, -header => 'none', -literal_key => 1, -padding => 'pkcs7', -mode => 'cbc' ); my $ciphertext = encode_base64($cipher->encrypt($message), ""); # 将二进制IV、Salt转为十六进制字符串,与JS逻辑对齐 my $iv_hex = unpack('H*', $iv); my $salt_hex = unpack('H*', $salt); my $combined = join('::', $iv_hex, $salt_hex, $ciphertext); my $encryptedMessage = encode_base64($combined, ""); print "encryptedMessage: $encryptedMessage\n";
验证说明
修正后的代码会生成与JavaScript格式、内容完全匹配的加密结果:
- IV和Salt以十六进制字符串形式拼接,长度与JS一致
- 密钥派生、加密模式、填充逻辑完全对齐
- 最终Base64编码前的内容长度为92字节,与JS输出一致
内容的提问来源于stack exchange,提问作者h q
相关产品推荐
相关产品推荐

