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=ssh | Git 1.7.9(2012年)以降の git commit -S。署名付きタグはそれよりずっと前から |
| 署名ツール | ssh-keygen -Y sign、OpenSSH 8.1 以降(8.7 は避ける) | gpg または他の OpenPGP 実装 |
| 署名形式 | 名前空間 git の SSHSIG、ASCII 外装の BEGIN SSH SIGNATURE | OpenPGP 署名パケット、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 のグローバル設定は無効にしているので、手元のマシンの設定は何も変わりません。
#!/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 からサポートされています。
移行時の注意点
- SSH 署名を設定する:
git config --global gpg.format sshを実行し、user.signingkeyに公開鍵ファイル(またはkey::に続けて鍵そのもの)を設定し、commit.gpgsign trueとtag.gpgsign trueを設定します。 - ホスティングサービスで鍵を署名鍵として登録する:GitHub と GitLab は同じ鍵を認証と署名の両方に使えますが、署名鍵を別にしておくほうがローテーションしやすくなります。
- リポジトリで allowed signers を公開する(
.gitsignersなど):CI でgpg.ssh.allowedSignersFileがそのファイルを指すように設定し、マージ時にgit verify-commitを実行します。鍵を追加するときはvalid-afterを、鍵を廃止するときはvalid-beforeを追加します。 - 古い OpenPGP 公開鍵を残しておく:検証者が入手できる状態を保ち、ホスティングサービスのアカウントにも残しておけば、切り替え前に署名されたコミットも引き続き検証できます。
- すべてのコミッターが切り替えたら、保護されたブランチで署名付きコミットを強制します(GitHub のルールセット、GitLab のプッシュルール)。