繁体   English   中英

PHP:将 DER 私钥转换为 PEM 格式(椭圆曲线)

[英]PHP: Convert DER private key to PEM format (Elliptic Curve)

在这里生成了测试密钥:

openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
openssl ec -in vapid_private.pem -pubout -out vapid_public.pem
openssl ec -in vapid_private.pem -pubout -outform DER | tail -c 65 | base64 | tr -d '=' | tr '/+' '_-' > vapid_public.der
openssl ec -in vapid_private.pem -outform DER | tail -c +8 | head -c 32 | base64 | tr -d '=' | tr '/+' '_-' > vapid_private.der

vapid_private.pem

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICBP2ZDHFWjIfnJXDCXFMIQIXRwQofx531ikc2R3RK+qoAoGCCqGSM49
AwEHoUQDQgAEtHLtsp1L1Jaqj/IEueEvp9yvG3wg1POAlzEf77NsM9qTMFBQiBPj
Bu8tzN5lLXnjKMy4I+AATyOI7Kz2C8RVWg==
-----END EC PRIVATE KEY-----

vapid_public.pem

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtHLtsp1L1Jaqj/IEueEvp9yvG3wg
1POAlzEf77NsM9qTMFBQiBPjBu8tzN5lLXnjKMy4I+AATyOI7Kz2C8RVWg==
-----END PUBLIC KEY-----

vapid_private.pem

IE_ZkMcVaMh-clcMJcUwhAhdHBCh_HnfWKRzZHdEr6o

vapid_public.pem

BLRy7bKdS9SWqo_yBLnhL6fcrxt8INTzgJcxH--zbDPakzBQUIgT4wbvLczeZS154yjMuCPgAE8jiOys9gvEVVo

然后我有处理转换的转换器类。 4 次转换中有 3 次正常工作,我无法让私有 DER→PEM 工作。 这是执行此操作的缩小(剥离错误处理等)功能:

function convertECPrivateKeyFromDERtoPEM(string $der, string $passphrase = null): string
{
    $fullDer = "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00" . self::base64_decode_url($der);
    return "-----BEGIN EC PRIVATE KEY-----\n" . chunk_split(base64_encode($fullDer), 64, "\n") . "-----END EC PRIVATE KEY-----";
}

这种方法适用于公钥(当然有不同的页眉和页脚),它是由其他开发人员较早实现的。 我需要添加对私钥的支持,我很难获得正确的 PEM 密钥。 提到的函数返回类似 PEM 的密钥,但它不是有效的椭圆曲线密钥 - 它不能用openssl_pkey_get_private()加载。

我看过一些图书馆,但是:

  • simplito/elliptic-php :我看不到对生成 PEM 的支持,因此需要在库周围添加额外的代码
  • mdanter/ecc :它不加载原始 DER 密钥,我需要手动添加 ASN1 标头,因此它并不比当前方法更好
  • web-token/jwt-framework :它有JWKFactory::createFromKey()尝试解析 DER,然后 PEM 作为后备,但是当我传递 DER 密钥时,我得到InvalidArgumentException: Unable to load the key

完全相同的密钥对可以使用 PERL 正确转换:

perl -E 'use Crypt::PK::ECC; use MIME::Base64;my $k = Crypt::PK::ECC->new->import_key_raw(MIME::Base64::decode_base64url("IE_ZkMcVaMh-clcMJcUwhAhdHBCh_HnfWKRzZHdEr6o"), "prime256v1"); say $k->export_key_pem("private_short")'

并且可以获得与生成 DER 的 PEM 密钥完全相同的 PEM 密钥,所以这绝对是可能的,但我无法让 PHP 去做。

来自 OpenSSL 的错误:

error:0D07209B:asn1 encoding routines:ASN1_get_object:too long
error:0D068066:asn1 encoding routines:asn1_check_tlen:bad object header
error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error
error:10092010:elliptic curve routines:d2i_ECPrivateKey:EC lib
error:100DE08E:elliptic curve routines:old_ec_priv_decode:decode error
error:0D07209B:asn1 encoding routines:ASN1_get_object:too long
error:0D068066:asn1 encoding routines:asn1_check_tlen:bad object header
error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error
error:0907B00D:PEM routines:PEM_read_bio_PrivateKey:ASN1 lib

我不太喜欢密码学,所以我发现很难找到解决方案。 提前感谢您的帮助!

最后两个发布的密钥不是 DER 编码的数据,而是 Base64url 编码的原始私钥和 Base64url 编码的未压缩公钥(这是0x04|<x>|<y>的串联)。

使用 mdanter/ecc 可以将原始密钥转换为 PEM 密钥:

use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer;
use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer;
use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer;
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;

$nist = EccFactory::getNistCurves();
$generator = $nist->generator256(); // prime256v1
$adapter = EccFactory::getAdapter();

// Create SEC1-PEM from raw private key
$rawPrivKey = gmp_import(base64_decode_url("IE_ZkMcVaMh-clcMJcUwhAhdHBCh_HnfWKRzZHdEr6o"));
$privKey = $generator->getPrivateKeyFrom($rawPrivKey);
$derPrivKeySerializer = new DerPrivateKeySerializer($adapter);
$pemPrivKesSerializer = new PemPrivateKeySerializer($derPrivKeySerializer);
$privKeyPem = $pemPrivKesSerializer->serialize($privKey);
print($privKeyPem . PHP_EOL);

// Create X.509-PEM from uncompressed public key
$uncompressedPubKey = base64_decode_url("BLRy7bKdS9SWqo_yBLnhL6fcrxt8INTzgJcxH--zbDPakzBQUIgT4wbvLczeZS154yjMuCPgAE8jiOys9gvEVVo");
$x = gmp_import(substr($uncompressedPubKey, 1, 32));
$y = gmp_import(substr($uncompressedPubKey,33, 32));
$pubKey = $generator->getPublicKeyFrom($x, $y);
$derPubKeySerializer = new DerPublicKeySerializer($adapter);
$pemPubKeySerializer = new PemPublicKeySerializer($derPubKeySerializer);
$pubKeyPem = $pemPubKeySerializer->serialize($pubKey);
print($pubKeyPem . PHP_EOL);

// Helper
function base64_decode_url( $data ){
    return base64_decode( strtr( $data, '-_', '+/') . str_repeat('=', 3 - ( 3 + strlen( $data )) % 4 ));
}

两个 PEM 密钥都与发布的 PEM 密钥匹配。


另一种方法是自己创建 ASN.1/DER 编码,利用同一曲线的某些字节序列是相同的,并且只需要替换修改后的数据,即原始私钥和未压缩的公钥。 这似乎是您用来转换公钥的方法。 这篇文章中,您将找到对这种方法和各种字节序列的详细描述,尤其是文章末尾的 prime256v1。 下面显示了私有 PEM 密钥:

function convertFromRawToSec1PEM(string $rawPrivate, string $uncompressedPublic, string $passphrase = null): string
{
    $fullDer = hex2bin("30770201010420") . base64_decode_url($rawPrivate) . hex2bin("a00a06082a8648ce3d030107a144034200") . base64_decode_url($uncompressedPublic);
    return "-----BEGIN EC PRIVATE KEY-----\n" . chunk_split(base64_encode($fullDer), 64, "\n") . "-----END EC PRIVATE KEY-----";
}

$rawPrivate = "IE_ZkMcVaMh-clcMJcUwhAhdHBCh_HnfWKRzZHdEr6o";
$uncompressedPublic = "BLRy7bKdS9SWqo_yBLnhL6fcrxt8INTzgJcxH--zbDPakzBQUIgT4wbvLczeZS154yjMuCPgAE8jiOys9gvEVVo";
$sec1Pem = convertFromRawToSec1PEM($rawPrivate, $uncompressedPublic);
print($sec1Pem . PHP_EOL);

PEM 私钥与发布的 PEM 私钥匹配。


第一种方法是通用的,可以快速适应其他曲线。 但是,这会花费对库的依赖。
第二种方法不需要库,可以更紧凑地实现,但准备起来更复杂,因为必须确定字节序列(明确地或使用同一曲线的已知密钥),原始密钥必须集成到其中正确的位置。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM