本文へスキップ
ecdsa.com

SSH と GPG のコミット署名の比較|Git の署名鍵の選び方

どちらも、Git やコードホスティングサービスが検証できる署名をコミットオブジェクトに埋め込みます。SSH 署名は開発者がすでに持っている鍵を再利用し、OpenPGP は有効期限、失効、独自の信頼モデルを備えています。

SSH と GPG のどちらでコミットに署名するかは、git commit -S や git tag -s を実行したときに Git がどの鍵を使うかという選択です。OpenPGP(既定。通常は GnuPG)では、Git はコミットの gpgsig ヘッダーに OpenPGP 署名を格納します。SSH(gpg.format=ssh、Git 2.34 以降)では、Git は ssh-keygen -Y sign を呼び出し、同じ場所に SSHSIG 署名を格納します。どちらの場合も、署名はツリー、親コミット、作成者、メッセージを記載したコミットオブジェクトを対象とするため、1 つの署名でそのコミットまでの履歴全体を認証できます。

比較表

SSH 署名OpenPGP(GPG)署名
Git の対応Git 2.34(2021年11月)、gpg.format=sshGit 1.7.9(2012年)以降の git commit -S。署名付きタグはそれよりずっと前から
署名ツールssh-keygen -Y sign、OpenSSH 8.1 以降(8.7 は避ける)gpg または他の OpenPGP 実装
署名形式名前空間 git の SSHSIG、ASCII 外装の BEGIN SSH SIGNATUREOpenPGP 署名パケット、ASCII 外装の BEGIN PGP SIGNATURE
コミットオブジェクト内のサイズ(Ed25519、実測)306 バイト279 バイト
信頼される対象gpg.ssh.allowedSignersFile に記載された鍵十分な所有者信用度を持つキーリング内の鍵
有効期限とローテーションallowed signers ファイルの鍵ごとの valid-after と valid-before(OpenSSH 8.8 以降)鍵の中の有効期限(延長可能)、副鍵
失効gpg.ssh.revocationFile(鍵のリストまたは KRL)鍵とともに公開される失効証明書
ハードウェアキーFIDO2 キー(ed25519-sk、ecdsa-sk)、セキュアエンクレーブを利用するエージェントOpenPGP スマートカード、OpenPGP アプレットを搭載したセキュリティキー
認証局cert-authority 行による SSH 証明書なし。ID は他の鍵によって証明される
GitHub の「Verified」2022年8月23日から2016年4月から
GitLab の「Verified」15.7(2022年12月)から、15.8 で一般提供対応

両方の方法で署名・検証する

以下のスクリプトは、一時ディレクトリ内で同じ ID の SSH 鍵と OpenPGP 鍵を作成し、それぞれで 1 つずつコミットに署名して、両方を検証します(Git 2.54、OpenSSH 10.3、GnuPG 2.5.21)。Git のグローバル設定は無効にしているので、手元のマシンの設定は何も変わりません。

1 つのリポジトリに、SSH で署名したコミットと OpenPGP で署名したコミットを 1 つずつ
#!/bin/sh
base=$(pwd)
dir=$(mktemp -d "$base/commitdemo.XXXXXX")
cd "$dir" || exit 1
GNUPGHOME=$(mktemp -d /tmp/gpg.XXXXXX)   # short path: gpg-agent sockets need one
export GNUPGHOME GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_NOSYSTEM=1
export GIT_AUTHOR_NAME=Alice GIT_AUTHOR_EMAIL=alice@example.org
export GIT_COMMITTER_NAME=Alice GIT_COMMITTER_EMAIL=alice@example.org

# One SSH key with an allowed_signers entry, one OpenPGP key
ssh-keygen -q -t ed25519 -N '' -C alice@example.org -f alice
echo "alice@example.org namespaces=\"git\" $(cat alice.pub)" > allowed_signers
gpg --batch --quiet --passphrase '' --quick-gen-key "Alice <alice@example.org>" ed25519 sign 1y
fpr=$(gpg --list-keys --with-colons alice@example.org 2>/dev/null | awk -F: '/^fpr/ { print $10; exit }')

# Two commits: the first signed with SSH, the second with OpenPGP
git init -q repo && cd repo || exit 1
git config gpg.ssh.allowedSignersFile ../allowed_signers
echo one > file && git add file
git -c gpg.format=ssh -c user.signingkey=../alice.pub commit -q -S -m "signed with SSH"
echo two >> file
git commit -q -a -S"$fpr" -m "signed with OpenPGP" 2>/dev/null

for c in HEAD~1 HEAD; do
  echo "== $(git log -1 --format=%s "$c")"
  git verify-commit "$c" 2>&1 | grep -E "Good|using"
  git cat-file commit "$c" | awk '/^gpgsig/,/END/' > sig.txt
  echo "$(head -1 sig.txt | cut -c8-) ($(wc -c < sig.txt | tr -d ' ') bytes in the commit object)"
done
gpgconf --kill gpg-agent
cd "$base" && rm -rf "$dir" "$GNUPGHOME"
出力
== signed with SSH
Good "git" signature for alice@example.org with ED25519 key SHA256:7z1vKkx5UJmp2Qrc5ucSDqImz8+r8pfG21roc32f/2A
-----BEGIN SSH SIGNATURE----- (306 bytes in the commit object)
== signed with OpenPGP
gpg:                using EDDSA key 6A4E1CC8308072568C9E240CDDBB1B4BE59BFC00
gpg: Good signature from "Alice <alice@example.org>" [ultimate]
-----BEGIN PGP SIGNATURE----- (279 bytes in the commit object)

SSH の検証では allowed_signers のプリンシパルと鍵のフィンガープリントが表示され、OpenPGP の検証ではユーザー ID と、キーリングに基づく信用度が表示されます。どちらも Ed25519 の署名を 1 つ含むため、サイズは同程度です。SSHSIG は公開鍵全体も埋め込みますが、OpenPGP が含むのはそのフィンガープリントだけです。SSH 署名検証ツールと OpenPGP インスペクターで、git cat-file commit から取り出したどちらのブロックもデコードできます。

誰の署名を有効とみなすか

有効な署名に意味があるのは、どの鍵が誰のものかを検証者が知っている場合だけです。SSH では、この対応付けは allowed_signers ファイルです。プリンシパルと鍵ごとに 1 行を記述し、必要に応じて git 名前空間と有効期間に限定できます。Git は valid-after と valid-before を署名が作成された時刻と照合するため、使わなくなった鍵も終了日を付けてファイルに残しておけば、古いコミットは引き続き検証できます。cert-authority を含む行は、組織の SSH 証明書の認証局が証明したすべての鍵を信頼します。これは、すでに有効期間の短い SSH 証明書を発行している企業に適しています。このファイルはリポジトリに置けるので、ファイルへの変更そのものもレビューされ、署名されます。

OpenPGP では、この対応付けは各検証者のキーリングと信用データベースにあります。CI システムやホスティングサービスが鍵を明示的にインポートするのはそのためです。信頼の輪は面識のない者同士を結び付けられますが、古くからあるコミュニティの外ではほとんど使われていません。鍵はそれ自体に有効期限と失効証明書を持ち、副鍵を使えば、開発者は主鍵をオフラインに保ったまま、ID を変えずに署名用の副鍵をローテーションできます。

ホスティングサービスは独自の層を加えます。GitHub と GitLab は、署名鍵がコミッターの確認済みメールアドレスを持つアカウントに登録されている場合に「Verified」と表示します。このバッジはサービス側の記録に依存します。2024年11月からプレビュー中の GitHub の永続的なコミット署名検証は、プッシュ時に結果を保存するため、後で鍵を削除しても結果は変わりません。

どちらを選ぶべきか

  • GitHub や GitLab を使う個人の開発者:SSH を使います。理想的にはセキュリティキー上の ed25519-sk 鍵を使い、署名のたびにタッチが必要になるようにします。
  • チームと企業:リポジトリに置いた allowed_signers ファイル、または既存の CA が発行する SSH 証明書とともに SSH を使います。
  • 上流のタグを OpenPGP で検証するプロジェクト(Linux カーネル、多くのディストリビューション、鍵を公開しているリリースマネージャー):OpenPGP を使います。
  • CI やボットが作成するコミット:どちらの長期鍵も使いません。有効期間の短い証明書によるキーレス署名(Sigstore の gitsign など。Sigstore と GPG 署名の比較を参照してください)か、ホスティングサービス自身のボット署名を使います。
  • X.509 スマートカードを使う規制対象の環境:S/MIME 署名ツールとともに gpg.format=x509 を使います。これは Git 2.19 からサポートされています。

移行時の注意点

  1. SSH 署名を設定する:git config --global gpg.format ssh を実行し、user.signingkey に公開鍵ファイル(または key:: に続けて鍵そのもの)を設定し、commit.gpgsign true と tag.gpgsign true を設定します。
  2. ホスティングサービスで鍵を署名鍵として登録する:GitHub と GitLab は同じ鍵を認証と署名の両方に使えますが、署名鍵を別にしておくほうがローテーションしやすくなります。
  3. リポジトリで allowed signers を公開する(.gitsigners など):CI で gpg.ssh.allowedSignersFile がそのファイルを指すように設定し、マージ時に git verify-commit を実行します。鍵を追加するときは valid-after を、鍵を廃止するときは valid-before を追加します。
  4. 古い OpenPGP 公開鍵を残しておく:検証者が入手できる状態を保ち、ホスティングサービスのアカウントにも残しておけば、切り替え前に署名されたコミットも引き続き検証できます。
  5. すべてのコミッターが切り替えたら、保護されたブランチで署名付きコミットを強制します(GitHub のルールセット、GitLab のプッシュルール)。

参考資料

すべての比較