本文へスキップ
ecdsa.com

Sigstore と GPG の比較|長期鍵あり・なしのリリース署名

GPG の署名が示すのは、特定の鍵の保有者が署名したということです。Sigstore の署名が示すのは、特定の ID(多くの場合は CI ワークフロー)が、ログに記録された時点で、数分間だけ存在した鍵を使って署名したということです。
比較対象SigstoreOpenPGP

Sigstore と GPG のどちらを使うかは、リリースアーティファクト、コンテナ、パッケージに署名する 2 つの方法のどちらを選ぶかという問題です。OpenPGP(GnuPG と互換ツール)では、メンテナーが長期間使う秘密鍵を保管してそのフィンガープリントを公開し、各ファイルの横に分離署名を添えて配布します。Sigstore では、署名者が OpenID Connect で認証を受けると、Fulcio 認証局が新しい鍵をその ID に 10 分間だけ結び付ける証明書を発行し、署名が Rekor 透明性ログに記録され、鍵は破棄されます。検証者が確認するのは、鍵ではなく ID です。

比較表

Sigstore(キーレス)OpenPGP(GPG)
検証者が信頼するものOIDC の ID(メールアドレスまたはワークフローの URI)と発行者。Fulcio のルートを介して信頼帯域外で入手・確認した特定の公開鍵
署名鍵の有効期間一時的。証明書の有効期間は 10 分数年。有効期限は所有者が設定・延長
鍵管理署名者には不要生成、バックアップ、保護、ローテーション、失効
署名時刻の証明Rekor のログエントリ(と任意の RFC 3161 タイムスタンプ)署名者が記載した署名作成時刻
透明性すべての署名を公開ログに記録なし
失効一時鍵には不要。侵害された ID はポリシーで対処所有者が公開しなければならない失効証明書
オフライン署名不可(公開またはプライベートの Fulcio と Rekor が必要)可能
アーティファクトの形式Sigstore バンドル(.sigstore.json):証明書、署名または DSSE エンベロープ、ログの証明.asc または .sig ファイル
主なツールcosign、sigstore-python、sigstore-js、gitsign、GitHub の attest-build-provenancegpg、Sequoia(sq、sqv)、rpm と apt のツール群
標準的に使われている場所npm の来歴(2023年9月に一般提供開始)、PyPI のアテステーション(2024年11月)、GitHub のアーティファクトアテステーション(2024年6月に一般提供開始)、CPython 3.11 以降(3.14 からは唯一の選択肢)、Kubernetes と多くのコンテナイメージMaven Central(必須)、Debian と RPM のリポジトリ、Linux カーネルのタグ、多くのソースアーカイブ
プライバシー署名者の ID(メールアドレスまたはリポジトリ)はログで公開される鍵とユーザー ID は署名者が公開した場合のみ公開

キーレス署名に含まれるもの

npm は、来歴付きで公開されたパッケージの Sigstore バンドルを保存しています。以下のスクリプトは、公開レジストリの API から @noble/curves 2.3.0 のアテステーションを取得し、署名用証明書をデコードします(2026年10月20日、curl、jq 1.8.1、OpenSSL 3.6.5)。

npm の来歴アテステーションの元になる Sigstore バンドルを調べる
#!/bin/sh
curl -s "https://registry.npmjs.org/-/npm/v1/attestations/@noble%2fcurves@2.3.0" -o att.json
jq -r '.attestations[].predicateType' att.json
bundle='.attestations[] | select(.predicateType == "https://slsa.dev/provenance/v1") | .bundle'
jq -r "$bundle | .mediaType" att.json
jq -r "$bundle | .verificationMaterial.tlogEntries[0] | \"Rekor log index \(.logIndex), integrated \(.integratedTime | tonumber | todate)\"" att.json
jq -r "$bundle | .verificationMaterial.certificate.rawBytes" att.json | openssl base64 -d -A > leaf.der
openssl x509 -inform DER -in leaf.der -noout -issuer -dates -ext subjectAltName
openssl x509 -inform DER -in leaf.der -noout -text | grep -A1 "57264.1.1:" | tail -1 | sed 's/^ */OIDC issuer: /'
rm -f att.json leaf.der
出力
https://github.com/npm/attestation/tree/main/specs/publish/v0.1
https://slsa.dev/provenance/v1
application/vnd.dev.sigstore.bundle.v0.3+json
Rekor log index 2357213216, integrated 2026-08-06T07:37:48Z
issuer=O=sigstore.dev, CN=sigstore-intermediate
notBefore=Aug  6 07:37:48 2026 GMT
notAfter=Aug  6 07:47:48 2026 GMT
X509v3 Subject Alternative Name: critical
    URI:https://github.com/paulmillr/jsbt/.github/workflows/release.yml@5320e209ca8cc9b1b0b569675e164bdd2a139d71
OIDC issuer: https://token.actions.githubusercontent.com

このパッケージには 2 つのアテステーションがあります。npm 自身の公開アテステーションと、CI で署名された SLSA の来歴ステートメントです。証明書の有効期間はちょうど 10 分で、発行と同じ秒に Rekor に記録されています。証明書のサブジェクトは人や鍵ではなく、実行されたワークフローです。この例では、別のリポジトリにある共有のリリースワークフローで、特定のコミットに固定されています。さらに Fulcio の証明書拡張に、ソースリポジトリ、コミット、タグ、ワークフローの実行が記録されます。Sigstore バンドル検証ツールと npm 来歴チェッカーを使えば、どのパッケージでも同じデータをデコードできます。

検証の違い

GPG の署名の検証は gpg --verify file.asc file というコマンド 1 つで済みますが、その意味はそれ以前のすべてに左右されます。検証者がどのように鍵を入手したか、フィンガープリントを信頼できる情報源と照合したか、その後に鍵が失効していないか、です。CI パイプラインで出る「Good signature」の結果の多くは、数行前にインポートされた鍵を、それが何であれそのまま信頼しています。

Sigstore の署名を検証するには期待する ID を指定する必要があるため、誰を信頼するかというポリシーの問いに、コマンドの段階で答えざるを得ません。以下のスクリプトは、cosign 自身の v3.1.3 リリースのチェックサムファイルを、公式の cosign v3.1.3 コンテナイメージで 3 回検証します。1 回目は Sigstore プロジェクトがリリース用として文書化している ID(Google のサービスアカウント、発行者は https://accounts.google.com)に対して、2 回目はこのファイルに署名していない GitHub Actions のワークフローの ID に対して、3 回目はファイルに 1 バイト追加した後です。

cosign v3.1.3 による公開リリースファイルのキーレス検証
url=https://github.com/sigstore/cosign/releases/download/v3.1.3
curl -sSLO "$url/cosign_checksums.txt"
curl -sSLO "$url/cosign_checksums.txt.sigstore.json"
cosign() { docker run --rm -v "$PWD":/work:ro -w /work ghcr.io/sigstore/cosign/cosign:v3.1.3 "$@"; }

echo "== expected identity"
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com

echo "== any other identity"
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3" \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

echo "== modified file"
printf 'x' >> cosign_checksums.txt
cosign verify-blob cosign_checksums.txt \
  --bundle cosign_checksums.txt.sigstore.json \
  --certificate-identity keyless@projectsigstore.iam.gserviceaccount.com \
  --certificate-oidc-issuer https://accounts.google.com
出力(2026年10月20日)
== expected identity
Verified OK
== any other identity
Error: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
error during command execution: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value "https://github.com/sigstore/cosign/.github/workflows/release.yml@refs/tags/v3.1.3", got "keyless@projectsigstore.iam.gserviceaccount.com"
== modified file
Error: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature
error during command execution: failed to verify signature: could not verify message: invalid signature when validating ASN.1 encoded signature

最初の結果では、cosign は Sigstore のルート(TUF で配布)までの証明書チェーン、ファイルに対する署名、Rekor の包含証明、そしてログの時刻が証明書の 10 分間の有効期間内に収まっていることを確認しました。2 回目の実行は、ID を固定する意義を示しています。署名は暗号学的には有効ですが、検証者が求めた ID によるものではないため拒否されます。3 回目は署名そのものの検証で失敗します。GitHub Actions でビルドしたアーティファクトの場合、ID は ref を含むワークフローファイル(上の npm 来歴証明書にある release.yml@5320e209… ワークフローなど)で、発行者は https://token.actions.githubusercontent.com です。

パッケージのエコシステムは、これらの確認を独自のツールに組み込んでいます。npm は、インストールしたすべてのパッケージについて、レジストリの署名と Sigstore のアテステーションの両方を検証します。

インストール済み npm パッケージのレジストリ署名と来歴を検証する
cd "$(mktemp -d)" && npm init -y > /dev/null
npm install --ignore-scripts --no-fund --no-audit @noble/curves@2.3.0 @noble/hashes@2.3.0
npm audit signatures
出力
added 2 packages in 589ms
audited 2 packages in 0s

2 packages have verified registry signatures

2 packages have verified attestations
(use --json --include-attestations to view attestation details)

gh attestation verify は、GitHub のアーティファクトアテステーションについて同じ検証を行います。PyPI は、アテステーションがアップロードされた時点で、プロジェクトの Trusted Publisher(信頼できる公開元)と照合します。

知っておくべきトレードオフ

  • 信頼の対象が ID プロバイダーと Sigstore のインフラストラクチャに移る:メンテナーの GitHub アカウントやリリースワークフローを掌握した攻撃者は、有効な署名を取得できます。透明性によってこれは検出可能になります(メンテナーは自分の ID についてログを監視できます)が、不可能になるわけではありません。
  • メタデータが公開される:個人のメールアドレスの ID は公開ログに記録され、ワークフローの ID からはリポジトリ名がわかります。プライベートな Sigstore 環境なら、どちらも避けられます。
  • 可用性:署名には、オンラインの Fulcio と Rekor が必要です。バンドルの検証は、信頼ルートをキャッシュした後ならオフラインでも行えます。
  • 運用は変化し続けている:タイルベースのログである Rekor v2 は 2025年10月から一般提供されていますが、メンテナーがポスト量子への対応を進める間、公開インスタンスは引き続き Rekor v1 を既定としています。
  • GPG のコストは人に起因する:鍵は紛失され、気付かないうちに期限切れになり、下流のユーザーに一度も検証されないこともあります。これが、Python、npm、PyPI が GPG から離れた主な理由です。

どちらを選ぶべきか

  • GitHub Actions や GitLab CI でビルドする npm、PyPI、crates、コンテナ向けのパッケージ:Trusted Publishing と来歴を通じて Sigstore を使います。
  • コンテナイメージ:cosign の署名とアテステーションを使い、アドミッションポリシーで検証します。
  • Maven Central:OpenPGP が必須です。Sigstore バンドルも追加してください。Central は 2025年1月から Sigstore バンドルを検証しています。
  • Linux ディストリビューションのパッケージとリポジトリ:ディストリビューションのツールが前提としているとおり、OpenPGP を使います。
  • エアギャップ環境やオフラインでの署名:OpenPGP を使います(または、独自の ID プロバイダーを用いたプライベートな Sigstore 環境を使います)。
  • Git のコミットとタグ:SSH 鍵か OpenPGP 鍵を使うか、キーレスなコミットには gitsign を使います(SSH と GPG のコミット署名の比較を参照してください)。

移行時の注意点

  1. ノートパソコンではなく CI で署名する:リリースを、OIDC トークンを使えるワークフロー(GitHub Actions では id-token: write)に移し、そこで署名します。
  2. 移行期間中は両方を公開する:.asc と .sigstore.json の両方のファイルを配布し、期待する ID と発行者を、古い鍵のフィンガープリントと並べて文書化します。
  3. レジストリ固有の仕組みを使う(存在する場合):npm の --provenance と Trusted Publishing、PyPI の Trusted Publisher、GitHub のアーティファクトアテステーションがこれに当たります。
  4. 検証ポリシーでは ID を厳密に固定する:組織だけでなく、リポジトリ、ワークフローファイル、タグまたはブランチまで指定します。
  5. 自分の ID についてログを監視し、OpenPGP 鍵で署名した最後のリリースのサポートが終わるまで、その鍵の失効証明書を保管しておきます。

参考資料

すべての比較