Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

PGP鍵を交換しました + 令和最新版のPGP鍵運用メモ

PGP鍵を交換しました。というのも、元々使っていたPGP鍵はYubikey上で生成して使っていたところ、数ヶ月前にYubikeyを紛失するという赤っ恥の行いをやらかしてしまい、とっとと失効させて作り直す必要があったからです。ありがたいことに手元に旧い鍵の失効証明書があったので、失効は終わっています。そのまましばらくPGP自体使っていなかったのでそのままにしていたのですが、最近PGPを使う用事が発生したのでちゃんと作ることにしました。

本当は、旧い鍵の主鍵で新たな鍵の主鍵に署名したり、Key Transition Statementというのを作成して両者の鍵で署名したりして、「正当に鍵が移行しましたよ」ということを表明しなければならないのですが、いかんせん旧い主鍵を使えない形態で旧鍵を運用していたので、これができません。しょうがないので、たとえばhttps://www.3qe.usに公開鍵情報が増えているとか、このブログにログインしてこの記事を書いているとか、Twitterのbioに新たな鍵のFingerprintを表示している、といったあたりで納得してもらうことにします。今後はちゃんと主/副鍵の管理をやろうと思い、今回はちゃんと手順を作ってYubikeyに焼きました。

こちらがPGP鍵でございます

Fingerprintは FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D です。

-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEYzmGABYJKwYBBAHaRw8BAQdAUM3TQ/gd1jQiPFiMQ9ouIXzAuzvPBY+ZeR1x
QBi/Fum0HFdpbmR5bWVsdCA8d2luZHltZWx0QDNxZS51cz6IiwQTFgoAMwIbAxYh
BP78s4Gey8JdwTKdifL8Y8JCwE2dBQJjeEODBQsHCQMCBRUICgIDAxYBAgIeBAAK
CRDy/GPCQsBNnSJ/AP4tZKrihfKH/zjegEygaYLpQ9Ps/HStj5GhNzJ/0tpiggEA
mT+6jhZkqgWfypMBL8ItaAdHXMYs9fzGVsJ/RZbQAwq4OARjOYYAEgorBgEEAZdV
AQUBAQdA1Z0CwyRQM2G8wyAXt8mGneeUdnX9oL234pc1h9nwoHwDAQgHiH4EGBYK
ACYCGwwWIQT+/LOBnsvCXcEynYny/GPCQsBNnQUCYzmG1wUJAeE0VwAKCRDy/GPC
QsBNnfCyAQCzgf5NrnRkSqNSduArIoicWHb2qRz/+7yfQxfYfGmdZgD/YH7FbUhQ
bOtoVI0v6VV8m5A5qHXwZpCkrv47lVTm+w+4MwRjOYcDFgkrBgEEAdpHDwEBB0DD
s66twNmJIXr+B4XW9QHT8aIj7ZMf3RnX6FJ4amty14j1BBgWCgAmFiEE/vyzgZ7L
wl3BMp2J8vxjwkLATZ0FAmM5hwMCGyIFCQHhM4AAgQkQ8vxjwkLATZ12IAQZFgoA
HRYhBLCd6/IHq8HMZHNBIAsGd6ZTNu4IBQJjOYcDAAoJEAsGd6ZTNu4IJUwA/1Zk
1wxbdcBCTD3VGwPf+vNurfCtYKlEPxaDx6Ux27CIAQCNkyTwa9mtEpr654gNcuM2
t4G21qwGXlgLuPQHAgGLCZ9jAQDVC3U4DLosm4egtz0MxhIghoKQP2+0RE+gKEfT
cLxiegEAwkG3HVVj7bVblVcBv099O5mjJ7f2ziKRt1Wchrcf/gs=
=RjIT
-----END PGP PUBLIC KEY BLOCK-----

keys.openpgp.orgでも入手できます。IPFS経由でも入手できます。

$ gpg --keyserver keys.openpgp.org --recv-keys FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D

RSA4096bitとかやってた時代と比べて、めちゃくちゃデータ量がスマートになってて良いですね。これで主鍵+副鍵2本分です。この記事に暗号化メッセージをトラバなりコメント投稿なりすると、Windymeltが読みます。

今回以降のPGP鍵運用方針

主鍵を紛失してエラい目に遭ったので、今回以降は主鍵はエアギャップセキュリティ、つまり完全にオフラインに保管することにして、副鍵のみをYubikeyに焼き込む*1ことにします。いくつかの資料を参考にして、以下の方針としました。

  • 鍵アルゴリズムはed25519/cv25519を用いる
    • 最新のGnuPGではdefaultになっているはずで、とりあえずこれを使えばよい
    • ed25519は暗号化モードでは使えないのでcv25519を使っている
  • 副鍵として、デフォルトで作成される暗号化用(E)の鍵に加えて、署名+認証用(SA)の鍵を作成する
    • こちらは日常使いする
  • 主鍵は、Key Transitionや副鍵を署名するためだけに使用する
    • 日常使いしないので、失効証明書と一緒になんらかの方法で安全に保管する
    • ローカルマシンからは抹消する
  • 副鍵2つはYubikeyに転送する
    • ローカルマシンからは抹消する
    • 現行(Yubikey 5)ではYubikeyは3つの鍵スロットを持っており、それぞれS(Sign; 署名)、E(Encryption; 暗号化)、A(Authentication; 認証)用の鍵を持てる
    • 前述した2つの副鍵(E/SA)をそれぞれ割り当てる(SA鍵は兼用なので、SとA両方のスロットに入れる)

そもそも副鍵ってなんやねん

ちょっと副鍵の説明もしましょうか。副鍵とは、主鍵によって署名されて特別な関係にある鍵です。副鍵は、いくつでも主鍵に「追加」できます。そしていつでも「失効」させることができます。

でも1つの鍵を使えばよいのでは?と思うかもしれませんが、セキュリティ等の観点から、「副鍵」という概念が要請される理由がいくつかあります。https://security.stackexchange.com/a/76950 によれば、

  • 普段使う鍵と安全な場所に保管する鍵とを分け、安全な場所に保管する鍵によって普段使う鍵を署名することで、普段使う鍵がなんらかの方法で使えなくなったり、流出した場合にアイデンティティを失うことを防ぐ
  • 他人には主鍵の指紋だけ伝えておけば、副鍵を作り直しても問題がない(主鍵が署名しているから)
  • 複数のビルドサーバで署名を行うような場合、物理的に複数の鍵が要求されることがある(でも鍵を使い回したくはないよね?)
  • 普段は鍵長を抑えた高速な鍵を使い、それらを鍵長の長い安全な鍵でマネージメントできる
  • 鍵アルゴリズムによっては、署名・暗号化・認証のすべてをカバーしていない場合がある。用途をカバーするために用途別に鍵を作ることが必要である
    • ed25519が特に有名
  • (これはwindymeltによる追記)同様に、マシンが特定のアルゴリズムをサポートしていない場合に対応するために複数の鍵が必要になることもある
    • GPGが古くてRSAしか使えないよ〜みたいなことがたまにある

まとめると、主鍵はその持ち主の電子的なアイデンティティの保証を担当するのに対して、副鍵はもっぱら実務上の暗号化などを行うためにある、という感じです。

はじめてのGPG 〜第2回 主鍵と副鍵〜 – 水珈琲の日誌も参考にしてください。

さて副鍵の説明を軽くしたところで、以下は実際に行った手順です。

手順

一応注意しておきますが、筆者はこの手順に従った結果生じた損害の責任を負いません。また、所々鍵のパスワードやYubikeyのPINを尋ねられている箇所は省略しています。

鍵作成

まずなにより最初に、鍵を生成します:

$ gpg --expert --full-gen-key
(インタラクティブにやりとりする。ここで鍵アルゴリズムを聞かれる)

この時点では副鍵はE用のものが1つだけ生える(ed25519は暗号化をサポートしないので、cv25519の副鍵が自動的に生えます)ので、さらにSA用の副鍵を追加します:

$ gpg --expert --edit-key FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
gpg> addKey
(鍵の役割を尋ねられるので、SignとAuthを有効化する)
(subkeyがEとSAで2本になる)

ちなみに主鍵の役割はCSになっています(自動でそうなる)。CはCertificate、つまり副鍵がこの主鍵に属していることを証明するという用途だと思っています。どうしてSがあるのかというと、Key Transitionなどのために使います。主鍵が変わるという宣言に署名するのだから、主鍵でやらなければならないのですね。

定期的に副鍵はローテしたいので、1年で期限が切れるようにします:

gpg> key 1 (副鍵1を選択するためのコマンド)
gpg> expire
(期間を尋ねられるので1yと入力する)
gpg> key 1 (選択解除する)
(key 2で同様にする)

するとこういう状態になります。

pub   ed25519 2022-10-02 [SC]
      FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
uid           [  究極  ] Windymelt <windymelt@3qe.us>
sub   cv25519 2022-10-02 [E] [有効期限: 2023-10-02]
sub   ed25519 2022-10-02 [SA] [有効期限: 2023-10-02]

Ultimateの箇所だけ日本語で究極になってるのがなんとも趣がある。

Keyoxideを使う場合はここで設定する

keyoxide.org

PGP鍵を使ったデジタルアイデンティティツールとしてKeyoxideというものがあります。PGP鍵に例えば自分のソーシャルアカウントの情報を登録しておき、実際にその鍵の指紋をソーシャルアカウントに投稿することでアカウントの所有権を証明するという仕組みで、Twitter乗っ取りや脱出、アカウント引越しなどを機に最近広く使われています。

mitome.in

このKeyoxideは鍵ファイルに直接notationを追加するという方法で運用するので、もし使う場合はここで設定してください。

例えばMastodonのアカウント(ここでは@windymelt@mstdn.takuya-a.netを例とします)の所有権を紐付ける場合は以下のように設定します。

$ gpg --edit-key F2FC63C242C04D9D
gpg> notation
> proof@ariadne.id=https://mstdn.takuya-a.net/@windymelt
gpg> save

公開する

この時点で鍵を公開します。鍵サーバは最近評判が良いkeys.openpgp.orgにしました。どこでもよいと思います。

$ gpg --keyserver keys.openpgp.org --send-keys F2FC63C242C04D9D

鍵を公開するメリットは、指紋さえわかっていれば鍵を渡さなくてよくなることです。もちろん手で渡してもよさそう。

公開鍵を出力する

一般公開したりauthorized_keysに突っ込んだりする用の公開鍵情報を書き出します。

$ gpg --export --armour F2FC63C242C04D9D > windymelt20221002.asc
$ gpg --export-ssh-key --armour F2FC63C242C04D9D > windymelt20221002-ssh.key

適当な場所に保存したりしてください。

副鍵の秘密鍵をバックアップする

Yubikeyには副鍵だけを書き込みたいのですが、現行のgpgは副鍵だけを書き込む機能を持たないので、

  1. 主鍵を除いた副鍵の秘密鍵を分けてバックアップする
  2. 主鍵を含めた秘密鍵をバックアップする(こちらは紙に保管する)
  3. マシンから秘密鍵情報を消し去る
  4. 1.を読み出して副鍵だけが手元にある状態にする(これは可能な操作です)
  5. Yubikeyに鍵を書き込む(副鍵だけが書き込まれる)
  6. あらためてマシンから秘密鍵情報を消し去る

という手順を使ってYubikeyに副鍵の秘密鍵を書き込みます。

その1.の手順をここで行います。--export-secret-subkeysオプションは、--export-secret-keyの動作から主鍵を取り除いた結果を返却するため、この目的に即しています:

$ gpg --armour --export-secret-subkeys F2FC63C242C04D9D > subs.asc

subs.ascに副鍵情報がバックアップされました。これは後で使います。

主鍵をバックアップする

さて、エアギャップセキュリティのために主鍵を安全な媒体にバックアップするわけですが、適当な既存のツールがないっぽかったのと、勉強も兼ねて、そういうツールを作りました:

github.com

依存として、satysfilibdmtxdmtx-utilsgawkgsedmakepaperkeyが必要です。

  • SATySFi
    • 組版ツール。ベターLaTeXという感じ。インターネット知り合いが活発に開発している
    • バックアップPDFを生成するために必要。
  • libdmtx / dmtx-utils
    • 二次元バーコードDataMatrixの実装。秘密鍵データを印刷・読み取りするために必要。
    • なぜQRではなくDataMatrixかというと、↑実装がカッチリしたCで書かれているので長持ちしそうという点、同じ人がライブラリとラッパーを開発している点、読み込み用のdmtxreadと書き込み用のdmtxwriteがペアで提供されている点に注目して選んだ。あとは趣味
    • もちろん秘密鍵のASCIIを直接印刷しても良いが、苦行になるはず。
  • paperkey
    • exportしたPGP秘密鍵情報から公開鍵の部分だけを削除してデータ量を削減するツール。二次元バーコードに保管する都合上、データ量を削減する必要があった。
    • 復旧するときは公開鍵情報とあわせることで復元する(単独復元できない)ので、なるだけ公開鍵の部分は公開しておいたほうがよい。

鍵情報を指定して make すると、秘密鍵のエクスポートとデータ削減、失効証明書の作成、DataMatrixの生成が行われ、自動的にPDFまで作成されます。

$ PGP_KEY_ID=FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D make
$ make clean

出力されるPDFの雰囲気はこういう感じ。

あとはこれを物理的にA4の紙に印刷するか、PDFをUSBメモリかに保管します。

バックアップが完了したら、秘密鍵と失効証明書は削除します。

$ gpg --delete-secret-keys F2FC63C242C04D9D
$ rm ~/.gnupg/private-keys-v1.d/*
$ rm ~/.gnupg/openpgp-revocs.d/FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D.rev

もうマシンには主鍵の秘密鍵はありません。

もし主鍵を復旧する必要が生じた場合は、DataMatrixを読み込むことで鍵が復旧されます:

$ gpg --keyserver keys.openpgp.org --recv-keys FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
$ gpg --export FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D > public.asc
$ dmtxread image.png > secret.asc
$ paperkey --pubring public.asc --secrets secret.asc | gpg --import

一応、手元で何回か実験してうまくいっているので、いざという時に困ることはないはず。

副鍵だけimportする

さて、残るはYubikeyに副鍵の秘密鍵を書き込む作業です。先程バックアップした副鍵の秘密鍵をimportします:

$ gpg --import subs.asc
$ gpg -K
/home/windymelt/.gnupg/pubring.kbx
----------------------------------
sec#  ed25519 2022-10-02 [SC]
      FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
uid           [  究極  ] Windymelt <windymelt@3qe.us>
ssb   cv25519 2022-10-02 [E] [有効期限: 2023-10-02]
ssb   ed25519 2022-10-02 [SA] [有効期限: 2023-10-02]

主鍵の部分がsecではなくsec#になっているのが分かるでしょうか。これは該当する秘密鍵が手元に無いということを表現しています。

$ man gpg
(snip)
 --list-secret-keys
       -K     List the specified secret keys.  If no keys are specified, then all known secret keys are listed.  A # after the initial tags sec or ssb means that the secret key or subkey is currently not usable.  We also say that this key has been taken offline (for example, a primary key can be taken offline
              by exporting the key using the command --export-secret-subkeys).  A > after these tags indicate that the key is stored on a smartcard.  See also --list-keys.

Yubikeyに書き込む

副鍵の秘密鍵を使えるようになったので、これをYubikeyに書き込みます。書き込みはスロットごとに行うので、今回は3回の書き込みを行いました:

$ gpg --edit-key F2FC63C242C04D9D
gpg> key 1
(1番目の副鍵が選択された)
gpg> keytocard
(どのスロットに書き込むかを尋ねられる。1番目の副鍵はE鍵なのでEスロットを選択する)
gpg> key 1
(1の選択を解除する)
gpg> key 2
gpg> keytocard
(2番目の副鍵はSA鍵なのでSとAとのスロットに対して書き込む。書き込みはスロット単位なので2回に分けて書き込む必要がある)
(ここではSスロットを選択した)
gpg> keytocard
(ここではAスロットを選択した)
gpg> quit
gpg --card-status
...
Signature key ....: B09D EBF2 07AB C1CC 6473  4120 0B06 77A6 5336 EE08
      created ....: 2022-10-02 12:41:39
Encryption key....: 65A6 08AA A076 BCE3 5B5B  C128 0D53 5FB1 7638 2021
      created ....: 2022-10-02 12:37:20
Authentication key: B09D EBF2 07AB C1CC 6473  4120 0B06 77A6 5336 EE08
      created ....: 2022-10-02 12:41:39
General key info..: sub  ed25519/0B0677A65336EE08 2022-10-02 Windymelt <windymelt@3qe.us>
sec#  ed25519/F2FC63C242C04D9D  作成: 2022-10-02  有効期限: 無期限    
ssb>  cv25519/0D535FB176382021  作成: 2022-10-02  有効期限: 2023-10-02
                                カード番号: xxxx xxxxxxxx
ssb>  ed25519/0B0677A65336EE08  作成: 2022-10-02  有効期限: 2023-10-02
                                カード番号: xxxx xxxxxxxx

Yubikeyに副鍵を書き込めました。もう副鍵は手元には必要無いので削除します。

まずはバックアップを削除します:

rm subs.asc

副鍵をキーリングから削除する手順はちょっと面倒で、まずgpg内部の鍵ID?にあたるkeygripを取得し、それをgpg-agentに伝えて削除してもらいます。

$ gpg --with-keygrip --list-secret-keys FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
sec#  ed25519 2022-10-02 [SC]
      FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
      Keygrip = 1E471149A80F4C6C4B5AD9B67EFE49809FBB8484
uid           [  究極  ] Windymelt <windymelt@3qe.us>
ssb  cv25519 2022-10-02 [E] [有効期限: 2023-10-02]
      Keygrip = 0AB80B9AFFF9DE109B1FB268B1A9D3A028CDE533 ←これがkeygrip
ssb  ed25519 2022-10-02 [SA] [有効期限: 2023-10-02]
      Keygrip = 7831C2B439BC636AC67151D602B26176347BEB47 ←これがkeygrip
$ gpg-connect-agent "delete_key 0AB80B9AFFF9DE109B1FB268B1A9D3A028CDE533" /bye
 OK
$ gpg-connect-agent "delete_key 7831C2B439BC636AC67151D602B26176347BEB47" /bye
 OK

これで手元から完全に秘密鍵は消え去りました。Yubikeyに鍵があることを確認します:

$ gpg -K
/home/windymelt/.gnupg/pubring.kbx
----------------------------------
sec#  ed25519 2022-10-02 [SC]
      FEFCB3819ECBC25DC1329D89F2FC63C242C04D9D
uid           [  究極  ] Windymelt <windymelt@3qe.us>
ssb>  cv25519 2022-10-02 [E] [有効期限: 2023-10-02]
ssb>  ed25519 2022-10-02 [SA] [有効期限: 2023-10-02]
  • secの後ろに#が付いている: 主鍵の秘密鍵は存在しない
  • ssbの後ろに>が付いている: 副鍵はスマートカード上にあることを示している

念のため、署名ができるところまで確認します:

$ gpg --armor --clear-sign
Hello, PGP World!

以下のようなテキストが得られました:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Hello, PGP world!
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQSwnevyB6vBzGRzQSALBnemUzbuCAUCYzm1uAAKCRALBnemUzbu
COFZAQDNbisGDlZSzqvl4yoVbjexeh40bzoqRBV85J37+etBmgEAiwiZ1m7rvyn7
Y7rnDzJ7C4SEzE8wGIxJnTPkJmFl3wY=
=piud
-----END PGP SIGNATURE-----

署名を検証してみます:

$ gpg --verify gpg.txt
gpg: 2022年10月03日 01時00分56秒 JSTに施された署名
gpg:                EDDSA鍵B09DEBF207ABC1CC647341200B0677A65336EE08を使用
gpg: "Windymelt <windymelt@3qe.us>"からの正しい署名 [究極]

検証成功!

感想

(鍵を)換装した感想ですが・・・

まず手間がけっこうかかりました。最終的にマシンから鍵を削除するので、手順を間違えると最初からやり直しになってしまうのでそこの注意がけっこう大変でした。

参考資料

PGP鍵の更新にあたって以下の資料を参考にしました:

blog.jj1lfc.dev

keens.github.io

text.baldanders.info

wiki.archlinux.jp

*1:焼き込むと言っても、何度も書き込めます

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?