本文へスキップ
ecdsa.com

HMAC とデジタル署名の比較|共有秘密か鍵ペアか

どちらも、メッセージが改ざんされていないことを証明します。しかし、自分では同じものを作れない相手に対して、誰が作成したかを証明できるのは署名だけです。

HMAC かデジタル署名かは、あらゆるメッセージ認証方式で最も重要な設計上の判断であり、問われるのは速度ではなく信頼です。HMAC のタグは同じ共通鍵で計算・検証されるため、検証できる者は誰でも偽造もできます。署名は秘密鍵で作成され公開鍵で検証されるため、検証者は新たに有効なメッセージを作成できません。

比較表

HMAC(RFC 2104、FIPS 198-1)デジタル署名
鍵1 つの共有秘密秘密鍵で署名し、公開鍵で検証
有効なタグや署名を作成できる者シークレットを持つ全員(すべての検証者を含む)秘密鍵の保有者のみ
否認防止(第三者に証明できるか)なしあり(鍵と署名者の信頼できる紐付けがある場合)
鍵の配布シークレットをすべての検証者に秘密裏に届ける必要あり公開鍵は公開できる(JWKS、証明書、DNS)
証明のサイズHMAC-SHA256 で 32 バイト64 バイト(Ed25519、ECDSA P-256)、256〜512 バイト(RSA)
速度(Apple M4 Max の 1 コア)256 バイトのメッセージで毎秒約 7,000,000 タグ(OpenSSL 3.6.5)Ed25519 で毎秒約 60,000 署名、P-256 で毎秒約 87,000 署名
主な用途webhook、セッション Cookie、AWS SigV4、TOTP、HS256 の JWT、TLS 1.2 のレコード MACTLS 証明書とハンドシェイク、OIDC の ID トークン、パッケージ署名、Git、パスキー
ポスト量子SHA-256 なら安全(Grover のアルゴリズムでも原像計算の労力が半分になるだけ)従来の方式は Shor のアルゴリズムで破られるため、ML-DSA などが必要

HMAC のスループットは openssl speed -hmac sha256 で測定しました(256 バイトの入力で約 1.83 GB/s)。署名の速度は openssl speed で測定しました。

違いは信頼モデルにある

HMAC は鍵付きハッシュで、H((K ⊕ opad) ‖ H((K ⊕ ipad) ‖ m)) と計算されます。その安全性の目標は、鍵を持たない者には有効なタグを生成できないことです。ちょうど 2 者が鍵を共有し、互いに偽造しないと信頼している場合は、これで十分です。その場合、有効なタグは「相手がこれを送った」ことを意味するからです。3 者以上になると、この前提は崩れます。どの検証者も他の参加者に対して送信者になりすますことができ、紛争になれば、送信者はタグを作ったのは受信者だと主張できます。

デジタル署名は、署名する能力と検証する能力を切り離します。検証鍵を公開しても、それで誰かが署名できるようになるわけではありません。そのため 1 つの発行者が数千もの検証者に対応でき、証明書、鍵ディレクトリ、アカウントの記録によって鍵が署名者に紐付けられていれば、署名を出所の証拠として監査人や裁判所に提示できます。多数のサービスが利用する ID 関連のトークン(OpenID Connect の ID トークンや、リソースサーバーが検証するアクセストークン)が署名を使うのも、検証側の設定が漏洩しても署名なら無害なのに HMAC では致命的になるのも、この分離があるからです。

実務上のポイント

鍵管理:HMAC シークレットは、ランダムに生成し(HMAC-SHA256 では 256 ビット以上。RFC 7518 §3.2 は HS256 にこれを要求しています)、秘密裏に伝送し、すべての検証者が保存し、すべての場所で一斉に、または 2 つの有効なシークレットを併用する猶予期間を設けてローテーションしなければなりません。署名鍵では、秘密鍵を 1 か所(理想的には HSM や KMS)に保管し、公開された鍵セット内の鍵識別子を使ってローテーションします。

性能とサイズ:HMAC はハッシュ計算 2 回分で、1 コアあたり毎秒数百万回実行でき、タグは 32 バイトです。署名には数十マイクロ秒かかり、サイズは 64 バイト以上です。ほとんどの Web トラフィックでは、どちらも問題になりません。問題になるのはパケット単位の認証や非常に大量の内部メッセージングで、そこでは HMAC か AEAD を使うのが一般的です。

落とし穴:HMAC は定数時間で比較しなければならず、hash(key ‖ message) で代用してはいけません(SHA-256 では伸長攻撃によって破られます)。また、受信したバイト列そのもの(再シリアライズした JSON オブジェクトではなく、生のリクエストボディ)を認証しなければなりません。署名は、検証者が固定したアルゴリズムで検証しなければならず、メッセージ側が選んだアルゴリズムを使ってはいけません。

Node.js 26.10 で実行した次のスクリプトは、同じ webhook のボディを両方の方式で認証します。

Node.js:1 つの webhook ボディに対する HMAC-SHA256 タグと Ed25519 署名
import { createHmac, generateKeyPairSync, randomBytes, sign, timingSafeEqual, verify } from "node:crypto";

const body = Buffer.from('{"event":"invoice.paid","id":"evt_123"}');

// HMAC: sender and receiver share one secret (in production it comes from a secret store).
const secret = randomBytes(32);
const tag = createHmac("sha256", secret).update(body).digest();
const expected = createHmac("sha256", secret).update(body).digest();
console.log("HMAC-SHA256 tag:", tag.length, "bytes, valid:", timingSafeEqual(tag, expected));

// Signature: sender keeps the private key, receivers hold only the public key.
const { privateKey, publicKey } = generateKeyPairSync("ed25519");
const signature = sign(null, body, privateKey);
console.log("Ed25519 signature:", signature.length, "bytes, valid:", verify(null, body, publicKey, signature));
出力
HMAC-SHA256 tag: 32 bytes, valid: true
Ed25519 signature: 64 bytes, valid: true

HMAC のシークレットはすべての検証者と共有しなければなりません。一方、Ed25519 の秘密鍵は送信者の手元から出ることがなく、配布されるのは 32 バイトの公開鍵だけです。

webhook と API の実際

ほとんどの webhook プロバイダーは、購読者ごとに専用のシークレットを発行するため HMAC を使います。GitHub は、X-Hub-Signature-256: sha256= の後にボディの HMAC-SHA256 を 16 進数で付けて送ります。Stripe は、受信側がリプレイを拒否できるよう、タイムスタンプとボディを合わせたものに署名し、Stripe-Signature ヘッダーで送ります。購読者ごとのシークレットが一意であるため、多者間の問題は生じません。HTTP メッセージ署名(RFC 9421)や JWS に基づく署名付き webhook が使われるのは、1 つの送信者が、シークレットを共有すべきでない多数の受信者とやり取りする場合や、受信側がシークレットを用意せずに検証したい場合です。AWS Signature Version 4 は HMAC ベースのリクエスト署名です。OAuth DPoP と Web Bot Auth が署名を使うのは、検証者がクライアントになりすませてはならないからです。

どちらを選ぶべきか

  • 同じサービスが発行と検証を行う(セッション Cookie、CSRF トークン、社内の JWT):HMAC-SHA256 を使います。
  • 送信者も受信者も 1 つで、シークレットを帯域外で共有済み(顧客ごとの webhook):署名対象のデータにタイムスタンプを含めた HMAC を使います。
  • 発行者が 1 つで、検証者が多数(ID トークン、公開 API、サーバーのデータを検証するモバイルアプリ):署名を使い、鍵は JWKS で公開します。
  • 第三者に対する証拠(契約書、監査ログ、ソフトウェアのリリース):署名に信頼できるタイムスタンプを組み合わせます。
  • 互いになりすませてはならないクライアント(DPoP、ボット認証、デバイスアテステーション):クライアントごとの鍵による署名を使います。
  • 信頼できる経路での非常に高いメッセージレート:HMAC または AEAD を使います。

移行時の注意点

HMAC から署名への移行は、たいてい 2 つ目の検証者が現れたときか、シークレットが漏洩したときに行われます。

  1. アルゴリズム識別子を導入する:形式にまだない場合は追加します(JOSE の alg、HTTP 署名の alg パラメーター、バージョンを示すプレフィックスなど)。こうすれば検証者は、メッセージを盲目的に信頼することなく、どの方式が使われているかを判断できます。
  2. 公開鍵を公開する:鍵識別子を付けて公開し(JWKS、HTTP 署名の鍵ディレクトリ)、検証者が取得するための時間を確保します。
  3. 移行期間中は二重に署名する:HMAC のヘッダーと署名の両方を送り、検証者には署名を優先させます。
  4. 検証だけを行う検証者からは共有秘密を削除します。これこそがセキュリティ上の利点なので、「念のため」に残しておいてはいけません。
  5. 1 つの鍵を両方の方式に使ってはいけません。RSA 公開鍵を HMAC シークレットとして流用することこそ、JWT ライブラリに脆弱性をもたらしたアルゴリズム混同です。HS256 と RS256 の比較を参照してください。

参考資料

すべての比較