JSON形式の公開鍵をPEM形式に変換する方法をご紹介させて頂きます。
OpenID Connectの署名検証によく使われる証明書公開鍵のJSONフォーマットでは exponent(e)と modulus(n)として表されます。
SeciossLinkの場合こちらでご確認頂けます。
{ "keys": [ { "kty": "RSA", "alg": "RS256", "use": "sig", "kid": "bea608b769586866dacb65405a0522f8", "n": "sV4L1Ujt5N4nt1g1taaZpk-R5woC46vZej3wDe0D6WLdhs-UWd3AaG75X5qdNnThd1zpeoF2tp8p-TMeY6KpQxcAIXeGyvs0ZUiG8XivNYdYH8DoWlZt7qvPtkYbCM9_iyFZCRw3t8y_OePMzeYAQA4Of_Ygn4RKt9Oz-ty9HF0chDMA5GpsQWq3CPk5asKQ8rXYfYVWn6Fy6QKIrsQLhje7tUytp3DEOIAxAPQ19qNJ135wwADL_XzeXoDanBEfJQfYeW82UAIobfn6nFqgwUZCUpo6pJYv09SRCYUBpr08FhIwziom22yAwGS8ff-BK847H0VeoZB2n7OscaXKVw", "e": "AQAB" } ] }
|
RHEL7 PHPで実装される場合、pem から JSON(exponent & modulus)へ
$key = new Crypt_RSA(); $rsa->loadKey(trim(file_get_contents('PublicKey.pem'))); $n = urlEncode($rsa->modulus->toBytes()); $e = urlEncode($rsa->publicExponent->toBytes()); |
JSON(exponent & modulus)から pem へ
$key = new Crypt_RSA(); $key->loadKey([ 'e' => new Math_BigInteger(urlDecode($key['e']), 256), 'n' => new Math_BigInteger(urlDecode($key['n']), 256), ]);
|
すごく簡単ですが、php-phpseclib-crypt-rsa が RHEL8 に非対応なので、OpenSSL関数に置き換える。
pem から JSON(exponent & modulus)へ
$key= openssl_pkey_get_details(openssl_pkey_get_public(trim(file_get_contents('PublicKey.pem')))); $n = urlEncode($key['rsa']['n']); $e = urlEncode($key['rsa']['e']); |
JSON(exponent & modulus)から pem へ の変換は、少し複雑になりました。
まずは、RHEL7と同じように、exponent & modulus を計算します
$exponent = bcdechex(new Math_BigInteger(urlDecode($key['e']), 256)); $modulus = bcdechex(new Math_BigInteger(urlDecode($key['n']), 256));
public static function bchexdec($hex) { if (strlen($hex) == 1) { return hexdec($hex); } else { $remain = substr($hex, 0, -1); $last = substr($hex, -1); return bcadd(bcmul(16, bchexdec($remain)), hexdec($last)); } } public static function bcdechex($dec) { $last = bcmod($dec, 16); $remain = bcdiv(bcsub($dec, $last), 16); if ($remain == 0) { return dechex($last); } else { return bcdechex($remain).dechex($last); } } |
そうしたら、計算結果のexponent & modulusを次のファイル(def.asn1)に置き換えて保存
# Start with a SEQUENCE asn1=SEQUENCE:pubkeyinfo # pubkeyinfo contains an algorithm identifier and the public key wrapped # in a BIT STRING [pubkeyinfo] algorithm=SEQUENCE:rsa_alg pubkey=BITWRAP,SEQUENCE:rsapubkey # algorithm ID for RSA is just an OID and a NULL [rsa_alg] algorithm=OID:rsaEncryption parameter=NULL # Actual public key: modulus and exponent [rsapubkey] n=INTEGER:0x$modulus e=INTEGER:0x$exponent
|
そして、def.asn1を基にDERファイルを生成
openssl asn1parse -genconf /tmp/def.asn1 -out /tmp/pubkey.der -noout |
最後は、DERからPEMへ変換
openssl rsa -in /tmp/pubkey.der -inform der -pubin -out /tmp/pubkey.pem |
上記の手順をプログラムかすると
exec(echo -e "asn1=SEQUENCE:pubkeyinfo\n[pubkeyinfo]\nalgorithm=SEQUENCE:rsa_alg\npubkey=BITWRAP,SEQUENCE:rsapubkey\n[rsa_alg]\nalgorithm=OID:rsaEncryption\nparameter=NULL\n[rsapubkey]\nn=INTEGER:0x$modulus\ne=INTEGER:0x$exponent" | openssl asn1parse -genconf /dev/stdin -noout -out /dev/stdout | base64) |
※ DERとPEMのASN.1 key構造について、こちらでご参照ください。
0 30 159: SEQUENCE { 3 30 13: SEQUENCE { 5 06 9: OBJECT IDENTIFIER '1 2 840 113549 1 1 1' 16 05 0: NULL : } 18 03 141: BIT STRING 0 unused bits, encapsulates { 22 30 137: SEQUENCE { 25 02 129: INTEGER : 00 EB 11 E7 B4 46 2E 09 BB 3F 90 7E 25 98 BA 2F : C4 F5 41 92 5D AB BF D8 FF 0B 8E 74 C3 F1 5E 14 : 9E 7F B6 14 06 55 18 4D E4 2F 6D DB CD EA 14 2D : 8B F8 3D E9 5E 07 78 1F 98 98 83 24 E2 94 DC DB : 39 2F 82 89 01 45 07 8C 5C 03 79 BB 74 34 FF AC : 04 AD 15 29 E4 C0 4C BD 98 AF F4 B7 6D 3F F1 87 : 2F B5 C6 D8 F8 46 47 55 ED F5 71 4E 7E 7A 2D BE : 2E 75 49 F0 BB 12 B8 57 96 F9 3D D3 8A 8F FF 97 : 73 157 02 3: INTEGER 65537 : } : } : }
|
---------------------------------------- 分 割 線 ----------------------------------------
やはり面倒ですね。。。
それでは、次の方法でやりましょうか
...... $key = $this->getKeys($kid); $pem = $this->buildPemKey($key['e'], $key['n']); $key_or_secret = openssl_pkey_get_public(trim($pem)); $valid = $jws->verify($key_or_secret); ......
public function buildPemKey($e, $n) { $alg = pack("H*", "300D06092A864886F70D0101010500"); $exp = $this->_makeAsnSegment(0x02, Akita_JOSE_Base64::urlDecode($e)); $mod = $this->_makeAsnSegment(0x02, Akita_JOSE_Base64::urlDecode($n)); $seq = $this->_makeAsnSegment(0x30, $mod.$exp); $bit = $this->_makeAsnSegment(0x03, $seq); $seg = $this->_makeAsnSegment(0x30, $alg.$bit); $b64 = base64_encode($seg); $res = "-----BEGIN PUBLIC KEY-----\n"; $offset = 0; while ($tmp = substr($b64, $offset, 64)) { $res = $res.$tmp."\n"; $offset += 64; } $res = $res."-----END PUBLIC KEY-----\n"; $publicKey = openssl_pkey_get_public($res); $pkey = openssl_pkey_get_details($publicKey); return $res; }
function _makeAsnSegment($type, $string) { switch ($type) { case 0x02: if (ord($string) > 0x7f) { $string = chr(0).$string; } break; case 0x03: $string = chr(0).$string; break; } $length = strlen($string); if ($length < 128) { $output = sprintf("%c%c%s", $type, $length, $string); } elseif ($length < 0x0100) { $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); } elseif ($length < 0x010000) { $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string); } else { $output = null; } return($output); }
|
以上です。