【令和最新版】PGP鍵の作り方から管理方法、Git Commitへの署名まで

## はじめに

この記事は[限界開発鯖 Advent Calendar 2022](https://qiita.com/advent-calendar/2022/approvers)の11日目の記事です。

## PGP(OpenPGP)とは？

PGPは _Pretty Good Privacy_ の略で、電子メールなどで使用できるデジタル署名のプロトコルの一つです。OpenPGPはそれをオープンな規格にしたもので、[RFC 4880](https://www.rfc-editor.org/rfc/rfc4880)などで標準化されています。
OpenPGPは規格なので規格に沿っていれば自由に実装することが可能です。その中でも特に有名なのがGNUによるGnuPG( _GNU Privacy Guard_ )です。GnuPGでは暗号化や署名などの様々な操作をすることができ、これらを電子メールやGit Commitの署名などに活用することが出来ます。GnuPG自体はOpenPGPの実装の一つに過ぎないのですが、かなり有名なため、PGPとGPGが混同されて使用されることも多々あります。

以後PGP鍵の作成や管理はGnuPG(GPG)を使用して行います。

## GnuPGのインストール

お使いの環境に応じて、GnuPGをインストールしてください。Windowsの場合はGPG4Win、macOS/Linuxの場合はgpgといった名前で提供されていることが多いです。また、これらをインストールする際には、一緒にpinentryと呼ばれるアプリケーションもインストールされます。これはGnuPGを使用する際にPINやパスフレーズを入力するために使用されるもので、こちらも様々な実装が存在します。基本的にはデフォルトでついてきたものを使用すれば良いですが、macOSの場合はGUIから使用できる`pinentry-mac`の方が使い勝手が良いため、インストールしておくことをおすすめします。

```shell
# GnuPGとpinentry-macをインストール
brew install gpg pinentry-mac

# どのpinentryを使用するか設定する
echo "pinentry-program $(which pinentry-mac)" >> .gnupg/gpg-agent.conf
```

また、macOSやLinuxでデフォルトのpinentryを使用する場合はttyの設定も追加で必要です。環境変数にて行うので、`.bashrc`や`.zshrc`などで設定しておきましょう。

```shell
export GPG_TTY=${TTY}
```

## GnuPGのデータの保存場所

GnuPG内で使用したデータはデフォルトでは全て`${HOME}/.gnupg`ディレクトリに保存されます。つまり、このディレクトリを削除してしまうとPC上のGnuPGのデータが全て消えてしまうため注意が必要です。また、機密データであるためパーミッション設定をしっかりと行うなど、対策を施しましょう。
なお、データが保存される場所は`GNUPGHOME`という環境変数を設定することで変更することも出来ます。なので、バックアップとして`.gnupg`をUSBなどに保存していた場合は、`GNUPGHOME`でそのパスを指定することによって一時的にバックアップの方を使用するといったことも可能です。

## 鍵の構成について

PGPには主鍵と副鍵という概念があります。それぞれの鍵は秘密鍵と公開鍵のペアになっています。
基本的には主鍵が一つあり、副鍵は主鍵によって署名されています。こうすることで鍵のローテーションが容易になり、鍵の管理もしやすくなります。
普段の用途では副鍵を使い、主鍵は副鍵を編集する用途のみで使用するようにすることで、主鍵は普段はPC以外の安全な場所に保管することが出来るようになり、安全性が増します。

## 主鍵の作成

ひとまず主鍵を作成しましょう。主鍵の作成は`gpg --expert --full-gen-key`で行います。`--gen-key`などでも主鍵の作成自体は出来ますが、鍵のアルゴリズムや、カギの用途の指定を詳細に行う場合は`--expert --full-gen-key`で行ったほうが良いです。

途中で鍵のアルゴリズムを指定する箇所がありますが、おすすめはed25519です。ECC(楕円曲線暗号)を使用したアルゴリズムであり、RSAに比べて短い鍵長で十分なセキュリティ強度を担保することが出来ます。新しいバージョンのGnuPGを使用している場合は`ed448`という更にセキュリティ強度の高いアルゴリズムを選択することも出来ますが、こちらは他のアプリケーションが対応していないことが多いので現時点では使用は控えたほうが良いでしょう。[^1]

鍵の有効期限は主鍵の場合は設定しないほうがいいでしょう。主鍵は主鍵さえあれば有効期限を好きに設定することが出来ますし、主鍵の有効期限を更新するたびに公開鍵を配布し直すのは面倒です。

[^1]: 例として、執筆時点ではKeybaseはed448の鍵に非対応です。

```console
> # full-gen-keyでないと鍵の詳細な設定ができない
> # expertを使用しないと鍵用途の指定ができない
> gpg  --expert --full-gen-key
gpg (GnuPG) 2.3.8; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
  (14) Existing key from card
> # どのような鍵を作るか聞かれます。おすすめは11(ECCで鍵の用途を自由に決められる)です。
Your selection? 11

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

> # 鍵の用途を設定します。はじめはSign(署名)とCertify(証明)になっています。
> # s/aで鍵の用途をトグルすることが出来ます。主鍵は証明だけ持たせる運用にしたいので、
> # sでSign(署名)の用途を消します。
Your selection? s

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Certify

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? q
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (2) Curve 448
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
> # どの楕円曲線を使用するか聞かれます。お好きなのをお選びください。
> # (個人的なおすすめはCurve 25519です)
Your selection? 1
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
> # 鍵の有効期限を聞かれます。0にすると有効期限なしになります。
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

> # 鍵の所有者情報を登録します。
> # なお、Git Commitの署名に使用する場合はGitHubに登録されているメールアドレスにする必要があります。
Real name: Shuntaro Nishizawa
Email address: me@shun.technology
Comment:
You selected this USER-ID:
    "Shuntaro Nishizawa <me@shun.technology>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
> # ここでpinentryが起動してパスフレーズを聞かれるので2回パスフレーズを入力してください。
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: revocation certificate stored as '/Users/shun_shobon/tmp/gnupg/openpgp-revocs.d/29BD5BDFF1687EAE9B47B8E374D9F7E8365C94D7.rev'
public and secret key created and signed.

pub   ed25519 2022-12-08 [C]
      29BD5BDFF1687EAE9B47B8E374D9F7E8365C94D7
uid                      Shuntaro Nishizawa <me@shun.technology>
```

秘密鍵を持っている鍵の一覧は`gpg -K`で表示することが出来ます。

```console
> gpg -K
gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
gpg: next trustdb check due at 2023-12-08
/Users/shun_shobon/tmp/gnupg/pubring.kbx
----------------------------------------
sec   ed25519 2022-12-08 [C]
      29BD5BDFF1687EAE9B47B8E374D9F7E8365C94D7
uid           [ultimate] Shuntaro Nishizawa <me@shun.technology>
```

## 副鍵作成

次に副鍵を作成します。副鍵は何本でも作成出来ますが、用途ごとに分ける運用がやりやすいでしょう。副鍵の用途自体はSign(署名)、Authentication(認証)、Encryption(暗号化)と3つの中から選ぶことになりますが、DSA/ECCの場合は署名/認証と暗号化を同時に持たせることが出来ないので注意しましょう。

副鍵の作成は`gpg --expert --edit-key <鍵指紋>`です。鍵指紋は鍵生成時や`gpg -K`したときに表示される長い文字列のことです。鍵指紋は鍵生成時に決定され、以後鍵の情報を編集しても変化することはありません。この鍵指紋を利用することで改ざんされていない鍵を使用していることを確認することも出来ます。

なお、鍵を作成した後は **必ず`save`して終了してください。** saveを忘れると鍵の編集情報が保存されません。

```console
> # --expertを付けないと鍵の用途を設定できない
> gpg --expert --edit-key <鍵指紋>
gpg (GnuPG) 2.3.8; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec  ed25519/74D9F7E8365C94D7
     created: 2022-12-08  expires: never       usage: C
     trust: ultimate      validity: ultimate
[ultimate] (1). Shuntaro Nishizawa <me@shun.technology>

> # プロンプトが起動するので、addkeyで副鍵を追加します。
> # その他のコマンドはhelpと打つことで確認できます。
gpg> addkey
Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (12) ECC (encrypt only)
  (13) Existing key
  (14) Existing key from card
> # 鍵の種類を聞かれるので主鍵同様に好きなものを指定します。
Your selection? 11

Possible actions for this ECC key: Sign Authenticate
Current allowed actions: Sign

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

> # 鍵の用途を設定します。今回はSing(署名)用の鍵を作ります。
Your selection? q
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (2) Curve 448
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
   (9) secp256k1
> # どの楕円曲線を使用するか聞かれます。お好きなのをお選びください。
Your selection? 1
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
> # 有効期限を聞かれます。おおよそ1年ほどがいいでしょう。
Key is valid for? (0) 1y
Key expires at Fri Dec  8 15:00:47 2023 JST
Is this correct? (y/N) y
Really create? (y/N) y
> # 主鍵のパスフレーズが聞かれるので入力してください。
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

sec  ed25519/74D9F7E8365C94D7
     created: 2022-12-08  expires: never       usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/2CAC08BF404BF4F5
     created: 2022-12-08  expires: 2023-12-08  usage: S
[ultimate] (1). Shuntaro Nishizawa <me@shun.technology>

> # 更に鍵を増やしたい場合は更にaddkeyを繰り返します。
> # 終わる場合は必ずsaveして終わりましょう。saveしないと追加した鍵の情報が保存されません。
gpg> save
```

## 秘密鍵のバックアップ

**秘密鍵はとても重要なのでどこかにバックアップを取っておくことを強く推奨します。** 秘密鍵は`gpg --armor --export-secret-keys <鍵指紋>`でエクスポートすることが出来ます。デフォルトではバイナリデータとして出力されますが、`--armor`を付けることでPEM形式で出力されるようになります。なお、`--exports-secret-keys`では主鍵も副鍵も全てエクスポートされるのでコマンドは一回で十分です。
また、`--output <ファイル名>`でファイル出力も可能です。

## 主鍵の削除

主鍵は普段の用途では使用することが無いのでPC上からは削除しておくと良いです。鍵の本体は`${GNUPGHOME}/private-keys-v1.d/`にあります。なおここに保存されるときの名前は`<KEYGRIP>.key`となっています。keygripは`gpg -K --with-keygrip`で表示することが出来ます。

```console
> # 鍵の一覧を表示
> gpg -K --with-keygrip
/Users/shun_shobon/tmp/gnupg/pubring.kbx
----------------------------------------
sec   ed25519 2022-12-08 [C]
      29BD5BDFF1687EAE9B47B8E374D9F7E8365C94D7
      Keygrip = 8B2ED1BF944B5905FEE4902B9C7AB0429F359671
uid           [ultimate] Shuntaro Nishizawa <me@shun.technology>
ssb   ed25519 2022-12-08 [S] [expires: 2023-12-08]
      Keygrip = 91AFA2B8E271DDABAD61B485908D51951A156536

> # private-keys-v1.dの中身を表示
> ls private-keys-v1.d
8B2ED1BF944B5905FEE4902B9C7AB0429F359671.key
91AFA2B8E271DDABAD61B485908D51951A156536.key

> # 主鍵を削除
> rm private-keys-v1.d/8B2ED1BF944B5905FEE4902B9C7AB0429F359671.key

> # 主鍵を消したのでsecの後に#がついている
> gpg -K
/Users/shun_shobon/tmp/gnupg/pubring.kbx
----------------------------------------
sec#  ed25519 2022-12-08 [C]
      29BD5BDFF1687EAE9B47B8E374D9F7E8365C94D7
uid           [ultimate] Shuntaro Nishizawa <me@shun.technology>
ssb   ed25519 2022-12-08 [S] [expires: 2023-12-08]
```

## 失効証明書の保存

失効証明書は何らかの理由で鍵が信頼できなくなったときなどに使用するものです。主鍵作成時に`opengpg-revocs.d`に自動的に生成されます。 **こちらも主鍵の秘密鍵同様に非常に重要なため、PC以外に保存しておき、PCからは削除しておきましょう。** なお、新しく作成する場合は`gpg --gen-revoke <鍵指紋>`で可能です。

## 鍵の情報を見る

秘密鍵の一覧は`gpg -K`、公開鍵の一覧は`gpg -k`で表示することが出来ますが、ここにオプションを付けることによって更に表示を増やすことが出来ます。
Git設定時などに使用する鍵IDはデフォルトでは表示されませんが、`gpg -K/k --keyid-format=LONG`で鍵IDを表示することが出来ます。また、鍵指紋もデフォルトでは主鍵のみですが、`gpg -K/k --with-subkey-fingerprint`を付けることで副鍵の鍵指紋も表示することが出来ます。

## 公開鍵のエクスポート

公開鍵のエクスポートは`gpg --armor --export <鍵指紋|鍵ID>`で行うことができます。なお、デフォルトでは主鍵副鍵どちらを指定しても主鍵副鍵全ての公開鍵がエクスポートされます。主鍵のみ、副鍵のみのエクスポートをする場合は`<鍵指紋|鍵ID>`の後に`!`を付けることで指定した鍵のみがエクスポートされます。ただし、副鍵の場合は主鍵も一緒にエクスポートされます。

## Git Commitで使用する

GitではGnuPGを使用してコミットやタグに署名をすることが可能です。この署名はGitHubやGitLabで確認することが可能で、公開鍵を登録しておけば正しい署名がついたコミットには`Verified`のバッジが表示されるようになります。

![GitHubのコミットに表示されたVerifiedバッジ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628629/b23fa0aa-9683-7708-0727-bba828b2023f.png)

## Gitの設定

まずGitの設定を行います。

```shell
# GnuPGのパスの設定
git config --global gpg.program $(which gpg)

# どの鍵を使用して署名するのかを指定
git config --global user.signingKey <鍵ID>

# コミット時に署名するように設定
git config --global commit.gpgsign true
```

これでコミット時に自動的に署名されるようになります。

## GitHubの設定

これでコミットに署名がされるようになりましたが、まだGitHubに公開鍵を設定していないので`Verified`バッジは表示されません。なのでGitHubに公開鍵を設定しましょう。
[SSH and GPG keys](https://github.com/settings/keys)にて、「new GPG key」からGPG鍵を登録します。

![GPG鍵の一覧](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628629/09c00ea0-758d-3225-8182-2715b816c5e3.png)

なお、鍵の有効期限を伸ばしたりなど鍵を編集した場合は再び公開鍵を登録し直す必要があります。

## GitHubのブランチ保護設定

これでGitHub上でも`Verified`バッジが表示されるようになります。しかし、Verifiedバッジをいちいち確認するのも大変なのでブランチ保護設定を利用して、正しい署名がされているコミット以外は弾くように設定しておくことをおすすめします。

![Require signed commitsのブランチ保護設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628629/d5120fb0-dda3-b897-84e5-327d41b72bc3.png)

## 最後に

ここで紹介した鍵の管理方法はあくまで一部ですので詳しくは`man`コマンドや公式サイトなどで確認してください。また、記載にミス等がありましたらコメントや編集リクエストをお願いします。