2020/07/29
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);
}
以上です。