Вызов openssl_pkey_export() дважды с одной и той же парольной фразой дает разные результаты

У меня есть базовые знания об асимметричной криптографии, но я новичок в расширении 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) отличаются (я публикую только первый и последний символы):

Выход 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 в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
0
28
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Код PHP создает зашифрованный ключ PKCS#8 в кодировке PEM. Во время шифрования генерируются значения случайный для соли и 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, т.е. Triple DES в режиме CBC, что подразумевает 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>

Для полноты: Алгоритм шифрования можно изменить 4-м параметром в openssl_pkey_export(), например:

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

применяет AES-256 в режиме CBC вместо Triple DES в режиме CBC.

Другие вопросы по теме