【问题标题】:Passing public key in PEM format to openssl_pkey_get_public gives error:0906D06C:PEM routines:PEM_read_bio:no start line将 PEM 格式的公钥传递给 openssl_pkey_get_public 会出现错误:0906D06C:PEM 例程:PEM_read_bio:no start line
【发布时间】:2020-07-16 19:37:36
【问题描述】:

以下 PEM 格式的公共 RSA 密钥已提供给 openssl_pkey_get_public。

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

公钥无效吗?作为记录,我的代码从公钥模数和指数开始,并使用发布在here 的算法将其转换为 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());

【问题讨论】:

  • 已确认:1) 如果从证书中提取公钥,则不会显示错误消息。 2)错误信息不影响操作的结果(这至少适用于签名的验证)。
  • @Topaco:不根据文档。它可以准确读取 op 提供的密钥,事实上,这个例子对我来说在 PHP 7.4.4 中运行良好
  • @Topaco:啊,我明白了。是的,错误信息存在,你是对的。并且密钥可以用来加密。但是我发现了密钥的另一个问题:它 not 有效,因为模数是负数!我猜openssl_pkey_get_public() 只是忽略了负模数并将它们视为正数。这确实意味着生成 PEM 编码密钥的任何代码都是错误的。
  • 我打开了一个issue 关于这个错误。
  • @PresidentJamesMoveonPolk - 是的,你是对的。一种可能的解决方法:由于createPemFromModulusAndExponent 期望模数为(Base64Url 编码的)十六进制字符串,它可以以已经正确的符号传递。然后将生成一个有效的密钥。当然,更好的方法是修复createPemFromModulusAndExponent

标签: php openssl cryptography rsa


【解决方案1】:

首先:openssl_pkey_get_public 旨在加载公钥直接或从证书中提取它,如certificate 参数的文档中所述openssl_pkey_get_public.

从 2017 年 12 月(版本 7.1.12)开始,已经为此问题提交了一个错误,#75643,其状态为 No Feedback,目前已暂停(请注意,实际上是 #75643指的是openssl_public_encrypt,但它使用与openssl_pkey_get_publichere相同的密钥逻辑:

队列中的错误是预期的。如果您提供字符串作为 PEM (字符串不以“file://”为前缀,这将是一个文件路径),然后 首先尝试证书(使用 PEM_ASN1_read_bio)。这意味着它 失败并且错误被保存到队列中。然而这个队列只是 已清空的 OpenSSL 副本。之后,密钥被加载 使用 PEM_read_bio_PUBKEY 这在你的情况下是成功的,所以你得到 回结果。总结起来 openssl_error_string 并不意味着 操作失败但只是发出了一些错误...

据此,该错误消息是由于未能从证书中提取密钥引起的。但是,继续处理并直接加载密钥。换句话说,错误消息在直接加载键时按预期出现,在这种情况下可以忽略(至少在直接加载成功的情况下)。

记录:从 7.2(.17) 开始,显示的错误消息略有不同:error:0909006C:PEMroutines:get_name:no start line


更新:

正如@President James Moveon Polk 在他的评论中指出的那样,createPemFromModulusAndExponent 没有正确生成密钥。如果第一个/最高有效字节大于0x7F,则模数前面必须有一个0x00 字节,目前不会发生这种情况。例如。在发布的代码中,模数以0x88 开头(Base64url 解码),这意味着生成的(= 发布的)密钥无效。如果手动添加了 0x00,并且将如此更正的值(Base64url 编码)传递给 createPemFromModulusAndExponent,则以下,现在 有效键结果:

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

当然,如果createPemFromModulusAndExponent 能够自动进行此更正会更好。 @President James Moveon Polk 为此提交了一个问题,here

【讨论】:

  • 你能展示你用来将 0 字节添加到模数之前的代码吗?我将$modulus = chr(0x00) . $modulus; 行添加到createPemFromModulusAndExponent 的顶部并获得了不同的PEM 密钥。
  • 这是对的,但必须在模数的Base64解码之后执行,即urlsafeB64Decode($n)-调用之后。
【解决方案2】:

请允许我提出一种更简单、更简洁的替代方法。使用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 了解更多信息。

【讨论】:

    猜你喜欢
    • 2017-02-18
    • 2019-01-31
    • 2018-07-04
    • 2018-11-01
    • 1970-01-01
    • 2020-11-07
    • 2014-04-30
    • 1970-01-01
    • 2019-10-16
    相关资源
    最近更新 更多