使用 openssl_decrypt() 和密码(注意 key/iv)
Use openssl_decrypt() with passphase (not key/iv)
我正在尝试解密一些已在 PHP 脚本中使用密码和 aes-256-cbc
方法加密的数据。
这是我加密原始数据的方法
printf "Hello" | openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD"
// output
U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=
当我尝试在命令行中解密时它工作正常
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD"
// output
Hello
但是当我在我的 PHP 脚本中使用 openssl_decrypt() 时,它 不起作用!!
$result = openssl_decrypt("U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=", 'AES-256-CBC', "MYPASSWORD");
var_dump($result);
//output
bool(false)
我附加以下几行以获取错误
while ($msg = openssl_error_string())
echo $msg . "<br />\n";
它returns:
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad
decrypt
我知道我应该使用 key/iv 对,但我无法使用任何 salt 从我的密码短语中提取它。 我怎样才能使下面的命令起作用?
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K ??????????????? -iv ????????????????
// expected output !!!
Hello
编辑:
我尝试使用 -p
参数获取 key/iv 但它不起作用
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04
Hello
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF05E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
bad decrypt
140735954895816:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:529:
??G?"r!C???&C&??
这里的问题是您没有使用 EVP_BytesToKey
。这是用于从您的密码派生密钥和 IV 的 OpenSSL KDF。
请注意,它是不安全的。您应该更喜欢将十六进制密钥和 IV 直接传递给 openssl enc
.
通过 -k
选项(在您的情况下 "MYPASSWORD"
)用作 openssl enc
参数的密码(或密码短语)与 key
PHP 函数 openssl_decrypt()
期望的参数。 openssl enc
的 -k
选项是一个任意长度的密码短语,实际的 256 位加密密钥将从中导出。这也是 PHP openssl_decrypt()
函数需要的关键。此加密密钥为 256 位,因为您选择了 aes-256
.
您可以通过在调用 openssl enc
时添加 -p
选项来了解导出的加密密钥是什么。这还会打印 iv
,这是您需要与 PHP openssl_decrypt()
函数一起使用的另一个参数。例如:
printf "Hello" | openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD" -nosalt -p
key=E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F
iv =5A79774BB4B326EED949E6871FC27697
sp0z18QezUO8tSy7tgjOEw==
这些打印的 key
和 iv
值是您需要输入到 PHP openssl_decrypt()
函数调用中的值,如下所示:
$ciphertext = 'sp0z18QezUO8tSy7tgjOEw==';
$key = hex2bin('E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F');
$iv = hex2bin('5A79774BB4B326EED949E6871FC27697');
$result = openssl_decrypt($ciphertext, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);
运行 PHP 脚本现在成功:
$ php decrypt.php
string(5) "Hello"
您可能已经注意到 运行 宁 openssl enc
时额外的 -nosalt
选项。 Salt用于在密钥推导过程中添加一些randomness/uniqueness,而-nosalt
省略了该步骤。因此,key
、iv
和 ciphertext
在每个 运行 中都是相同的(如果使用相同的密码和明文),您应该能够准确地重现输出。如果您不使用 -nosalt
,您的实验仍然有效,但每个 运行 的 key
、iv
和 ciphertext
值将不同,您也会必须摆脱 openssl
添加为 header 的盐——有关详细信息,请参阅此答案的下方。
另一种选择是让 PHP 代码在调用 openssl_decrypt()
之前从密码短语中导出 key
和 iv
。为此,您必须检查 code of the enc
tool 以查找您正在使用的 openssl
版本。在那里您可以看到使用了哪个密钥派生函数——这取决于您使用的 openssl
的版本以及您提供的选项——以及它是否在 PHP 绑定中可用openssl
.
更新,回复您的评论,您在其中添加了您只有密文和密码可用的信息,并且该密文是使用 crypto-js
创建的。
查看crypto-js
的源代码,其中提到in a comment in the source file evpkdf.js
即"the key derivation function is meant to conform with EVP_BytesToKey",这与大多数openssl
版本使用的功能相同。因此,您应该能够使用 openssl enc
工具通过使用 -p
选项来提取 key
和 iv
,如下所示:
$printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04
(您现在也在另一条评论中确认了这一点),然后在调用 PHP 函数时使用它们,如上所述。请注意,您必须分别对每个密文执行此操作,因为 salt
(以及 key
和 iv
)的选择不同,每次加密由 crypto-js
随机选择行动。要直接在 PHP 中执行此操作,请参阅我之前的评论:所需的功能似乎在其 decrypt
模块中不可用。
您可以通过在解密时将 key
和 iv
填入 openssl enc
来验证它是否有效。但是,有一个障碍。使用盐时,openssl
方法是在输出中包含该盐,如您在此处所见:
$ printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl base64 -d -A | hexdump -C
00000000 53 61 6c 74 65 64 5f 5f 9d 5a e0 6e 8a 2b 62 7c |Salted__.Z.n.+b||
00000010 7e 33 bb 56 2f fe 5e fe 1d c7 c8 a9 1f f0 c5 27 |~3.V/.^........'|
00000020
输出的前 16 个字节是 "magic" 个字节 Salted__
,然后是盐。如果您使用密码短语,该盐通常会被该工具读取,但如果您直接使用 key
和 iv
解密,它就会妨碍您。因此,在解密时将字节作为密文输入 openssl enc
之前,您必须删除 header ,例如使用 tail
像这样:
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl base64 -d -A | tail -c +17 | openssl enc -d -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
Hello
这个one-liner先做base64
解码,然后去掉前16个字节,然后将结果送入openssl enc
,不再需要-base64
选项,因为已经处理好了。
在PHP中:
$ciphertext = 'U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=';
$ciphertext_decoded = base64_decode($ciphertext);
$ciphertext_nosalt = base64_encode(substr($ciphertext_decoded, 16));
$key = hex2bin('8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA');
$iv = hex2bin('4150125DCCD36F73A9F08F3020151A04');
$result = openssl_decrypt($ciphertext_nosalt, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);
综上所述,您可能最好放弃 openssl enc
和 crypto-js
使用的密钥派生,它依赖于 OpenSSL EVP_ByesToKey
实现的专有机制功能。甚至 openssl enc
now warns about this being deprecated .
相反,开始使用像 PBKDF2 这样的标准算法。这是由 openssl enc
的更新版本支持的,我在 crypto-js
和 PHP crypto
模块的源代码中也发现了它(但我自己从未使用过它们).如果您有一个需要保留的加密数据数据库,您可以 re-encrypt 它的内容一次,使用旧方法解密并使用 PKDBF2 方法加密。确保将盐分开存储,而不是与密文一起存储为一个 blob。
我正在尝试解密一些已在 PHP 脚本中使用密码和 aes-256-cbc
方法加密的数据。
这是我加密原始数据的方法
printf "Hello" | openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD"
// output
U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=
当我尝试在命令行中解密时它工作正常
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD"
// output
Hello
但是当我在我的 PHP 脚本中使用 openssl_decrypt() 时,它 不起作用!!
$result = openssl_decrypt("U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=", 'AES-256-CBC', "MYPASSWORD");
var_dump($result);
//output
bool(false)
我附加以下几行以获取错误
while ($msg = openssl_error_string())
echo $msg . "<br />\n";
它returns:
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
我知道我应该使用 key/iv 对,但我无法使用任何 salt 从我的密码短语中提取它。 我怎样才能使下面的命令起作用?
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K ??????????????? -iv ????????????????
// expected output !!!
Hello
编辑:
我尝试使用 -p
参数获取 key/iv 但它不起作用
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04
Hello
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF05E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
bad decrypt
140735954895816:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:evp_enc.c:529:
??G?"r!C???&C&??
这里的问题是您没有使用 EVP_BytesToKey
。这是用于从您的密码派生密钥和 IV 的 OpenSSL KDF。
请注意,它是不安全的。您应该更喜欢将十六进制密钥和 IV 直接传递给 openssl enc
.
通过 -k
选项(在您的情况下 "MYPASSWORD"
)用作 openssl enc
参数的密码(或密码短语)与 key
PHP 函数 openssl_decrypt()
期望的参数。 openssl enc
的 -k
选项是一个任意长度的密码短语,实际的 256 位加密密钥将从中导出。这也是 PHP openssl_decrypt()
函数需要的关键。此加密密钥为 256 位,因为您选择了 aes-256
.
您可以通过在调用 openssl enc
时添加 -p
选项来了解导出的加密密钥是什么。这还会打印 iv
,这是您需要与 PHP openssl_decrypt()
函数一起使用的另一个参数。例如:
printf "Hello" | openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD" -nosalt -p
key=E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F
iv =5A79774BB4B326EED949E6871FC27697
sp0z18QezUO8tSy7tgjOEw==
这些打印的 key
和 iv
值是您需要输入到 PHP openssl_decrypt()
函数调用中的值,如下所示:
$ciphertext = 'sp0z18QezUO8tSy7tgjOEw==';
$key = hex2bin('E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F');
$iv = hex2bin('5A79774BB4B326EED949E6871FC27697');
$result = openssl_decrypt($ciphertext, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);
运行 PHP 脚本现在成功:
$ php decrypt.php
string(5) "Hello"
您可能已经注意到 运行 宁 openssl enc
时额外的 -nosalt
选项。 Salt用于在密钥推导过程中添加一些randomness/uniqueness,而-nosalt
省略了该步骤。因此,key
、iv
和 ciphertext
在每个 运行 中都是相同的(如果使用相同的密码和明文),您应该能够准确地重现输出。如果您不使用 -nosalt
,您的实验仍然有效,但每个 运行 的 key
、iv
和 ciphertext
值将不同,您也会必须摆脱 openssl
添加为 header 的盐——有关详细信息,请参阅此答案的下方。
另一种选择是让 PHP 代码在调用 openssl_decrypt()
之前从密码短语中导出 key
和 iv
。为此,您必须检查 code of the enc
tool 以查找您正在使用的 openssl
版本。在那里您可以看到使用了哪个密钥派生函数——这取决于您使用的 openssl
的版本以及您提供的选项——以及它是否在 PHP 绑定中可用openssl
.
更新,回复您的评论,您在其中添加了您只有密文和密码可用的信息,并且该密文是使用 crypto-js
创建的。
查看crypto-js
的源代码,其中提到in a comment in the source file evpkdf.js
即"the key derivation function is meant to conform with EVP_BytesToKey",这与大多数openssl
版本使用的功能相同。因此,您应该能够使用 openssl enc
工具通过使用 -p
选项来提取 key
和 iv
,如下所示:
$printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl enc -d -base64 -A -aes-256-cbc -k "MYPASSWORD" -p
salt=9D5AE06E8A2B627C
key=8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA
iv =4150125DCCD36F73A9F08F3020151A04
(您现在也在另一条评论中确认了这一点),然后在调用 PHP 函数时使用它们,如上所述。请注意,您必须分别对每个密文执行此操作,因为 salt
(以及 key
和 iv
)的选择不同,每次加密由 crypto-js
随机选择行动。要直接在 PHP 中执行此操作,请参阅我之前的评论:所需的功能似乎在其 decrypt
模块中不可用。
您可以通过在解密时将 key
和 iv
填入 openssl enc
来验证它是否有效。但是,有一个障碍。使用盐时,openssl
方法是在输出中包含该盐,如您在此处所见:
$ printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl base64 -d -A | hexdump -C
00000000 53 61 6c 74 65 64 5f 5f 9d 5a e0 6e 8a 2b 62 7c |Salted__.Z.n.+b||
00000010 7e 33 bb 56 2f fe 5e fe 1d c7 c8 a9 1f f0 c5 27 |~3.V/.^........'|
00000020
输出的前 16 个字节是 "magic" 个字节 Salted__
,然后是盐。如果您使用密码短语,该盐通常会被该工具读取,但如果您直接使用 key
和 iv
解密,它就会妨碍您。因此,在解密时将字节作为密文输入 openssl enc
之前,您必须删除 header ,例如使用 tail
像这样:
printf "U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=" | openssl base64 -d -A | tail -c +17 | openssl enc -d -aes-256-cbc -K 8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA -iv 4150125DCCD36F73A9F08F3020151A04
Hello
这个one-liner先做base64
解码,然后去掉前16个字节,然后将结果送入openssl enc
,不再需要-base64
选项,因为已经处理好了。
在PHP中:
$ciphertext = 'U2FsdGVkX1+dWuBuiitifH4zu1Yv/l7+HcfIqR/wxSc=';
$ciphertext_decoded = base64_decode($ciphertext);
$ciphertext_nosalt = base64_encode(substr($ciphertext_decoded, 16));
$key = hex2bin('8ACC4E30E9128FBB0763DDDA8998A7141DFDC77B9DADF0A5FC65E67E2A8313FA');
$iv = hex2bin('4150125DCCD36F73A9F08F3020151A04');
$result = openssl_decrypt($ciphertext_nosalt, 'AES-256-CBC', $key, 0, $iv);
var_dump($result);
综上所述,您可能最好放弃 openssl enc
和 crypto-js
使用的密钥派生,它依赖于 OpenSSL EVP_ByesToKey
实现的专有机制功能。甚至 openssl enc
now warns about this being deprecated .
相反,开始使用像 PBKDF2 这样的标准算法。这是由 openssl enc
的更新版本支持的,我在 crypto-js
和 PHP crypto
模块的源代码中也发现了它(但我自己从未使用过它们).如果您有一个需要保留的加密数据数据库,您可以 re-encrypt 它的内容一次,使用旧方法解密并使用 PKDBF2 方法加密。确保将盐分开存储,而不是与密文一起存储为一个 blob。