JWTの署名検証などで利用する公開鍵をJWK形式に変換する

# はじめに
JWTの署名検証などの用途で鍵を作成した際、JWKSエンドポイントで公開するためにJWK形式に変換する必要があることがあります。
変換の際は[pem-jwk - npm](https://www.npmjs.com/package/pem-jwk)などを利用するのが簡単ですが、そういった外部モジュールを利用することが出来ない環境にいる場合のために、Linuxに標準搭載されていることが多いコマンドだけを使って変換する方法を記載します。

# 鍵の作成とJWTの生成
## RS256
### 鍵の作成
#### 秘密鍵
RS256形式の秘密鍵を作成するには、以下のコマンドを実行します。(鍵の名前「RS256_private.pem」は任意です)
```
# openssl genrsa 2048 > RS256_private.pem
```
秘密鍵の情報を確認するには以下のコマンドを実行します。
```
# openssl rsa -text < RS256_private.pem
```
```text:実行例
# openssl rsa -text < RS256_private.pem
Private-Key: (2048 bit, 2 primes)
modulus:
    00:be:6d:67:7a:7c:fd:75:2b:89:3e:98:3c:41:2b:
    4d:4a:08:52:82:65:67:d9:5d:f6:27:e5:87:76:64:
     -- 省略 --
    3b:c0:e2:84:0d:40:ab:57:ea:b9:da:a9:2f:22:e4:
    b6:a1:02:72:e4:1f:db:b1:3d:5b:1b:c8:df:21:91:
    b8:33
publicExponent: 65537 (0x10001)
privateExponent:
    5e:1f:63:5b:46:b6:5c:98:c5:40:7c:2c:a4:25:8c:
    11:40:a5:cf:af:e1:b1:ad:e0:c2:7d:90:04:33:dc:
     -- 省略 --
    9b:54:d5:bf:2d:ed:1c:79:e4:64:ef:45:7c:68:c4:
    ce:73:47:16:e0:23:d6:25:ec:31:9c:9e:a5:e6:6a:
    29
prime1:
    00:e0:d2:f7:0d:13:7a:4d:6c:21:9d:93:c4:d5:d7:
    99:1c:98:87:a7:29:56:55:70:3e:25:6d:2d:ad:13:
     -- 省略 --
    f1:ed:a9:8c:e8:05:58:f2:d4:3b:6a:15:51:1b:35:
    cd:a1:a9:25:09:8c:04:b0:0d
prime2:
    00:d8:d5:63:a2:0d:3f:b5:fa:18:ae:dd:ec:84:3e:
    9a:f3:07:14:5c:e7:46:94:d2:72:85:01:d0:14:54:
     -- 省略 --
    48:da:fe:d3:a1:70:cf:30:e7:fc:94:9a:0d:bd:98:
    67:11:67:40:d9:10:b5:b9:3f
exponent1:
    22:38:fa:d2:4a:bc:bb:3c:52:81:a2:14:bc:58:6d:
    3e:25:19:c1:20:51:bb:ff:b1:f6:10:80:7b:70:18:
     -- 省略 --
    39:ef:2d:3b:c4:54:9d:e2:20:89:2d:65:83:36:d5:
    6e:5e:48:fd:03:0b:53:a1
exponent2:
    77:1f:24:11:4e:fe:05:57:54:60:1d:03:66:d3:8e:
    04:9d:8b:0e:4e:f0:73:a9:f9:c8:20:6a:d4:00:31:
     -- 省略 --
    b5:ae:f1:4c:eb:d0:a7:a9:8b:15:88:5c:ac:20:21:
    df:69:be:32:81:ff:93:b9
coefficient:
    63:fa:b6:d3:17:95:f3:97:0a:b2:de:b2:34:0b:85:
    e9:b5:f0:ca:9c:2d:c4:7e:7a:60:3b:74:ae:54:d9:
     -- 省略 --
    e0:fa:e2:83:32:f3:23:18:86:61:50:f1:72:9e:4f:
    1c:17:a0:05:93:0f:9c:8b
writing RSA key
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC+bWd6fP11K4k+
mDxBK01KCFKCZWfZXfYn5Yd2ZJ0PwDNPbQR93jzzY4Nl+K8MLdmhym4lDEs2nDzL
     -- 省略 --
qNKuS03A1cKNhwIsY+hUOgqmqtcab/Y6PEH0+1RnCInh3Jq1bPI/OZHg+uKDMvMj
GIZhUPFynk8cF6AFkw+ciw==
-----END PRIVATE KEY-----
```

#### 公開鍵
秘密鍵から公開鍵を抽出するには、以下のコマンドを実行します。(鍵の名前「RS256_public.pem」は任意です)
```
# openssl rsa -in RS256_private.pem -pubout > RS256_public.pem
```
公開鍵の情報を確認するには以下のコマンドを実行します。
```
# openssl rsa -pubin -text < RS256_public.pem
```
```text:実行例
# openssl rsa -pubin -text < RS256_public.pem
Public-Key: (2048 bit)
Modulus:
    00:be:6d:67:7a:7c:fd:75:2b:89:3e:98:3c:41:2b:
    4d:4a:08:52:82:65:67:d9:5d:f6:27:e5:87:76:64:
    9d:0f:c0:33:4f:6d:04:7d:de:3c:f3:63:83:65:f8:
    af:0c:2d:d9:a1:ca:6e:25:0c:4b:36:9c:3c:cb:27:
    83:3b:38:7d:73:b8:e9:d5:e7:64:88:f2:b5:ae:b3:
    4a:c2:ca:70:24:35:08:ca:1b:12:97:ca:cb:03:b6:
    1c:fe:0f:d1:5d:d7:e5:cc:e9:8e:2c:07:b5:c3:0b:
    10:b8:bf:dd:20:1f:54:87:87:c2:65:22:5c:96:9d:
    1d:af:6a:b7:82:4c:34:3d:4a:b6:82:01:14:46:b9:
    be:1a:64:b5:a4:ea:cd:d5:1c:d5:38:02:bb:76:17:
    01:03:b5:ed:70:4f:02:96:1b:c7:35:47:e3:e5:51:
    f6:44:da:06:53:58:33:63:e3:32:a5:1d:7e:a0:55:
    7a:74:8b:42:b7:1c:01:70:84:a1:59:9d:61:83:b4:
    d1:3e:c8:01:91:18:df:93:43:22:ba:95:98:aa:af:
    78:27:18:a3:99:66:4a:d7:04:f0:a3:06:2a:6c:24:
    3b:c0:e2:84:0d:40:ab:57:ea:b9:da:a9:2f:22:e4:
    b6:a1:02:72:e4:1f:db:b1:3d:5b:1b:c8:df:21:91:
    b8:33
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvm1nenz9dSuJPpg8QStN
SghSgmVn2V32J+WHdmSdD8AzT20Efd4882ODZfivDC3ZocpuJQxLNpw8yyeDOzh9
c7jp1edkiPK1rrNKwspwJDUIyhsSl8rLA7Yc/g/RXdflzOmOLAe1wwsQuL/dIB9U
h4fCZSJclp0dr2q3gkw0PUq2ggEURrm+GmS1pOrN1RzVOAK7dhcBA7XtcE8ClhvH
NUfj5VH2RNoGU1gzY+MypR1+oFV6dItCtxwBcIShWZ1hg7TRPsgBkRjfk0MiupWY
qq94JxijmWZK1wTwowYqbCQ7wOKEDUCrV+q52qkvIuS2oQJy5B/bsT1bG8jfIZG4
MwIDAQAB
-----END PUBLIC KEY-----
```

### JWK形式への変換
公開鍵をJWK形式に変換するには、Modulus部分を利用します。
頭の「00」を除き、残りの部分を1行にまとめます。（スペースも削除）
例として
```text:Modulus
    00:be:6d:67:7a:7c:fd:75:2b:89:3e:98:3c:41:2b:
    4d:4a:08:52:82:65:67:d9:5d:f6:27:e5:87:76:64:
    9d:0f:c0:33:4f:6d:04:7d:de:3c:f3:63:83:65:f8:
    af:0c:2d:d9:a1:ca:6e:25:0c:4b:36:9c:3c:cb:27:
    83:3b:38:7d:73:b8:e9:d5:e7:64:88:f2:b5:ae:b3:
    4a:c2:ca:70:24:35:08:ca:1b:12:97:ca:cb:03:b6:
    1c:fe:0f:d1:5d:d7:e5:cc:e9:8e:2c:07:b5:c3:0b:
    10:b8:bf:dd:20:1f:54:87:87:c2:65:22:5c:96:9d:
    1d:af:6a:b7:82:4c:34:3d:4a:b6:82:01:14:46:b9:
    be:1a:64:b5:a4:ea:cd:d5:1c:d5:38:02:bb:76:17:
    01:03:b5:ed:70:4f:02:96:1b:c7:35:47:e3:e5:51:
    f6:44:da:06:53:58:33:63:e3:32:a5:1d:7e:a0:55:
    7a:74:8b:42:b7:1c:01:70:84:a1:59:9d:61:83:b4:
    d1:3e:c8:01:91:18:df:93:43:22:ba:95:98:aa:af:
    78:27:18:a3:99:66:4a:d7:04:f0:a3:06:2a:6c:24:
    3b:c0:e2:84:0d:40:ab:57:ea:b9:da:a9:2f:22:e4:
    b6:a1:02:72:e4:1f:db:b1:3d:5b:1b:c8:df:21:91:
    b8:33
```
上記の値を
```text
be:6d:67:7a:7c:fd:75:2b:89:3e:98:3c:41:2b:4d:4a:08:52:82:65:67:d9:5d:f6:27:e5:87:76:64:9d:0f:c0:33:4f:6d:04:7d:de:3c:f3:63:83:65:f8:af:0c:2d:d9:a1:ca:6e:25:0c:4b:36:9c:3c:cb:27:83:3b:38:7d:73:b8:e9:d5:e7:64:88:f2:b5:ae:b3:4a:c2:ca:70:24:35:08:ca:1b:12:97:ca:cb:03:b6:1c:fe:0f:d1:5d:d7:e5:cc:e9:8e:2c:07:b5:c3:0b:10:b8:bf:dd:20:1f:54:87:87:c2:65:22:5c:96:9d:1d:af:6a:b7:82:4c:34:3d:4a:b6:82:01:14:46:b9:be:1a:64:b5:a4:ea:cd:d5:1c:d5:38:02:bb:76:17:01:03:b5:ed:70:4f:02:96:1b:c7:35:47:e3:e5:51:f6:44:da:06:53:58:33:63:e3:32:a5:1d:7e:a0:55:7a:74:8b:42:b7:1c:01:70:84:a1:59:9d:61:83:b4:d1:3e:c8:01:91:18:df:93:43:22:ba:95:98:aa:af:78:27:18:a3:99:66:4a:d7:04:f0:a3:06:2a:6c:24:3b:c0:e2:84:0d:40:ab:57:ea:b9:da:a9:2f:22:e4:b6:a1:02:72:e4:1f:db:b1:3d:5b:1b:c8:df:21:91:b8:33
```
にします。
この値を以下のコマンドで変換します。
```text
# echo -n '<上記の値>' | tr -dc '[0-9a-f]' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '='
```
```text:実行例
# echo -n 'be:6d:67:7a:7c:fd:75:2b:89:3e:98:3c:41:2b:4d:4a:08:52:82:65:67:d9:5d:f6:27:e5:87:76:64:9d:0f:c0:33:4f:6d:04:7d:de:3c:f3:63:83:65:f8:af:0c:2d:d9:a1:ca:6e:25:0c:4b:36:9c:3c:cb:27:83:3b:38:7d:73:b8:e9:d5:e7:64:88:f2:b5:ae:b3:4a:c2:ca:70:24:35:08:ca:1b:12:97:ca:cb:03:b6:1c:fe:0f:d1:5d:d7:e5:cc:e9:8e:2c:07:b5:c3:0b:10:b8:bf:dd:20:1f:54:87:87:c2:65:22:5c:96:9d:1d:af:6a:b7:82:4c:34:3d:4a:b6:82:01:14:46:b9:be:1a:64:b5:a4:ea:cd:d5:1c:d5:38:02:bb:76:17:01:03:b5:ed:70:4f:02:96:1b:c7:35:47:e3:e5:51:f6:44:da:06:53:58:33:63:e3:32:a5:1d:7e:a0:55:7a:74:8b:42:b7:1c:01:70:84:a1:59:9d:61:83:b4:d1:3e:c8:01:91:18:df:93:43:22:ba:95:98:aa:af:78:27:18:a3:99:66:4a:d7:04:f0:a3:06:2a:6c:24:3b:c0:e2:84:0d:40:ab:57:ea:b9:da:a9:2f:22:e4:b6:a1:02:72:e4:1f:db:b1:3d:5b:1b:c8:df:21:91:b8:33' | tr -dc '[0-9a-f]' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '='
vm1nenz9dSuJPpg8QStNSghSgmVn2V32J-WHdmSdD8AzT20Efd4882ODZfivDC3ZocpuJQxLNpw8yyeDOzh9c7jp1edkiPK1rrNKwspwJDUIyhsSl8rLA7Yc_g_RXdflzOmOLAe1wwsQuL_dIB9Uh4fCZSJclp0dr2q3gkw0PUq2ggEURrm-GmS1pOrN1RzVOAK7dhcBA7XtcE8ClhvHNUfj5VH2RNoGU1gzY-MypR1-oFV6dItCtxwBcIShWZ1hg7TRPsgBkRjfk0MiupWYqq94JxijmWZK1wTwowYqbCQ7wOKEDUCrV-q52qkvIuS2oQJy5B_bsT1bG8jfIZG4Mw
```
この値をnとして、以下のJWKを作成します。
```json:JWK
{
    "kty":"RSA",
    "n": "<作成した値>",
    "e":"AQAB",
    "alg":"RS256",
    "kid":"<任意の値>"
}
```
※opensslを利用している場合、デフォルトではeの値は「AQAB」となります。公開鍵の中身を確認し、Exponentの値が65537となっていれば「AQAB」です。

## ES256
### 鍵の作成
#### 秘密鍵
ES256形式の秘密鍵を作成するには、以下のコマンドを実行します。(鍵の名前「ES256_private.pem」は任意です)
```
# openssl ecparam -name prime256v1 -genkey -noout > ES256_private.pem
```
秘密鍵の情報を確認するには以下のコマンドを実行します。
```
# openssl ec -text < ES256_private.pem
```
```text:実行例
# openssl ec -text < ES256_private.pem
read EC key
Private-Key: (256 bit)
priv:
    65:f0:0d:4e:8f:fc:d2:6c:41:69:82:04:0f:10:6c:
     -- 省略 --
    3f:7f
pub:
    04:59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:
     -- 省略 --
    fb:6e:db:a8:37
ASN1 OID: prime256v1
NIST CURVE: P-256
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGXwDU6P/NJsQWmCBA8QbKTo5zg12NEQYufUCArquz9/oAoGCCqGSM49
-- 省略 --
wYz4SheDTWhhbR0iEQxSvXzoObz7btuoNw==
```

#### 公開鍵
秘密鍵から公開鍵を抽出するには、以下のコマンドを実行します。(鍵の名前「ES256_public.pem」は任意です)
```
# openssl ec -in ES256_private.pem -pubout > ES256_public.pem
```
公開鍵の情報を確認するには以下のコマンドを実行します。
```
# openssl ec -pubin -text < ES256_public.pem
```
```text:実行例
# openssl ec -pubin -text < ES256_public.pem
read EC key
Public-Key: (256 bit)
pub:
    04:59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:
    c0:48:1e:1d:7b:46:b3:6d:59:94:08:6a:23:f6:e4:
    b8:22:fc:27:0a:25:ba:c0:87:49:c1:8c:f8:4a:17:
    83:4d:68:61:6d:1d:22:11:0c:52:bd:7c:e8:39:bc:
    fb:6e:db:a8:37
ASN1 OID: prime256v1
NIST CURVE: P-256
writing EC key
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWaunybCbS62HqklGWKPASB4de0az
bVmUCGoj9uS4IvwnCiW6wIdJwYz4SheDTWhhbR0iEQxSvXzoObz7btuoNw==
-----END PUBLIC KEY-----
```

### JWK形式への変換
公開鍵をJWK形式に変換するには、pub部分を利用します。
頭の一文字を除き、残りの「:」で区切られた2桁の16進数の群を半分に分割します。
例として
```text:pub
    04:59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:
    c0:48:1e:1d:7b:46:b3:6d:59:94:08:6a:23:f6:e4:
    b8:22:fc:27:0a:25:ba:c0:87:49:c1:8c:f8:4a:17:
    83:4d:68:61:6d:1d:22:11:0c:52:bd:7c:e8:39:bc:
    fb:6e:db:a8:37
```
上記の値を
```text
    04:
```
```text:pub-1/2
       59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:
    c0:48:1e:1d:7b:46:b3:6d:59:94:08:6a:23:f6:e4:
    b8:22:fc:
```
```text:pub-2/2
             27:0a:25:ba:c0:87:49:c1:8c:f8:4a:17:
    83:4d:68:61:6d:1d:22:11:0c:52:bd:7c:e8:39:bc:
    fb:6e:db:a8:37
```
に分割し、一行にまとめます。（スペースも削除）
```text:pub-1/2
59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:c0:48:1e:1d:7b:46:b3:6d:59:94:08:6a:23:f6:e4:b8:22:fc:
```
```text:pub-2/2
27:0a:25:ba:c0:87:49:c1:8c:f8:4a:17:83:4d:68:61:6d:1d:22:11:0c:52:bd:7c:e8:39:bc:fb:6e:db:a8:37
```
これらの値を以下のコマンドで変換します。
```text
# echo -n '<上記の値>' | tr -dc '[0-9a-f]' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '='
```
```text:実行例
# echo -n '59:ab:a7:c9:b0:9b:4b:ad:87:aa:49:46:58:a3:c0:48:1e:1d:7b:46:b3:6d:59:94:08:6a:23:f6:e4:b8:22:fc:' | tr -dc '[0-9a-f]' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '='
WaunybCbS62HqklGWKPASB4de0azbVmUCGoj9uS4Ivw

# echo -n '27:0a:25:ba:c0:87:49:c1:8c:f8:4a:17:83:4d:68:61:6d:1d:22:11:0c:52:bd:7c:e8:39:bc:fb:6e:db:a8:37' | tr -dc '[0-9a-f]' | xxd -r -p | base64 | tr '+/' '-_' | tr -d '='
JwolusCHScGM-EoXg01oYW0dIhEMUr186Dm8-27bqDc
```
pub-1/2を変換した値をx、pub-2/2を変換した値をyとして、以下のJWKを作成します。
```json:JWK
{
    "kty": "EC",
    "crv": "P-256",
    "kid": "<任意の値>",
    "x": "<pub-1/2を変換した値>",
    "y": "<pub-2/2を変換した値>"
}
```

# 参考文献
この記事は以下の情報を参考にして執筆しました。

- [RFC7517（JWKについて）](https://openid-foundation-japan.github.io/rfc7517.ja.html)
- [JWTハンドブック](https://auth0.com/resources/ebooks/jp-jwt-handbook)