Передача ключа publi c в формате PEM в openssl_pkey_get_publi c выдает ошибку: 0906D06 C: процедуры PEM: PEM_read_bio: нет начальной строки - PullRequest
1 голос
/ 04 апреля 2020

Следующий openli c Ключ RSA в формате PEM был предоставлен openssl_pkey_get_publi c.

-----BEGIN PUBLIC KEY-----
MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQCIZouo/rL5IkIIGrke/qkY
Nsb9JDXUw2MfutYdwIVjPiEbAcLiVxK6tOVXy7dq+hU0zyNd68bUi7VJjXWoiepS
+Mm6v76GCGvVvno48m7ofWIq6VLEaMQjIM/pzkF0TW7CmtjKvgg722Hx87AI/KCM
sWuHjhcQZsMgV4ibC8EAY6GYwHYAPWfUq+LI2wfRsQHumFC2IuT4guO/Vs5FJGXw
Arrvv7VPyKwZ8cpcZn9ka1K0N7su7QiGnzOhS3n2THaj25alE6TMXnrKmt6yIiXh
amsKVEKPPzHpw9ldTao1aG7vVNC9QXC8i9uQTWhhokxvSNw5OYFFkDZC5jD7McvB
AgMBAAE=
-----END PUBLIC KEY-----

Однако вызов метода завершается неудачно, возвращая false со строкой ошибки error:0906D06C:PEM routines:PEM_read_bio:no start line

Является ли ключ publi c недействительным? Напомним, что мой код начинается с ключевого модуля publi c и его экспоненты и преобразуется в формат PEM с использованием алгоритма, опубликованного здесь .

Вот полный сценарий:

<?php

function createPemFromModulusAndExponent($n, $e)
{
    $modulus = urlsafeB64Decode($n);
    $publicExponent = urlsafeB64Decode($e);
    $components = array(
        'modulus' => pack('Ca*a*', 2, encodeLength(strlen($modulus)), $modulus),
        'publicExponent' => pack('Ca*a*', 2, encodeLength(strlen($publicExponent)), $publicExponent)
    );

    $RSAPublicKey = pack('Ca*a*a*', 48, encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])), $components['modulus'], $components['publicExponent']);

    $rsaOID = pack('H*', '300d06092a864886f70d0101010500');
    $RSAPublicKey = chr(0) . $RSAPublicKey;
    $RSAPublicKey = chr(3) . encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;
    $RSAPublicKey = pack('Ca*a*', 48, encodeLength(strlen($rsaOID . $RSAPublicKey)), $rsaOID . $RSAPublicKey);

    $RSAPublicKey = "-----BEGIN PUBLIC KEY-----" . chunk_split(base64_encode($RSAPublicKey), 64) . '-----END PUBLIC KEY-----';
    return $RSAPublicKey;
}

function urlsafeB64Decode($input)
{
    $remainder = strlen($input) % 4;
    if ($remainder)
    {
        $padlen = 4 - $remainder;
        $input .= str_repeat('=', $padlen);
    }
    return base64_decode(strtr($input, '-_', '+/'));
}

function encodeLength($length)
{
    if ($length <= 0x7F)
    {
        return chr($length);
    }

    $temp = ltrim(pack('N', $length), chr(0));
    return pack('Ca*', 0x80 | strlen($temp), $temp);
}

$key = createPemFromModulusAndExponent('iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ', 'AQAB');

print_r($key);

print_r(openssl_pkey_get_public($key));

print_r(openssl_error_string());

Ответы [ 2 ]

2 голосов
/ 05 апреля 2020

Первое: openssl_pkey_get_public предназначено либо для прямой загрузки c ключа непосредственно , либо извлечения его из сертификата, как описано в документации параметра certificate openssl_pkey_get_public.

Для этой проблемы уже зарегистрировано сообщение об ошибке # 75643 от De c 2017 (версия 7.1.12), которое имеет статус Нет обратной связи и в настоящее время приостановлено (обратите внимание, что # 75643 фактически ссылается на openssl_public_encrypt, который, однако, использует тот же лог c относительно ключа, что и openssl_pkey_get_public, здесь ):

Ожидается ошибка в очереди. Если вы предоставляете строку как PEM (строка без префикса «file: //», которая будет являться путем к файлу), тогда сначала проверяется сертификат (с использованием PEM_ASN1_read_bio). Это означает, что произошел сбой, и ошибка сохраняется в очереди. Однако эта очередь является просто копией OpenSSL, которая очищается. После этого ключ загружается с использованием PEM_read_bio_PUBKEY, что успешно в вашем случае, поэтому вы получите результат. Подводя итог, можно сказать, что openssl_error_string не означает, что операция завершилась неудачно, а просто что была выдана какая-то ошибка ...

. В соответствии с этим сообщение об ошибке вызвано невозможностью извлечь ключ из сертификата. , Однако обработка продолжается и ключ загружается напрямую. Другими словами, сообщение об ошибке появляется, как и ожидалось, при загрузке ключа напрямую и может быть проигнорировано в этом контексте (по крайней мере, если прямая загрузка прошла успешно).

Для записей: As из 7.2 (.17), отображается немного другое сообщение об ошибке: ошибка: 0909006 C: процедуры PEM: get_name: нет начальной строки .


Обновление:

Как отметил @President James Moveon Polk в своем комментарии, createPemFromModulusAndExponent неправильно генерирует ключ. Если первый / самый значимый байт больше 0x7F, модулю должен предшествовать 0x00 байт, чего в настоящее время не происходит. Например, в опубликованном коде модуль начинается (декодируется Base64url) с 0x88, что означает, что сгенерированный (= опубликованный) ключ недействителен. Если 0x00 добавляется вручную и значение с таким исправлением (в кодировке Base64url) передается в createPemFromModulusAndExponent, то следующие, теперь действительные ключевые результаты:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiGaLqP6y+SJCCBq5Hv6p
GDbG/SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInq
UvjJur++hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPyg
jLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk+ILjv1bORSRl
8AK677+1T8isGfHKXGZ/ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl
4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw+zHL
wQIDAQAB
-----END PUBLIC KEY-----

Конечно, это было бы лучше, если бы createPemFromModulusAndExponent сделал это исправление автоматически. @ Президент Джеймс Мовеон Полк подал заявку на это, здесь .

0 голосов
/ 11 апреля 2020

Позвольте мне предложить альтернативный способ, который немного проще и лаконичнее. Используя phpseclib,

require __DIR__ . '/vendor/autoload.php';

use phpseclib\Math\BigInteger;
use phpseclib\Crypt\RSA;

$rsa = new RSA;
$rsa->loadKey([
    'e' => new BigInteger(base64_decode('AQAB'), 256),
    'n' => new BigInteger(base64_decode('iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ'), 256)
]);

print_r(openssl_pkey_get_public($rsa));

Код, который вы используете, фактически использует код, который был поднят из phpseclib 2.0. См. https://github.com/dragosgaftoneanu/okta-simple-jwt-verifier/issues/1#issuecomment -612503921 для получения дополнительной информации.

...