PEMとDERというものがあるがどういう違いがあるのか、ssh-keygenで作成される鍵とopenssl genrsaで作成される鍵は違うのか、など気になったので調査してまとめてみる。
形式について
PKCSという規格群が存在する。
この中でPKCS#1という規格があり、この中でRSA暗号の秘密鍵や公開鍵のフォーマットが規定されている。
openssl genrsa
で生成されるのはこちら。
RFC 8017 - PKCS #1: RSA Cryptography Specifications Version 2.2
PKCS#8という規格も存在し、RSA暗号に限らない秘密鍵のフォーマットが規定されている。こちらも使用されることがある。例えばJavaの標準クラスで読み込みが可能なのはこちらの形式。
PKCS8EncodedKeySpec (Java Platform SE 8)
RFC 5958 - Asymmetric Key Packages
PKCS#1やPKCS#8の中ではASN.1 (Abstract Syntax Notation One) という形式でフォーマットが記述されている。 ASN.1は抽象的な形でフォーマットを記述する記法であり、具体的なバイナリ列にするエンコード方法は定義されない。 ASN.1は暗号技術の文脈に限らず利用される。
Abstract Syntax Notation One - Wikipedia
RFC 6025 - ASN.1 Translation
具体的にはこのような形式で記述される。 (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を直接読める。
参考
- 『プロフェッショナルSSL/TLS』
- TSLについて詳しく解説されている。一部参考にしました。
- 無料で読める『OpenSSLクックブック』もどうぞ。