🔑

RSA鍵のフォーマットについて

2023/04/25に公開2

元々 https://htlsne.hatenablog.com/entry/2020/09/10/234227 に書いていた記事を、Zennにも転載しておきます。

はじめに

PEMとDERというものがあるがどういう違いがあるのか、ssh-keygenで作成される鍵とopenssl genrsaで作成される鍵は違うのか、など気になったので調査してまとめてみる。

形式について

PKCSという規格群が存在する。

https://ja.wikipedia.org/wiki/PKCS

この中でPKCS#1という規格があり、この中でRSA暗号の秘密鍵や公開鍵のフォーマットが規定されている。openssl genrsaで生成されるのはこちら。

https://tools.ietf.org/html/rfc8017

PKCS#8という規格も存在し、RSA暗号に限らない秘密鍵のフォーマットが規定されている。こちらも使用されることがある。例えばJavaの標準クラスで読み込みが可能なのはこちらの形式。

https://docs.oracle.com/javase/jp/8/docs/api/java/security/spec/PKCS8EncodedKeySpec.html
https://tools.ietf.org/html/rfc5958

PKCS#1やPKCS#8の中ではASN.1 (Abstract Syntax Notation One) という形式でフォーマットが記述されている。ASN.1は抽象的な形でフォーマットを記述する記法であり、具体的なバイナリ列にするエンコード方法は定義されない。ASN.1は暗号技術の文脈に限らず利用される。

https://ja.wikipedia.org/wiki/Abstract_Syntax_Notation_One
https://tools.ietf.org/html/rfc6025

具体的にはこのような形式で記述される。(RFCのAppendix Aを参照。)

RSAPrivateKey ::= SEQUENCE {
    version           Version,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1)
    exponent2         INTEGER,  -- d mod (q-1)
    coefficient       INTEGER,  -- (inverse of q) mod p
    otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

ASN.1で記述されたものをエンコードする方法として、DER (Distinguished Encoding Rules) というものが存在する。他にもBERなどエンコード方法は存在するが、DERは一通りにエンコード方法が定まる点が優れている。DERはテキストとは限らないバイナリ列になる。DERをBASE64化して、改行を入れたりヘッダを入れたりしてテキスト形式にしたのがPEM形式 (Privacy Enhanced Mail) 。テキストの方が扱いやすいためか、PEMの方が見ることが多い。

つまりPKCS#1のPEMとか、PKCS#8のDERとか、これだけで2 * 2 = 4通りある。

ややこしいことに、OpenSSHはバージョン7.8以降から別の形式を採用している。このバージョンから、ssh-keygenで生成される鍵フォーマットがPEM形式から独自形式 (OpenSSH format) に変更になった。これはPKCS#1ともPKCS#8とも異なる形式で、RFC4716で定義されている。見た目はBASE64化されておりPEMに似ているが、厳密にはPEMと異なる形式らしい。

https://www.openssh.com/txt/release-7.8
https://tools.ietf.org/html/rfc4716#section-3.5

ここまでのまとめ

一旦まとめる。フォーマットとして大きく異なるPKCS#1とPKCS#8がある。PEMやDERはそのエンコード(符号化)方法。全く違う形式としてOpenSSH形式がある。

鍵の中身を見てみる

opensslコマンドで鍵の中身を確認することができる。-nooutオプションでPEM形式の出力を抑制し、-textオプションで内容をテキスト形式で表示する。PKCS#1でもPKCS#8でも可能だが、以下はPKCS#1の例。なおOpenSSH形式は無理そう。
(長いので...部分は省略している)

$ openssl rsa -in a.pem -noout -text
RSA Private-Key: (4096 bit, 2 primes)
modulus:
    00:b0:e7:d4:47:2c:63:9c:f6:a8:20:b2:8b:f0:3f:
    ...
publicExponent: 65537 (0x10001)
privateExponent:
    31:a8:21:72:09:07:bd:1b:8f:7d:fe:20:41:c3:b8:
    ...
prime1:
    00:eb:94:66:41:e2:e8:42:af:4d:dc:b3:3b:bc:f0:
    ...
prime2:
    00:c0:3d:6d:ff:98:4c:6c:39:e9:5b:fe:47:ab:20:
    ...
exponent1:
    00:b9:96:e9:90:52:60:43:d8:b3:70:81:4b:38:a8:
    ...
exponent2:
    00:b8:b5:cd:8d:3a:ce:a3:66:79:7c:74:b4:74:0e:
    ...
coefficient:
    64:8d:8c:58:fb:fe:8e:43:98:89:c1:2e:16:69:4d:
    ...

RSA暗号について知識があれば、原理通り主に2つの素数からなっていることが見て取れる。

生成する

PKCS#1

openrsa genrsa

これだと標準出力に出力される。

openrsa genrsa 4096 -out a.pem

鍵の長さを指定したり、-outで出力ファイルを指定したりすることが多い。パスフレーズを指定したい場合は、-aes256 オプションをつければ良い。

ssh-keygenでもオプションをつければ作れる。

ssh-keygen -m PEM

PKCS#8

ssh-keygenを使えば一応可能。

ssh-keygen -m PKCS8 -f openssh.key

OpenSSH形式

バージョン7.8以降のssh-keygenをそのまま使うだけ。-fは出力ファイル。

ssh-keygen -f openssh.key

形式を変換する

必要そうなものだけにして、網羅はしない。

PKCS#1からPKCS#8

opensslコマンドでできる。

openssl pkcs8 -in a.pem -topk8

パスフレーズが不要な場合は-nocryptオプションを付ける。

openssl pkcs8 -in p1.key -topk8 -nocrypt

-outformオプションでPEMかDERか指定できる。デフォルトはPEM。

openssl pkcs8 -in a.pem -topk8 -nocrypt -outform DER

これだと標準出力に出力されるので、ファイルに保存したい場合はリダイレクトするか-outオプションで出力ファイルを指定する。

PKCS#8からPKCS#1

openssl rsaでできる。

openssl rsa -in p8.key

-outformオプションでPEMかDERか指定できたり、-outで出力ファイルが指定できるのは同じ。

OpenSSH形式からPKCS#1

素直にはできない。ssh-keygenの、形式を指定する-mオプションと、パスフレーズを変更する-pオプションを組み合わせるとできる。ただし、元のファイルを置き換えてしまうので注意。

cp a.key b.key    # 置き換えてしまうので一旦コピー
ssh-keygen -f b.pem -m PEM -p

なおこの場合の-fオプションは入力ファイルを表す。

形式を見分ける

どこまで厳密化はわからないが、PEMのヘッダを見ればある程度わかる。

PKCS#1

-----BEGIN RSA PRIVATE KEY-----

PKCS#8(暗号化なし)

-----BEGIN PRIVATE KEY-----

PKCS#8(暗号化あり)

-----BEGIN ENCRYPTED PRIVATE KEY-----

OpenSSH形式

-----BEGIN OPENSSH PRIVATE KEY-----

Javaで読み込む

標準ライブラリで読めるのはおそらくPKCS#8だけ。DER形式のPKCS#8にすると読める。詳しくは以下を参照。

https://stackoverflow.com/a/19387517

Bouncy Castleを使えばPKCS#1を直接読める。

https://www.bouncycastle.org/

参考

Discussion

いわもと こういちいわもと こういち

RFC4716に書かれているのは「The Secure Shell (SSH) Public Key File Format」というタイトルのとおり公開鍵ファイルの形式です。Tectia SSH Server等で使われている公開鍵ファイルの形式ですね。
公開鍵ファイルの形式なので、OpenSSH形式の秘密鍵ファイルとは関係ありません。

OpenSSH形式の秘密鍵ファイルのフォーマットは、OpenSSHのソースに含まれているPROTOCOL.keyファイルに書かれています。
https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.key?rev=HEAD

kwikwi

細かいところですが、気が付いてしまったので:openssl genrsaopenrsa genrsaとtypoされているようです。大変よくまとまった記事をありがとうございます!