この付録では PuTTY が秘密鍵を格納するのに使うファイルフォーマットについて解説する。
この付録で、バイナリデータの構造は SSH プロトコル標準と同じように “uint32
”, “string
”, “mpint
” といったデータ型を使って表現する。これらは RFC 4251 第 5 節, “Data Type Representations Used in the SSH Protocols” で厳密に定義されている。
PPK ファイルは秘密鍵とそれに対応する公開鍵を格納している。どちらも同じファイルに含まれている。
このファイルフォーマットでは全く暗号化しないか、または秘密鍵を暗号化できる。 公開鍵はどちらの場合も平文で格納される。 (これによって PuTTY は公開鍵を SSH サーバに送信し、サーバがそれを受け入れるかどうかを確認できる。そして受け入れられない場合はパスフレーズをユーザに要求する必要がない。)
鍵ファイルが暗号化されている場合、パスフレーズから暗号化の鍵を導出する。暗号化された PPK ファイルは MAC (メッセージ認証) によって改竄検出できるようになっており、またこれには同じパスフレーズが使用される。 MAC によって改竄から保護されるのは秘密鍵だけでなく、他の平文の部分も同様である。そのため MAC を保ったまま公開鍵を変更できず、変更すると鍵ファイル自体が無効となる。
この MAC が鍵ファイルを暗号解読攻撃から守る。攻撃者が公開鍵を制御された方法で改竄し、生成される破損した署名から秘密鍵の情報を推測するのを防げるのである。 PPK ファイルへの攻撃は、MAC が不一致になるため検出できる。
(そのような攻撃は、鍵ファイルが攻撃者によって変更可能で、かつパスフレーズを入力するプロセスへのアクセスはできない状況でのみ意味がある。この状況はしかしあり得ない事ではない。例えば、あなたのホームディレクトリがネットワークサーバにあり、ファイルサーバの管理者が鍵ファイルにアクセスできるけれどもクライアントマシンのプロセスにはアクセスできない場合である。)
MAC は鍵のコメントも保護する。そのため攻撃者が、鍵を入れ替えてコメントを編集して偽装することも阻止できる。結果として、PuTTYgen はパスフレーズで鍵を復号しない限り鍵のコメントを編集できない。
(その攻撃が有用な状況はより限定される。一例として、あなたの接続するサーバが鍵によって特定の異なる動作を行うようになっていて、それらの一つが攻撃者によってより有用な場合である。しかし MAC があれば、更に特段の注意をしなくても通常は検知できる。)
PPK ファイルの外層はテキストベースになっている。 PuTTY ツールは常に LF 改行で書き出すが、読み込み時は CR+LF や CR のみの改行でも許容する。
最初の数行は PPK だと識別するものと、何がどのように格納されているかのデータである。次の通りになっている:
PuTTY-User-Key-File-version: algorithm-name
Encryption: encryption-type
Comment: key-comment-string
version はファイルフォーマット自体のバージョン番号を示す十進数である。現在のファイルフォーマットのバージョンは 3 である。
algorithm-name はこの鍵で使っている公開鍵アルゴリズムを SSH プロトコルで定義される識別子で表したものである。 (例えば “ssh-dss
” や “ecdsa-sha2-nistp384
”。)
encryption-type はこの鍵が暗号化されているかと、暗号化されている場合はその方式を示す。現在対応している暗号化の種類は “aes256-cbc
” と “none
” である。
key-comment-string はコメントのための自由なテキストフィールドである。 13 と 10 (CR と LF) 以外のバイト値を含められる。
ファイルの次の部分は公開鍵である。これは暗号化されていないが Base64 エンコードされていて (RFC 4648)、何行の Base64 データがあるかを表すヘッダが先行する。次の通りになる:
Public-Lines: number-of-lines
その行数 (number-of-lines) だけの Base64 データ
Base64 エンコードされたデータは SSH プロトコル自体でネットワークに送信するものとまったく同一の書式である。また OpenSSH の authorized_keys
ファイルの Base64 データとも同じ書式だが、PPK ファイルでは複数行に分割されている。しかし改行を削除すれば、出来上がる Base64 データは authorized_keys
に挿入できる形になる。
もし鍵が暗号化されている場合 (つまり encryption-type が “none
” でなければ)、次は暗号化するための鍵をパスフレーズからどのように導出するかを示した行になる:
Key-Derivation: argon2-flavour
Argon2-Memory: decimal-integer
Argon2-Passes: decimal-integer
Argon2-Parallelism: decimal-integer
Argon2-Salt: hex-string
argon2-flavour は “Argon2d
”, “Argon2i
”, “Argon2id
” のいずれかの識別子で、すべて Argon2 パスワードハッシュ関数の一種である。
次の 3 つの整数値は Argon2 のパラメータとして使われる。使用するメモリ量 (KiB)、何回アルゴリズムを実行するか (実行時間の調整のため)、そしてハッシュ関数の必要な並列数である。 salt (ソルト) はバイナリ文字列にデコードされて Argon2 への入力になる。 (鍵が保存される際にランダムに生成される。それによって複数の鍵ファイルでパスワードの推測を並行して実行できなくなる。)
ファイルの次の部分は秘密鍵である。これは公開鍵と同様に Base64 エンコードされている。
Private-Lines: number-of-lines
その行数 (number-of-lines) だけの Base64 データ
この Base64 エンコードされたバイナリデータは、前述のヘッダの encryption-type によっては暗号化されている場合がある:
none
” ならば、データは平文で格納されている。
aes256-cbc
” ならば、データは AES の鍵長 256 ビット CBC モードで暗号化されている。鍵と初期化ベクトルはパスフレーズから導出される: Section C.4 を参照のこと。
秘密鍵を AES で暗号化するために、鍵は 16 バイトの倍数でなくてはならない (AES 暗号のブロック長)。そのためにデータを暗号化する前にランダムなデータをパディングする。復号してからデコードするときは、ランダムなデータは無視できる: データの内部構造で十分に、どこで有意なデータが終わるかを判断できる。
公開鍵と異なり、秘密鍵のバイナリエンコードは SSH 標準に示されていない。 PuTTY の対応する秘密鍵の形式の詳細については section C.3 を参照のこと。
鍵ファイルの最後は MAC である:
Private-MAC: hex-mac-data
hex-mac-data は 16 進数エンコードされた 64 桁の値で (つまり 32 バイト)、以下のバイナリ入力から HMAC-SHA-256 アルゴリズムで生成される:
string
: algorithm-name ヘッダフィールド
string
: encryption-type ヘッダフィールド
string
: key-comment-string ヘッダフィールド
string
: バイナリの公開鍵データ, “Public-Lines
” ヘッダの後の Base64 データをデコードしたもの
string
: 平文状態のバイナリの秘密鍵データ, “Private-Lines
” ヘッダの後の Base64 データをデコードしたもの。もしデータが暗号化されて格納されていた場合、復号した状態で原像として MAC へ入力され、上述のランダムパディングも含む。
MAC の鍵はパスフレーズから導出される: section C.4 を参照のこと。
このセクションでは PuTTY の対応しているそれぞれの種類の鍵について秘密鍵の形式を解説する。
PPK フォーマットは公開鍵も含むため (そして公開鍵と秘密鍵の対応が一致するように同じ MAC で保護されるため)、公開鍵で記述した情報を秘密鍵でも繰り返す必要はない。そのためいくつかの形式は非常に短くなる。
すべての場合で、デコードするアプリケーションは復号した秘密鍵がどこで終わるか判断できる。そのため有意なデータの後のランダムなパディングは安全に無視できる。
RSA 鍵は algorithm-name が “ssh-rsa
” に設定されている。 (SHA-1 以外のハッシュを使った RSA 署名スキームでも同様である。)
公開鍵に鍵のモジュラスと公開指数は示されている。秘密鍵データに含まれるのは:
mpint
: 鍵の秘密復号化指数。
mpint
: 鍵の片方の素因数 p。
mpint
: 鍵のもう一方の素因数 q。 (この形式の RSA 鍵は丁度 2 つの素因数があるはずである。)
mpint
: q mod p の乗法逆元。
DSA 鍵は algorithm-name が “ssh-dss
” に設定されている。
公開鍵に鍵のパラメータ (大きな素数 p, 小さい素数 q, 群生成元 g) と公開鍵 y は示されている。秘密鍵データに含まれるのは:
mpint
: 秘密鍵 x, これは g mod p から生成された群に対する y の離散対数である。
NIST 楕円曲線鍵は以下のうちの一つの algorithm-name が設定されている。それぞれが別の楕円曲線と鍵長に対応する:
ecdsa-sha2-nistp256
”
ecdsa-sha2-nistp384
”
ecdsa-sha2-nistp521
”
公開鍵データに公開楕円曲線上の点は示されている。秘密鍵データに含まれるのは:
mpint
: 秘密指数, これは公開点の離散対数である。
訳注: 楕円曲線暗号においては秘密鍵は Q = d⋅G のスカラー d であり、通常は秘密指数とは呼びません。秘密鍵 d は公開鍵 Q の 生成元 G に対する楕円曲線上の離散対数問題 (ECDLP) の解です。
EdDSA 楕円曲線鍵は以下のうちの一つの algorithm-name が設定されている。それぞれが別の楕円曲線と鍵長に対応する:
ssh-ed25519
”
ssh-ed448
”
公開鍵データに公開楕円曲線上の点は示されている。秘密鍵データに含まれるのは:
mpint
: 秘密指数, これは公開点の離散対数である。
鍵ファイルが暗号化される際、パスフレーズから 3 つのキーデータを算出する必要がある:
もし encryption-type が “aes256-cbc
” ならば、共通鍵暗号の鍵長は 32 バイトで、IV は 16 バイト (1 ブロック) である。 MAC 鍵の長さも 32 バイトである。
もし encryption-type が “none
” ならば、これら 3 つのデータは 0 バイトである。 (MAC は生成され検証されるが、鍵長は 0 である。)
もしこれらデータの長さが 0 でなければ、パスフレーズが Argon2 鍵導出関数に渡される。その時のモードは鍵ファイルの “Key-Derivation
” ヘッダに示され、“Argon2-
Parameter:
” ヘッダのパラメータが与えられる。
(鍵が暗号化されていない場合、これらのヘッダはすべて省略され、Argon2 は全く実行されない。)
Argon2 はパスフレーズとソルトの他に 2 つ、秘密データ “secret
” と関連データ “ad
” の文字列入力を取る。 PPK の使う Argon2 では、どちらも空文字列になる。
Argon2 の出力長パラメータ “outlen
” (つまり出力するデータの長さ) はデータ項目すべての長さの合計になる。つまり、(鍵長 + IV の長さ + MAC の鍵長) である。出力データは共通鍵と IV と MAC 鍵がその順番で連結されているものとして扱われる。
つまり “aes256-cbc
” の場合、出力長は 32+16+32 = 80 バイトで、80 バイトのデータの先頭 32 バイトが 256 ビット AES 鍵、次の 16 バイトが CBC IV、そして最後の 32 バイトが HMAC-SHA-256 の鍵である。
PPK バージョン 2 は PuTTY 0.52 から 0.74 までで使われた。
PPK バージョン 2 では、MAC アルゴリズムに HMAC-SHA-1 を使う (そのため Private-MAC
は 40 桁の 16 進数である)。
“Key-Derivation:
” ヘッダとすべての “Argon2-
Parameter:
” ヘッダは存在しない。 Argon2 を使う代わりに、秘密データの暗号に使うキーデータは全く違う方法でパスフレーズから生成される。次の通り:
“aes256-cbc
” の暗号鍵は 2 つの SHA-1 ハッシュを生成して組み立てる。ハッシュ計算して、連結して、結果の最初の 32 バイトを利用する。 (つまり最初のハッシュ出力の 20 バイトすべてと、次のハッシュの先頭 12 バイト)。それぞれのハッシュの入力データ (原像) は次の通り:
uint32
: 連番。これは最初のハッシュ計算では 0 で、次は 1 である。 (これは将来の変更でより長い鍵が必要になった時に、連番を進めて更に別のハッシュを作成しようという考えからであった。)
PPK v2 では CBC 初期化ベクトルはすべて 0 である。
MAC の鍵長は 20 バイトで、以下のデータの単純な SHA-1 ハッシュである:
putty-private-key-file-mac-key
”, 文字列長フィールドは付けない。
PPK バージョン 1 は悪い設計の形式で、初期開発の間だけ使われた。そのため実用は推奨されない。
PPK バージョン 1 はリリースバージョンの PuTTY では一度も使われていない。初期開発スナップショットの 0.51 (SSH-2 公開鍵に全く対応していない) と 0.52 (PPK バージョン 2 を使っている) の間だけで使われた。私は PPK v1 ファイルが何にも使われていないことを期待している。しかし、いずれにせよ念のため、古くて悪い設計のフォーマットについて解説する。
In PPK version 1, the input to the MAC does not include any of the header fields or the public key. It is simply the private key data (still in plaintext and including random padding), all by itself (without a wrapping string
).
PPK version 1 keys must therefore be rigorously validated after loading, to ensure that the public and private parts of the key were consistent with each other.
PPK version 1 only supported the RSA and DSA key types. For RSA, this validation can be done using only the provided data (since the private key blob contains enough information to reconstruct the public values anyway). But for DSA, that isn't quite enough.
Hence, PPK version 1 DSA keys extended the private data so that immediately after x was stored an extra value:
string
: a SHA-1 hash of the public key data, whose preimage consists of
string
: the large prime p
string
: the small prime q
string
: the group generator g
The idea was that checking this hash would verify that the key parameters had not been tampered with, and then the loading application could directly verify that g^
x =
y.
In an unencrypted version 1 key file, the MAC is replaced by a plain SHA-1 hash of the private key data. This is indicated by the “Private-MAC:
” header being replaced with “Private-Hash:
” instead.
このマニュアルや PuTTY のツールに意見がある場合は、 Feedback page を参照してください。
翻訳についてはフィードバックから送信できます。
©1997-2025 Simon Tatham ©2015-2025 SATO Kentaro
[PuTTY custom build 0.83.ranvis-doc]