1

我正在尝试解密一些已使用aes-256-cbcPHP 脚本中的密码和方法加密的数据。

这是我加密原始数据的方法

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";

它返回:

错误:06065064:数字信封例程:EVP_DecryptFinal_ex:错误解密

我知道我应该使用密钥/iv 对,但我无法用任何盐从我的密码中提取它。我怎样才能让它使以下命令起作用?

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&??
4

2 回答 2

3

openssl enc通过-k选项(在您的情况下"MYPASSWORD")用作参数的密码(或密码)key与 PHP 函数openssl_decrypt()期望的参数之间存在差异。-k选项是任意长度的openssl enc密码短语,从中可以派生出实际的 256 位加密密钥。这也是PHPopenssl_decrypt()函数需要的关键。此加密密钥是 256 位,因为您选择了aes-256.

-p您可以通过在调用时添加选项来了解派生的加密密钥是什么openssl enc。这还会打印iv,您需要与 PHPopenssl_decrypt()函数一起使用的另一个参数。例如:

printf "Hello" |  openssl enc -e -base64 -A -aes-256-cbc -k "MYPASSWORD" -nosalt -p
key=E0FAC2DD2C00FFE30F27A6D14568CB4F12EB84676A3A2BFB172A444C3BBB831F
iv =5A79774BB4B326EED949E6871FC27697
sp0z18QezUO8tSy7tgjOEw==

这些打印的keyiv值是您需要输入到 PHPopenssl_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"

运行时您可能已经注意到了额外的-nosalt选项openssl encSalt用于向密钥派生过程添加一些随机性/唯一性,并-nosalt省略该步骤。因此,key,ivciphertext在每次运行中都是相同的(如果使用相同的密码和明文),您应该能够准确地重现输出。如果您不使用-nosalt,您的实验仍然可以工作,但每次运行的key,ivciphertext值会有所不同,您还必须摆脱openssl作为标题添加的盐 - 有关详细信息,请参阅此答案的下方。

key另一种选择是让 PHP 代码iv在调用openssl_decrypt(). 为此,您必须检查您正在使用的版本的工具代码。encopenssl在那里你可以看到使用了哪个密钥派生函数——这取决于openssl你使用的版本以及你给它的选项——以及它是否在 .php 的 PHP 绑定中可用openssl


更新,回复您的评论,在其中添加您只有密文和密码可用的信息,并且密文是用crypto-js.

查看源代码crypto-js,它在源文件的注释中evpkdf.js提到“密钥派生函数旨在符合EVP_BytesToKey”,这与大多数openssl版本使用的函数相同。因此,您应该能够使用该工具通过使用选项openssl enc来提取key和,如下所示:iv-p

$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(因此keyand )的选择方式不同,对于每个加密操作iv都是随机的。crypto-js要直接在 PHP 中执行此操作,请参阅我之前的评论:所需的功能似乎在其decrypt模块中不可用。

您可以通过在解密时输入keyiv来验证这是否有效。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 个字节是“魔术”字节Salted__,之后是盐。如果您使用密码短语,该工具通常会读取此盐,但如果您使用keyiv直接解密,则它会妨碍您。因此,您必须在解密时将字节作为密文输入之前删除该标头openssl enc,例如使用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

这个单行首先进行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功能实现的专有机制的密钥派生openssl enc和使用。即使现在警告这已被弃用crypto-jsEVP_ByesToKeyopenssl enc

相反,开始使用像PBKDF2这样的标准算法。最新版本支持这一点openssl enc,我也在crypto-js和 PHPcrypto模块的源代码中发现了它(但我自己从未使用过这些)。如果您有一个需要保留的加密数据数据库,您可以使用旧方法解密和 PKDBF2 方法加密一次重新加密其内容。确保将盐分开存储,而不是与密文一起存储。

于 2018-07-28T04:37:15.870 回答
1

这里的问题是您没有使用EVP_BytesToKey. 这是用于从您的密码派生密钥和 IV 的 OpenSSL KDF。

请注意,它是不安全的。您应该更喜欢将十六进制密钥和 IV 直接传递给openssl enc.

于 2018-07-28T00:26:46.227 回答