简体   繁体   English

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

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

I have test keys generated like here :在这里生成了测试密钥:

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 : vapid_private.pem

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

vapid_public.pem : vapid_public.pem

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

vapid_private.pem : vapid_private.pem

IE_ZkMcVaMh-clcMJcUwhAhdHBCh_HnfWKRzZHdEr6o

vapid_public.pem : vapid_public.pem

BLRy7bKdS9SWqo_yBLnhL6fcrxt8INTzgJcxH--zbDPakzBQUIgT4wbvLczeZS154yjMuCPgAE8jiOys9gvEVVo

Then I have converter class that handles conversion.然后我有处理转换的转换器类。 3 out of 4 conversions work properly, I can't get private DER→PEM to work. 4 次转换中有 3 次正常工作,我无法让私有 DER→PEM 工作。 This is minified (stripped error handling etc.) function that does it:这是执行此操作的缩小(剥离错误处理等)功能:

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-----";
}

This approach works properly for public key (of course with different header+footer), it was implemented earlier by other developer.这种方法适用于公钥(当然有不同的页眉和页脚),它是由其他开发人员较早实现的。 I need to add support for private keys and I struggle to get proper PEM key.我需要添加对私钥的支持,我很难获得正确的 PEM 密钥。 Mentioned function returns PEM-like key, but it's not valid Elliptic Curve key - it can't be loaded with openssl_pkey_get_private() .提到的函数返回类似 PEM 的密钥,但它不是有效的椭圆曲线密钥 - 它不能用openssl_pkey_get_private()加载。

I've looked at some libraries, but:我看过一些图书馆,但是:

  • simplito/elliptic-php : I don't see support for generating PEM, so it would require additional code around the library simplito/elliptic-php :我看不到对生成 PEM 的支持,因此需要在库周围添加额外的代码
  • mdanter/ecc : it does not load raw DER key, I need to manually add ASN1 header so it's not any better than current approach mdanter/ecc :它不加载原始 DER 密钥,我需要手动添加 ASN1 标头,因此它并不比当前方法更好
  • web-token/jwt-framework : it has JWKFactory::createFromKey() which tries to parse DER and then PEM as a fallback, but when I pass DER key I get InvalidArgumentException: Unable to load the key web-token/jwt-framework :它有JWKFactory::createFromKey()尝试解析 DER,然后 PEM 作为后备,但是当我传递 DER 密钥时,我得到InvalidArgumentException: Unable to load the key

The exact same key pair can be properly converted using PERL:完全相同的密钥对可以使用 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")'

and it's possible to get exact same PEM key like the one from which DER was generated, so it definitely is possible, but I can't get PHP to do it.并且可以获得与生成 DER 的 PEM 密钥完全相同的 PEM 密钥,所以这绝对是可能的,但我无法让 PHP 去做。

Errors from OpenSSL:来自 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

I'm not so much into cryptography so I find it hard to get solution.我不太喜欢密码学,所以我发现很难找到解决方案。 Thanks in advance for any help!提前感谢您的帮助!

The last two posted keys are not DER encoded data but the Base64url encoded raw private key and the Base64url encoded uncompressed public key (which is the concatenation of 0x04|<x>|<y> ).最后两个发布的密钥不是 DER 编码的数据,而是 Base64url 编码的原始私钥和 Base64url 编码的未压缩公钥(这是0x04|<x>|<y>的串联)。

The conversion of the raw to the PEM keys is possible with eg mdanter/ecc:使用 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 ));
}

Both PEM keys match the posted PEM keys.两个 PEM 密钥都与发布的 PEM 密钥匹配。


Another approach is to create the ASN.1/DER encoding yourself, exploiting that for the same curve certain byte sequences are identical and only the modified data needs to be replaced, ie the raw private key and the uncompressed public key.另一种方法是自己创建 ASN.1/DER 编码,利用同一曲线的某些字节序列是相同的,并且只需要替换修改后的数据,即原始私钥和未压缩的公钥。 This seems to be the approach you used to convert the public key.这似乎是您用来转换公钥的方法。 In this post you will find a detailed description of this approach and the various byte sequences, especially for prime256v1 at the end of the post.这篇文章中,您将找到对这种方法和各种字节序列的详细描述,尤其是文章末尾的 prime256v1。 In the following this is shown for the private PEM key:下面显示了私有 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);

The private PEM key matches the posted private PEM key. PEM 私钥与发布的 PEM 私钥匹配。


The first approach is generic and can be quickly adapted for other curves.第一种方法是通用的,可以快速适应其他曲线。 However, this costs the dependence on a library.但是,这会花费对库的依赖。
The second approach does not require a library, can be implemented more compactly, but is more elaborate to prepare because the byte sequences must be determined (explicitly or using a known key of the same curve), into which the raw keys must be integrated at the correct positions.第二种方法不需要库,可以更紧凑地实现,但准备起来更复杂,因为必须确定字节序列(明确地或使用同一曲线的已知密钥),原始密钥必须集成到其中正确的位置。

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

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