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