Ed25519 は、32 バイトの秘密鍵を用いて 64 バイトの署名を生成し、32 バイトの公開鍵を用いて検証できるデジタル署名アルゴリズムである。
- Ed25519: high-speed high-security signatures
- RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
このアルゴリズムで用いる鍵は、基本的には公開鍵・秘密鍵ともに 32 バイトのバイナリデータである。
しかし、このデータを様々なフォーマットに格納して表現することがある。
この記事では、このようなフォーマットを紹介する。
生データ
秘密鍵は、32 バイトの任意のバイナリデータである。(十分ランダムであることが望ましい)
公開鍵は、秘密鍵から決まる 32 バイトのバイナリデータである。
これらのバイナリデータをそのまま扱う。
例として、Ed25519 Online Tool では、十六進文字列 (hex) や Base64 などで鍵のデータを入出力する。
この記事では、「生データ」として十六進文字列を掲載する。
Ed25519 Online Tool の Meta や、Software で公開されているテストケース sign.input
など、秘密鍵の後に公開鍵を繋げて 64 バイトのバイナリデータ 1 個で表すこともある。
DER (Distinguished Encoding Rules) と PEM
DER は、様々な種類の鍵を表すことができるバイナリ形式である。
PEM は、DER 形式のバイナリデータを Base64 形式でエンコードし、ヘッダとフッタを追加したテキスト形式である。
PEM における Base64 データには、64 文字ごとに改行が入る。
Ed25519 Keys (PEM, DER, and Raw, with PKCS8 and OpenSSH)
で、DER 形式や PEM 形式の Ed25519 鍵を生成できる。
ASN.1: DER and PEM formats
で、生成した DER 形式の鍵の中身を確認できる。
秘密鍵
以下が、上記サイトで生成した秘密鍵の例である。
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIK7FHB6hqARWlPPXrJFowXzEoLjtQ2MQSHdZ27Wwf8o9
-----END PRIVATE KEY-----
これに含まれる Base64 データをデコードし、hexdump 形式に変換すると、以下のようになる。
00000000 30 2e 02 01 00 30 05 06 03 2b 65 70 04 22 04 20 |0....0...+ep.". |
00000010 ae c5 1c 1e a1 a8 04 56 94 f3 d7 ac 91 68 c1 7c |.......V.....h.||
00000020 c4 a0 b8 ed 43 63 10 48 77 59 db b5 b0 7f ca 3d |....Cc.HwY.....=|
このデータを解釈すると、以下のようになる。
30 : 以下のデータは列 (SEQUENCE) である
2e : 以下に続く (列の) データは 0x2e (46) バイトである
02 : 以下のデータは整数 (INTEGER) である
01 : 以下に続く (整数の) データは 1 バイトである
00 : 整数の値 0
30 : 以下のデータは列 (SEQUENCE) である
05 : 以下に続く (列の) データは 5 バイトである
06 : 以下のデータは ObjectIdentifier である
03 : 以下に続く (ObjectIdentifier の) データは 3 バイトである
2b 65 70 : ObjectIdentifier (1.3.101.112)
04 : 以下のデータはバイト列 (OCTETSTRING) である
22 : 以下に続く (バイト列の) データは 0x22 (34) バイトである
04 : 以下のデータはバイト列 (OCTETSTRING) である
20 : 以下に続く (バイト列の) データは 0x20 (32) バイトである
ae c5 1c 1e a1 a8 04 56 94 f3 d7 ac 91 68 c1 7c : バイト列
c4 a0 b8 ed 43 63 10 48 77 59 db b5 b0 7f ca 3d : バイト列
ObjectIdentifier を表す最初のバイトは、ObjectIdentifier の最初の数値 (この例では 1) の 40 倍と 2 番目の数値 (この例では 3) の和である。
今回扱う ObjectIdentifier には 127 以下の数値しか使われていないため、ObjectIdentifier の残りのバイトでそれぞれ ObjectIdentifier の残りの数値を表す。
今回の ObjectIdentifier は RFC 8410 で定義されており、Ed25519 を表す。
最後に格納されているバイト列
aec51c1ea1a8045694f3d7ac9168c17cc4a0b8ed436310487759dbb5b07fca3d
が、今回の秘密鍵の生データである。
公開鍵
以下が、上記サイトで生成した公開鍵の例である。
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAdUlN6rACl5fMXoSQ8WGEdSJvyrny7u3cCOMc4QIbP+s=
-----END PUBLIC KEY-----
これに含まれる Base64 データをデコードし、hexdump 形式に変換すると、以下のようになる。
00000000 30 2a 30 05 06 03 2b 65 70 03 21 00 75 49 4d ea |0*0...+ep.!.uIM.|
00000010 b0 02 97 97 cc 5e 84 90 f1 61 84 75 22 6f ca b9 |.....^...a.u"o..|
00000020 f2 ee ed dc 08 e3 1c e1 02 1b 3f eb |..........?.|
このデータを解釈すると、以下のようになる。
30 : 以下のデータは列 (SEQUENCE) である
2a : 以下に続く (列の) データは 0x2a (42) バイトである
30 : 以下のデータは列 (SEQUENCE) である
05 : 以下に続く (列の) データは 5 バイトである
06 : 以下のデータは ObjectIdentifier である
03 : 以下に続く (ObjectIdentifier の) データは 3 バイトである
2b 65 70 : ObjectIdentifier (1.3.101.112)
03 : 以下のデータはビット列 (BITSTRING) である
21 : 以下に続く (ビット列の) データは 0x21 (33) バイトである
00 : このビット列の最後のバイトで無視するビットは 0 個である
75 49 4d ea b0 02 97 97 cc 5e 84 90 f1 61 84 75 : ビット列
22 6f ca b9 f2 ee ed dc 08 e3 1c e1 02 1b 3f eb : ビット列
ObjectIdentifier は、秘密鍵と同じである。
秘密鍵と比べると、以下の違いがある。
- 列に整数 0 が含まれていない
- 秘密鍵は 2 層のバイト列で表されていたが、公開鍵は 1 層のビット列で表されている
最後に格納されているビット列 (無視するビットは無いので、実質バイト列)
75494deab0029797cc5e8490f1618475226fcab9f2eeeddc08e31ce1021b3feb
が、今回の公開鍵の生データである。
参考資料
- X.690 : Information technology - ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER)
- ASN.1 でのオブジェクトID (OID) とバイト列の表記 | 晴耕雨読
- LDAPWiki: Distinguished Encoding Rules
- PEM file format
- RFC 8410 - Algorithm Identifiers for Ed25519, Ed448, X25519, and X448 for Use in the Internet X.509 Public Key Infrastructure
JWK (JSON Web Key)
JWK は、JSON で鍵のデータを表す形式である。
Ed25519 の場合は、(最低限) 以下のメンバを持つオブジェクトである。
メンバ | 値 |
---|---|
kty |
鍵の種類 "OKP"
|
crv |
鍵のサブタイプ "Ed25519"
|
x |
公開鍵 (32 バイト) を base64url エンコードした文字列 |
d |
秘密鍵 (32 バイト) を base64url エンコードした文字列 |
メンバ d
は、秘密鍵を表す場合は必須、公開鍵を表す場合は使用禁止である。
例として、以下の鍵ペア (生データ) を用いる。
98B86775D43D277656331E4365328142E87B3902739B259D63CC951381CC816E
6BE62C0BEB39394A4387254AC2302E842517720881E3B07F65CC638D733EF02A
これを JWK で表すと、たとえば以下のようになる。
{
"kty": "OKP",
"crv": "Ed25519",
"x": "a-YsC-s5OUpDhyVKwjAuhCUXcgiB47B_ZcxjjXM-8Co",
"d": "mLhnddQ9J3ZWMx5DZTKBQuh7OQJzmyWdY8yVE4HMgW4"
}
{
"kty": "OKP",
"crv": "Ed25519",
"x": "a-YsC-s5OUpDhyVKwjAuhCUXcgiB47B_ZcxjjXM-8Co"
}
参考資料
- RFC 7517 - JSON Web Key (JWK)
- RFC 8037 - CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE)
SSH 鍵 (OpenSSH 形式)
以下のコマンドで、Ed25519 形式の SSH 鍵を生成できる。
このコマンドは、たとえば JSLinux の Alpine Linux 3.12.0 で使用できる。
ssh-keygen -t ed25519 -C test -f key
以下のオプションを指定している。
オプション | 意味 |
---|---|
-t ed25519 |
生成する鍵の種類の指定 |
-C test |
鍵に埋め込むコメントの指定 |
-f key |
出力する秘密鍵のファイル名の指定 |
コマンドを実行するとパスフレーズを聞かれるので、空のまま Enter を押す。
(2 回聞かれるので、2 回 Enter を押す)
たとえば、以下のファイルが生成された。
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACArTXcobTyxSkWCM3h/A4oWFp/lX9mTusdsfRi67qi3zgAAAIg/X4KuP1+C
rgAAAAtzc2gtZWQyNTUxOQAAACArTXcobTyxSkWCM3h/A4oWFp/lX9mTusdsfRi67qi3zg
AAAECNpuWSoC0NikEF5Omw31ytIYHW/qlr8x/9EKqMofZSdytNdyhtPLFKRYIzeH8DihYW
n+Vf2ZO6x2x9GLruqLfOAAAABHRlc3QB
-----END OPENSSH PRIVATE KEY-----
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICtNdyhtPLFKRYIzeH8DihYWn+Vf2ZO6x2x9GLruqLfO test
秘密鍵
秘密鍵に含まれる Base64 データをデコードし、hexdump 形式に変換すると、以下のようになる。
00000000 6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00 00 |openssh-key-v1..|
00000010 00 00 04 6e 6f 6e 65 00 00 00 04 6e 6f 6e 65 00 |...none....none.|
00000020 00 00 00 00 00 00 01 00 00 00 33 00 00 00 0b 73 |..........3....s|
00000030 73 68 2d 65 64 32 35 35 31 39 00 00 00 20 2b 4d |sh-ed25519... +M|
00000040 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 16 9f |w(m<.JE.3x......|
00000050 e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce 00 00 |._....l}........|
00000060 00 88 3f 5f 82 ae 3f 5f 82 ae 00 00 00 0b 73 73 |..?_..?_......ss|
00000070 68 2d 65 64 32 35 35 31 39 00 00 00 20 2b 4d 77 |h-ed25519... +Mw|
00000080 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 16 9f e5 |(m<.JE.3x.......|
00000090 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce 00 00 00 |_....l}.........|
000000a0 40 8d a6 e5 92 a0 2d 0d 8a 41 05 e4 e9 b0 df 5c |@.....-..A.....\|
000000b0 ad 21 81 d6 fe a9 6b f3 1f fd 10 aa 8c a1 f6 52 |.!....k........R|
000000c0 77 2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a |w+Mw(m<.JE.3x...|
000000d0 16 16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 |...._....l}.....|
000000e0 ce 00 00 00 04 74 65 73 74 01 |.....test.|
このデータを解釈すると、以下のようになる。
数値はビッグエンディアンである。
6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00 : ヘッダ "openssh-key-v1" + NUL
00 00 00 04 : 暗号化方式の名前の長さ
6e 6f 6e 65 : 暗号化形式 none
00 00 00 04 : 鍵派生方式の名前の長さ
6e 6f 6e 65 : 鍵派生方式 none
00 00 00 00 : 鍵派生方式のパラメータの長さ
00 00 00 01 : このデータに含まれる鍵ペアの数
00 00 00 33 : 公開鍵情報の長さ
00 00 00 0b : 鍵の種類の長さ
73 73 68 2d 65 64 32 35 35 31 39 : 鍵の種類 ssh-ed25519
00 00 00 20 : 公開鍵データの長さ
2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 : 公開鍵データ
16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce : 公開鍵データ
00 00 00 88 : 秘密鍵情報の長さ
3f 5f 82 ae : 復号チェック用のデータ
3f 5f 82 ae : 復号チェック用のデータ (前のものと同じにする)
00 00 00 0b : 鍵の種類の長さ
73 73 68 2d 65 64 32 35 35 31 39 : 鍵の種類 ssh-ed25519
00 00 00 20 : 公開鍵データの長さ
2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 : 公開鍵データ
16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce : 公開鍵データ
00 00 00 40 : 鍵データの長さ
8d a6 e5 92 a0 2d 0d 8a 41 05 e4 e9 b0 df 5c ad : 鍵データ (秘密鍵)
21 81 d6 fe a9 6b f3 1f fd 10 aa 8c a1 f6 52 77 : 鍵データ (秘密鍵)
2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 : 鍵データ (公開鍵)
16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce : 鍵データ (公開鍵)
00 00 00 04 : コメントの長さ
74 65 73 74 : コメント
01 : パディング
パディングは、「秘密鍵情報の長さ」がブロックサイズ (暗号化なしの場合は 8 バイト) の倍数になるよう、01 02 03 04 …
という感じで値が 1 ずつ増えるバイト列を格納する。
パディングより前の部分だけで「秘密鍵情報の長さ」がブロックサイズの倍数になっている場合は、パディングは格納しない。
以下が、このデータに含まれる秘密鍵の生データである。
8da6e592a02d0d8a4105e4e9b0df5cad2181d6fea96bf31ffd10aa8ca1f65277
公開鍵
公開鍵に含まれる Base64 データをデコードし、hexdump 形式に変換すると、以下のようになる。
00000000 00 00 00 0b 73 73 68 2d 65 64 32 35 35 31 39 00 |....ssh-ed25519.|
00000010 00 00 20 2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f |.. +Mw(m<.JE.3x.|
00000020 03 8a 16 16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee |......_....l}...|
00000030 a8 b7 ce |...|
このデータを解釈すると、以下のようになる。
数値はビッグエンディアンである。
00 00 00 0b : 鍵の種類の長さ
73 73 68 2d 65 64 32 35 35 31 39 : 鍵の種類 ssh-ed25519
00 00 00 20 : 公開鍵データの長さ
2b 4d 77 28 6d 3c b1 4a 45 82 33 78 7f 03 8a 16 : 公開鍵データ
16 9f e5 5f d9 93 ba c7 6c 7d 18 ba ee a8 b7 ce : 公開鍵データ
以下が、このデータに含まれる公開鍵の生データである。
2b4d77286d3cb14a458233787f038a16169fe55fd993bac76c7d18baeea8b7ce
暗号化された秘密鍵
同様のコマンドで SSH 鍵を生成し、パスフレーズとして a
を入力すると、たとえば以下の秘密鍵が得られた。
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA8Y/N2lw
ZQF8a/0330sZCYAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIC25J6amkFnAz1Ps
WxEHmTKY/GQIClhPrxr2MfTm/4aaAAAAkHSsmTSFjcKDOyr+dybLMe2smPOwwOviivNaxv
3ZRSYPjz9uPAeFbP1/5rjl0qT5jNjnSexxoK+Rr+zuatVa/pWoTgl/7KIzDiRUv3/kOirO
ogD+Oagmg228H27Yzpnlo3i5ZPjCeBUgLOeCR9g+Vs6G48SSoBJrC5K0s4pm+cQ/H998wj
PIghDYjV9/tEr7CA==
-----END OPENSSH PRIVATE KEY-----
これに含まれる Base64 データをデコードし、hexdump 形式に変換すると、以下のようになる。
00000000 6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00 00 |openssh-key-v1..|
00000010 00 00 0a 61 65 73 32 35 36 2d 63 74 72 00 00 00 |...aes256-ctr...|
00000020 06 62 63 72 79 70 74 00 00 00 18 00 00 00 10 3c |.bcrypt........<|
00000030 63 f3 76 97 06 50 17 c6 bf d3 7d f4 b1 90 98 00 |c.v..P....}.....|
00000040 00 00 10 00 00 00 01 00 00 00 33 00 00 00 0b 73 |..........3....s|
00000050 73 68 2d 65 64 32 35 35 31 39 00 00 00 20 2d b9 |sh-ed25519... -.|
00000060 27 a6 a6 90 59 c0 cf 53 ec 5b 11 07 99 32 98 fc |'...Y..S.[...2..|
00000070 64 08 0a 58 4f af 1a f6 31 f4 e6 ff 86 9a 00 00 |d..XO...1.......|
00000080 00 90 74 ac 99 34 85 8d c2 83 3b 2a fe 77 26 cb |..t..4....;*.w&.|
00000090 31 ed ac 98 f3 b0 c0 eb e2 8a f3 5a c6 fd d9 45 |1..........Z...E|
000000a0 26 0f 8f 3f 6e 3c 07 85 6c fd 7f e6 b8 e5 d2 a4 |&..?n<..l.......|
000000b0 f9 8c d8 e7 49 ec 71 a0 af 91 af ec ee 6a d5 5a |....I.q......j.Z|
000000c0 fe 95 a8 4e 09 7f ec a2 33 0e 24 54 bf 7f e4 3a |...N....3.$T...:|
000000d0 2a ce a2 00 fe 39 a8 26 83 6d bc 1f 6e d8 ce 99 |*....9.&.m..n...|
000000e0 e5 a3 78 b9 64 f8 c2 78 15 20 2c e7 82 47 d8 3e |..x.d..x. ,..G.>|
000000f0 56 ce 86 e3 c4 92 a0 12 6b 0b 92 b4 b3 8a 66 f9 |V.......k.....f.|
00000100 c4 3f 1f df 7c c2 33 c8 82 10 d8 8d 5f 7f b4 4a |.?..|.3....._..J|
00000110 fb 08 |..|
暗号化されていない秘密鍵と比べると、以下の違いがある。
- 暗号方式が
none
ではなくaes256-ctr
になっている - 鍵派生方式が
none
ではなくbcrypt
になっている - 鍵派生方式のパラメータの長さが正になっている
- 秘密鍵情報 (0x82 バイト目以降) が暗号化されている
- ブロックサイズが、8 バイトではなく (
aes256-ctr
の場合) 16 バイトである
鍵派生用のパラメータは、以下の構造になっている。
00 00 00 18 : 鍵派生用のパラメータの長さ
00 00 00 10 : salt の長さ
ed 83 6d ec 64 e6 35 33 78 db ed 8b c3 c4 d8 9e : salt
00 00 00 10 : ラウンド数
この暗号を解読して秘密鍵を取り出す方法は、面倒そうなので本記事では扱わない。
参考資料
- The OpenSSH Private Key Format
- The OpenSSH private key binary format – Marin Atanasov Nikolov – A place about Open Source Software, Operating Systems and some random thoughts
- OpenSSH's bcrypt implementation · phpseclib
まとめ
Ed25519 用の鍵の格納形式として、以下のフォーマットを紹介した。
- 生データ
- DER
- PEM
- JWK
- SSH 鍵 (OpenSSH 形式)