使用相同的密码两次调用 openssl_pkey_export() 会得到不同的结果

Calling openssl_pkey_export() twice with the same passphrase gives different results

我有非对称密码学的基础知识,但我是 OpenSSL PHP 扩展的新手,在执行以下代码后我感到困惑(请注意 openssl_pkey_new() 被称为只调用了一次,但是 openssl_pkey_export() 和密码被调用了两次):

// Based on:
// PHP: openssl_pkey_new - Manual
// => https://www.php.net/manual/es/function.openssl-pkey-new.php#111769
$config=array(
    "digest_alg" => "sha512",
    "private_key_bits" => 4096,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
);
// Create the private and public key
$res=openssl_pkey_new($config);
// Extract the private key from $res
openssl_pkey_export($res, $privKey);
openssl_pkey_export($res, $privKeyEnc1, "12345678");
openssl_pkey_export($res, $privKeyEnc2, "12345678");
print_r($privKeyEnc1); /* Output 1 */
print_r($privKeyEnc2); /* Output 2 */

我 运行 它使用 php -a 我发现输出 1 和输出 2($privKeyEnc1$privKeyEnc2)是不同的(我 post仅第一个和最后一个字符):

输出 1 ($privKeyEnc1):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIu9pGQ4UYhHMCAggA
...
b/Jzga55d9CZAez70XZ1IcDlqhtfCS0Q7+RDwdXgsAd9IYrZaVKBrUOxhaSc/Xe8
9GBsV9M67b7uyJ1wAeEpJw==
-----END ENCRYPTED PRIVATE KEY-----

输出 2 ($privKeyEnc2):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIiNaCw3b9l9wCAggA
...
bOL2WGBOymb6db0G5/IdIs7zQx6aQjOtoFx4hm0cY4YmEmNKKdiXOoVpRZT4SBRw
t8ksuWHoESag0z4NETpetw==
-----END ENCRYPTED PRIVATE KEY-----

这是错误的还是正常的行为? 谁能详细解释一下为什么会这样?

PHP 代码创建 PEM 编码的加密 PKCS#8 密钥。在加密期间,会生成 random salt 和 IV 值,即使对于相同(未加密)的密钥,每次都会导致不同的加密,这就是加密密钥不同的原因。


为了说明这一点,请考虑使用您的代码生成的以下加密密钥(为简单起见,使用 512 位密钥,出于安全原因,实际上密钥大小必须 >= 2048 位):

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBtDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIzjf3uTuaOSQCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECNaB+caJ1Ew7BIIBYJRrmSSelAKO
7PQR62MscnZmKcOnlJJ++5zryaV/B9qQdWQYRi9KYiyNAuVsvJgw9GsNZVjB99Mf
Ldx1a1ppOQ4tznhAl8db7cYYsREw1AbyTgQ5c58VQCNCVUqGWSS09zSooZA9dkBT
CE52vx0/SwNfDOUlW/NH9eQwQy3WAex7mYPSwOZfuTl00Wz7UuJE4HaXAS3F65Wb
4mt7nmtLk1hEFEPUFzxHh7xpXxDFFG2SN1iMUavQFp61947Y7JS+LhiP+B3gREiA
OFibK8q2sBJGZM2DnN+irA+meuQE/mUtsyWx4KSinUBvfFAXI+448lkV3+Wke7yK
M+BizIK6ua+4oAn33oj7Vd/SFiq0/h0rtXE/OvRMeTKGr1wBx64y+PhzuIsXarpQ
OunNUN2rnqBYlyRUY6PUB9XPJ8DlScgvKetseby+yg5aDSejjcdZyC3rwX+HvdVO
cQ8mPDn6Zrg=
-----END ENCRYPTED PRIVATE KEY-----

使用 ASN.1 解析器,例如https://lapo.it/asn1js/,解码结果如下:

这提供了有关加密密钥的以下信息:

  • 用于加密的对称密钥是使用PBKDF2导出的。除了密码 HMAC/SHA256, 随机 生成的盐 0xCE37F7B93B9A3924 和迭代计数 2048 被应用。
  • 加密本身使用 des-EDE3-CBC 进行,即使用 CBC 模式下的三重 DES,这意味着 192 位密钥。使用随机生成的IV 0xD681F9C689D44C3B。字节序列0x946B99249E...就是密文。

即使使用相同的密码,随机盐也会为每次加密生成不同的对称密钥。随机对称密钥和随机IV都会产生不同的密文,即每次加密都有不同的加密密钥,从而增强了安全性。


测试:

可以通过解密密钥轻松测试上述逻辑。解密密文,需要密码,盐和IV,例如:

$encryptedPkcs8 = hex2bin("946b99249e94028eecf411eb632c72766629c3a794927efb9cebc9a57f07da90756418462f4a622c8d02e56cbc9830f46b0d6558c1f7d31f2ddc756b5a69390e2dce784097c75bedc618b11130d406f24e0439739f15402342554a865924b4f734a8a1903d764053084e76bf1d3f4b035f0ce5255bf347f5e430432dd601ec7b9983d2c0e65fb93974d16cfb52e244e07697012dc5eb959be26b7b9e6b4b9358441443d4173c4787bc695f10c5146d9237588c51abd0169eb5f78ed8ec94be2e188ff81de044488038589b2bcab6b0124664cd839cdfa2ac0fa67ae404fe652db325b1e0a4a29d406f7c501723ee38f25915dfe5a47bbc8a33e062cc82bab9afb8a009f7de88fb55dfd2162ab4fe1d2bb5713f3af44c793286af5c01c7ae32f8f873b88b176aba503ae9cd50ddab9ea05897245463a3d407d5cf27c0e549c82f29eb6c79bcbeca0e5a0d27a38dc759c82debc17f87bdd54e710f263c39fa66b8"); 
$tripleDesKey = hash_pbkdf2("sha256", "12345678", hex2bin("ce37f7b93b9a3924"), 2048, 24, true);
$pkcs8Der = openssl_decrypt($encryptedPkcs8, "des-EDE3-CBC", $tripleDesKey, OPENSSL_RAW_DATA, hex2bin("d681f9c689d44c3b"));
$pkcs8Pem = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(base64_encode($pkcs8Der), 64, "\n") . "-----END PRIVATE KEY-----";
print($pkcs8Pem . PHP_EOL);

它给出了以下 PKCS#8 密钥:

-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAzw++K1mInb7pX8tH
LEwE/ZCpoRXcKvwCYPCVK4Rfor4Wt32BrdHGY6d8tTFFAdGKH5fPSyVDQWPBjyEM
I51+1wIDAQABAkBx7UyKF4I2oSNQ5MztT4pzZZQfoKJ6OByq79RzlCr2pDkEQHw+
PIFKOXXa0jKPFD9HqmNTyCJBXkrDg40RpThxAiEA6Vgs+h1wpR4ZD+woJSFhRkqZ
QY/rKZNDPL0aZZE0cxkCIQDjKkyZmxDfrns0wua9Mvp5mqOWjgdGX/qtfNKr2G4v
bwIgL6D45UCXGozvLqnUc+fBVDir2Y8HwB+37LDor2yZGRkCIQCp6QqQXe66D+yx
oxIo88drS2IOiz8fwUxjlRiSVnjb2wIhAJg4TNnK/0kx5yUrd/dT88ekQ7Pp7md2
DqALeeYGX1gE
-----END PRIVATE KEY-----

将获得等效的结果,例如:

openssl pkcs8 -topk8 -inform pem -in <encrypted PKCS#8 key> -outform pem -nocrypt -out <PKCS#8 key>

为了完整性:加密算法可以通过openssl_pkey_export()中的第4个参数来改变,例如:

openssl_pkey_export($res, $privKeyEnc1, "12345678", array('encrypt_key_cipher' => OPENSSL_CIPHER_AES_256_CBC));

在 CBC 模式下应用 AES-256,而不是在 CBC 模式下应用三重 DES。