概要
スマホ決済で支払いをするとき、アプリには金額や支払先が表示されます。
ネットショッピングでは、注文内容や配送先を確認してから購入ボタンを押します。
ソフトウェアをインストールするときには、「このファイルは本当に公式が配布したものなのか」が気になることもあります。
普段はあまり意識しませんが、デジタルの世界では、次のような確認がとても大切です。
- 送られてきた内容が、途中で書き換えられていないか
- そのメッセージは、本当に信頼できる相手から届いたものなのか
- 後から第三者にも「この人が確認した」と示せるのか
たとえば、支払い金額が途中で 1,000円 から 10,000円 に変わっていたら困ります。
また、公式アプリを装った偽物の通知や、正規の更新ファイルに見せかけた不正ファイルを信じてしまうのも危険です。
このような問題を防ぐために使われる代表的な仕組みが、MAC と デジタル署名 です。
どちらも、ざっくり言えば「メッセージが正しいかを確認するための印」のような役割を持ちます。
ただし、MACとデジタル署名は同じものではありません。
大きな違いは、誰がその印を作れて、誰が確認できるのか です。
MACは、同じ秘密を知っている相手同士で確認する仕組みです。
たとえるなら、合言葉を知っている仲間同士で「これは仲間内の正しいメッセージだ」と確認するようなイメージです。
一方、デジタル署名は、秘密鍵で署名を作り、公開鍵で検証する仕組みです。
たとえるなら、本人だけが押せる印鑑と、誰でも確認できる印鑑証明のような関係に近いです。
ただし、この例えはあくまで理解の入口です。
実際のデジタル署名は、手書きサインの画像や印鑑の写真ではなく、数学的な処理によって検証できるデータとして作られます。
この記事では、MACとデジタル署名について、次の流れで整理していきます。
- 日常のサービスで、どのような「確認」が必要になるのかを見る
- MACとデジタル署名の違いを先に比較する
- ハッシュ、MAC、デジタル署名の関係を整理する
- MACが使いやすい場面を理解する
- デジタル署名が使いやすい場面を理解する
- 実装・運用で注意すべき点を確認する
この記事のゴールは、MACやデジタル署名の数式をすべて理解することではありません。
まずは、暗号技術を使う場面で「この場合はMACでよいのか」「第三者にも確認してもらうなら署名が必要なのか」を考えられるようになることを目指します。
💡 豆知識
「デジタル署名」と聞くと、PDFに手書きサインの画像を貼るようなものを想像するかもしれません。
しかし、暗号技術としてのデジタル署名は、見た目のサイン画像ではありません。
秘密鍵を使って作られ、公開鍵で検証できる「改ざん検知つきの確認データ」と考える方が近いです。
この記事の立ち位置
この記事は、暗号技術の全体像を整理する記事から派生した個別記事です。
全体像の記事では、暗号技術を次の3つの視点から整理しました。
- 何を守るのか
- どの道具を使うのか
- 実サービスではどう組み合わせるのか
この記事では、その中でも特に 完全性、認証、否認防止 に関係するMACとデジタル署名に注目します。
| 観点 | ざっくり言うと | 関係する技術 |
|---|---|---|
| 完全性 | 中身が勝手に書き換えられていないこと | ハッシュ、MAC、デジタル署名、AEAD |
| 認証 | 相手や送信者が本物だと確認すること | MAC、デジタル署名、証明書 |
| 否認防止 | 後から「やっていない」と否定しにくくすること | デジタル署名 |
ここで押さえておきたいのは、MACやデジタル署名は「中身を隠す技術」ではないという点です。
中身を読めなくするには、暗号化が必要になります。
MACやデジタル署名の主な役割は、内容が変わっていないか、正しい相手が作ったものか を確認することです。
| よくある理解 | 本記事で整理すること |
|---|---|
| MACも署名も、なんとなく「認証っぽい技術」 | 鍵の持ち方と検証できる人の違いを見る |
| デジタル署名は、電子サインの画像のようなもの | 実際には秘密鍵で作り、公開鍵で検証するデータとして理解する |
| 署名されていれば中身も隠れている | 署名と暗号化は別物として整理する |
| ライブラリを使えば安全 | 鍵管理、署名対象、リプレイ対策まで含めて考える |
このように、この記事ではMACとデジタル署名を単なる用語として覚えるのではなく、どの確認に使うのか、どの場面で向いているのか という視点で見ていきます。
この記事で分かること
この記事で分かることは次のとおりです。
- MACとデジタル署名が必要になる身近な場面
- ハッシュ、MAC、デジタル署名の違い
- MACが「共有鍵を使う確認」であること
- デジタル署名が「秘密鍵で署名し、公開鍵で検証する確認」であること
- MACとデジタル署名の使い分け
- API通信、ソフトウェア配布、ブロックチェーンなどでの利用イメージ
- 実装・運用で注意すべき鍵管理やリプレイ攻撃の考え方
対象読者
この記事は、次のような人を想定しています。
- 情報セキュリティを学び始めた人
- 「ハッシュ」「MAC」「デジタル署名」の違いがまだ曖昧な人
- API認証やJWTで出てくる署名・MACの意味を整理したい人
- ブロックチェーンや電子署名で使われるデジタル署名の位置づけを知りたい人
- 暗号技術を数式からではなく、まず利用場面から理解したい人
本記事で扱わないこと
この記事では、MACとデジタル署名の使い分けを理解することを目的にしています。
そのため、以下は詳しく扱いません。
- HMAC、CMAC、KMACの内部構造
- RSA署名、ECDSA、EdDSAの数学的な仕組み
- 本番環境向けの完全な実装コード
- 電子署名法など、法律上の電子署名の詳細
- 証明書発行やPKI運用の詳細
- 耐量子デジタル署名の詳しいアルゴリズム
これらは、今後の個別記事で必要に応じて深掘りします。
この記事の読み進め方
この記事では、説明を分かりやすくするために、表、Mermaid図、Pythonコードを使っています。
ただし、すべてのコードを細かく追わなくても、MACとデジタル署名の違いは理解できるようにしています。
| 読み方 | おすすめする読み進め方 |
|---|---|
| まず全体像だけ知りたい | 概要、1章、2章、6章、9章を読む |
| ハッシュ・MAC・署名の関係を整理したい | 3章を読む |
| API通信でHMACを使うイメージを知りたい | 4章と8章を読む |
| デジタル署名の検証イメージを知りたい | 5章を読む |
| 実装時の注意点を確認したい | 8章を読む |
コード例は、実装をそのまま本番利用するためではなく、何がMACや署名の対象になるのか を理解するための学習用サンプルです。
実際のシステムでは、鍵管理、エラー処理、監査ログ、時刻同期、ライブラリの選定などを別途設計する必要になります。
1. 日常のサービスでは、どんな確認が必要になるのか
ここからは、MACとデジタル署名に入る前に、まず「なぜそのような仕組みが必要なのか」を身近な例から整理していきます。
MACやデジタル署名は、いきなり用語だけを見ると少し難しく感じます。
しかし、実際のサービスで起きてほしくないことから考えると、役割がかなり見えやすくなります。
1.1 スマホ決済:金額が途中で変わっていないか
コンビニでスマホ決済をするとき、画面には支払い先や金額が表示されます。
利用者としては、「表示された金額で支払われる」と信じて操作します。
ここで、もし通信の途中で支払い金額が書き換えられていたらどうでしょうか。
本来の支払い内容: amount=1000&to=shop_a
改ざん後の内容: amount=10000&to=shop_a
このような改ざんを見つけるには、単にデータを送るだけではそれだけでは足りません。
「送信した内容と、受信した内容が同じか」を確認する仕組みが必要になります。
この確認に関係するのが、ハッシュ、MAC、デジタル署名です。
1.2 API通信:本当に信頼できる相手から来たのか
Webサービス同士がAPIで連携する場面を考えます。
たとえば、決済サービス、在庫管理サービス、通知サービスなどは、APIを通じてリクエストをやり取りします。
このとき重要なのは、内容が変わっていないことだけではありません。
「このリクエストは、本当に信頼できる相手から送られてきたのか」も確認したくなります。
たとえば、サーバーAとサーバーBだけが知っている秘密の鍵を使って確認用の値を作れば、外部の第三者が正しい値を作ることは難しくなります。
このように、同じ秘密を知っている相手同士で確認する 場面では、MACがよく使われます。
1.3 ソフトウェア配布:本当に公式が出したものか
ソフトウェアをインストールするときには、「このファイルは本当に公式が配布したものなのか」が重要になります。
ここでは、検証する人が1人とは限りません。
世界中の利用者、OS、ブラウザ、パッケージマネージャなど、多くの人やシステムが確認する可能性があります。
この場合、MACのように全員が同じ秘密鍵を持つ設計はあまり現実的ではありません。
もし全員が同じ秘密鍵を持っていたら、その鍵が漏れた時点で誰でも正しい確認用の値を作れてしまうからです。
そこで使いやすいのが、デジタル署名です。
配布者は秘密鍵で署名を作り、利用者は公開鍵で検証します。
1.4 この章で押さえたいこと
ここまでの例を整理すると、確認したいことによって向いている技術が変わります。
| 場面 | 確認したいこと | 向いている考え方 |
|---|---|---|
| スマホ決済 | 金額や支払先が途中で変わっていないか | ハッシュ、MAC、デジタル署名 |
| API通信 | 信頼できる相手から来たリクエストか | MAC |
| ソフトウェア配布 | 第三者にも公式配布物だと確認してもらえるか | デジタル署名 |
| 証明書 | 公開鍵が本当にその相手のものか | デジタル署名、PKI |
| ブロックチェーン送金 | 秘密鍵の持ち主が取引を作ったか | デジタル署名 |
MACとデジタル署名は、どちらも「メッセージの正しさを確認する技術」です。
しかし、確認する相手が限られているのか、第三者にも確認してもらいたいのか によって、使いどころが変わります。
次の章では、この違いを先に比較表で整理していきます。
2. まず結論:MACとデジタル署名の違い
前の章では、スマホ決済やソフトウェア更新のような身近な場面を例に、デジタルの世界では「中身が変わっていないか」「本物の相手から届いたものか」を確認する必要があることを見ました。
ここからMACとデジタル署名を詳しく見ていきますが、先に全体像をつかんでおきます。
MACとデジタル署名は、どちらも メッセージの正しさを確認するための技術 です。
ただし、同じように見えても、使っている鍵の考え方が大きく違います。
ざっくり言うと、次のように分けられます。
- MAC:同じ秘密を知っている相手同士で確認する仕組み
- デジタル署名:秘密鍵で作った署名を、公開鍵で確認する仕組み
この違いが、使いどころの違いにつながります。
2.1 比較表で全体像を見る
まずは、MACとデジタル署名の違いを表でざっくり眺めてみます。
| 比較項目 | MAC | デジタル署名 |
|---|---|---|
| ざっくり言うと | 同じ秘密を知っている仲間内の確認 | 公開鍵で確認できる署名 |
| 使う鍵 | 共有鍵 | 秘密鍵と公開鍵 |
| 作れる人 | 共有鍵を持つ人 | 秘密鍵を持つ人 |
| 確認できる人 | 共有鍵を持つ人 | 公開鍵を持つ人 |
| 改ざん検知 | できる | できる |
| 送信者確認 | 共有鍵を持つ相手から来たことを確認する | 対応する秘密鍵の持ち主が署名したことを確認する |
| 第三者への証明 | 苦手 | 得意 |
| 使いやすい場面 | API通信、内部システム、サーバー間通信 | 証明書、ソフトウェア署名、電子契約、ブロックチェーン |
ここで一番注目したいのは、確認できる人の違い です。
MACは、共有鍵を持っている人だけが作れて、共有鍵を持っている人だけが確認できます。
そのため、API通信やサーバー間通信のように「関係者だけが分かればよい」場面に向いています。
一方、デジタル署名は、秘密鍵を持っている人が署名を作り、公開鍵を持っている人が検証できます。
公開鍵は名前の通り公開できるため、第三者にも確認してもらいやすい点が特徴です。
NIST FIPS 198-1では、HMACは暗号学的ハッシュ関数と共有秘密鍵を組み合わせて使うメッセージ認証の仕組みとして説明されています。
参考: NIST FIPS 198-1 - The Keyed-Hash Message Authentication Code (HMAC)
また、NIST FIPS 186-5では、デジタル署名はデータの不正な変更を検出し、署名者の身元を認証するために使われると説明されています。さらに、署名されたデータの受信者は、その署名が主張された署名者によって生成されたことを第三者に示す証拠として利用できます。
参考: NIST FIPS 186-5 - Digital Signature Standard
2.2 図で見る:鍵の持ち方が違う
文章だけだと少し分かりにくいので、鍵の持ち方を図にすると次のようになります。
MACでは、作る側も確認する側も同じ共有鍵を使います。
そのため、共有鍵を知っている人同士の間では便利ですが、外部の第三者に「この人が作った」と示す用途には向きにくいです。
デジタル署名では、署名を作るための秘密鍵と、署名を確認するための公開鍵が分かれています。
秘密鍵は本人だけが守り、公開鍵は検証したい人に渡せます。
この構造のおかげで、デジタル署名は「本人だけが作れて、みんなが確認できる」仕組みに近くなります。
💡 豆知識
MACは「合言葉を知っている仲間同士の確認」に近いです。
デジタル署名は「本人だけが押せる印鑑と、誰でも確認できる証明書」に近いです。
ただし、これはあくまでイメージです。実際には、どちらも鍵とアルゴリズムに基づいて計算されるデータです。
2.3 どちらも「暗号化」ではない
MACとデジタル署名を理解するときに、最初に注意したいことがあります。
それは、MACやデジタル署名を付けても、中身が自動的に隠れるわけではない という点です。
たとえば、次のように整理できます。
| 技術 | 主な目的 | 中身を隠すか |
|---|---|---|
| 暗号化 | 内容を読まれにくくする | 隠す |
| MAC | 改ざんされていないか、共有鍵を持つ相手から来たかを確認する | 隠さない |
| デジタル署名 | 改ざんされていないか、秘密鍵の持ち主が署名したかを確認する | 隠さない |
たとえば、注文内容にデジタル署名を付けたとしても、注文内容そのものが暗号化されていなければ、中身は読める場合があります。
同じように、APIリクエストにMACを付けても、通信内容を隠したい場合はTLSなど別の仕組みで通信経路を保護する必要になります。
ここは、最初のうちはかなり混同しやすいポイントです。
MACやデジタル署名は、主に 「内容が変わっていないか」「正しい相手が作ったものか」 を確認する技術です。
「内容を読めなくする」ための技術ではありません。
2.4 具体例で考える:API通信ならMAC、公式配布なら署名
ここまでの説明だけだと少し抽象的なので、具体例で考えてみます。
API通信で考える
あるWebサービスのサーバーAが、決済サービスのサーバーBにAPIリクエストを送る場面を考えます。
このとき、サーバーBは次のことを確認したいはずです。
- リクエスト内容が途中で書き換えられていないか
- 本当にサーバーAから送られてきたものか
- 過去のリクエストをコピーして再送したものではないか
このような場面では、サーバーAとサーバーBだけが共有鍵を持ち、その共有鍵を使ってMACを作る設計が使われることがあります。
この場合、サーバーBは「共有鍵を知っている相手が作った可能性が高いリクエストだ」と確認できます。
ただし、MACだけでリプレイ攻撃を自動的に防げるわけではありません。
過去に送られた正しいリクエストを、そのままもう一度送られる可能性があるためです。
そのため、実際にはタイムスタンプやnonceのような値を組み合わせて、「古いリクエストの使い回しではないか」も確認する必要になります。
ソフトウェア配布で考える
次に、公式サイトからソフトウェアをダウンロードする場面を考えます。
利用者は次のことを確認したくなります。
- このファイルは途中で改ざんされていないか
- 本当に公式の開発元が配布したものか
- 自分以外の人も同じように確認できるか
このような場面では、デジタル署名が向いています。
開発元は秘密鍵でソフトウェアに署名し、利用者は公開鍵や証明書を使って署名を検証します。
公開鍵で検証できるため、多くの利用者が「この署名は正しいか」を確認できます。
このように、MACとデジタル署名はどちらも「正しさを確認する」技術ですが、使いやすい場面が違います。
関係者だけで確認できればよいなら、MACが使いやすい場合があります。
第三者や多くの利用者にも確認してもらいたいなら、デジタル署名が向いています。
2.5 JWTやJWSでも両方の考え方が出てくる
WebアプリやAPIを学んでいると、JWTという言葉を見かけることがあります。
JWTそのものの詳しい説明は別記事に回しますが、ここでもMACとデジタル署名の違いが関係します。
JWTでよく使われるJWSという仕様では、JSONベースのデータ構造を使って、デジタル署名またはMACにより内容を保護する仕組みが定義されています。
参考: RFC 7515 - JSON Web Signature (JWS)
ここで押さえておきたいのは、JWSという名前に「Signature」と入っていても、使うアルゴリズムによってはデジタル署名ではなくMACの場合もある、という点です。
たとえば、HMAC系のアルゴリズムを使う場合は、共有鍵を使って検証します。
一方、RSA署名やECDSAのような方式を使う場合は、秘密鍵と公開鍵を使って検証します。
💡 豆知識
JWTの中身は、署名されていても暗号化されているとは限りません。
署名は「改ざんされていないこと」を確認するためのものです。
中身を隠したい場合は、JWEなど暗号化を含む別の仕組みを検討する必要になります。
2.6 この章のまとめ
この章では、MACとデジタル署名の違いを先に整理しました。
ポイントは、次の3つです。
- MACは、共有鍵を使ってメッセージの正しさを確認する仕組み
- デジタル署名は、秘密鍵で署名し、公開鍵で検証する仕組み
- 第三者にも確認してもらいたい場面では、デジタル署名が向いている
どちらも「改ざんされていないか」を確認できますが、誰が作れて、誰が確認できるのか が違います。
次の章では、この違いをさらに理解しやすくするために、まずハッシュ関数との関係を整理していきます。
「ハッシュだけでは何が足りないのか」を見ると、MACとデジタル署名の役割がより見えやすくなります。
3. ハッシュ・MAC・デジタル署名の関係
前の章では、MACとデジタル署名の違いを先に比較しました。
ここからは、その違いをもう少し丁寧に理解するために、まず ハッシュ関数 との関係を整理していきます。
MACとデジタル署名をいきなり理解しようとすると、少し難しく感じます。
しかし、次のように段階を分けると見通しやすくなります。
- ハッシュ関数で、データの「指紋」を作る
- 共有鍵を加えると、MACになる
- 秘密鍵と公開鍵を使うと、デジタル署名になる
ここでいう「指紋」は、データから計算される短い値のことです。
元のデータが少しでも変わると、指紋にあたる値も大きく変わるため、改ざん検知の材料になります。
NIST FIPS 180-4では、SHA-256などのハッシュアルゴリズムによってメッセージのダイジェストを生成し、そのダイジェストはメッセージが変更されたかを検出するために使われると説明されています。
参考: NIST FIPS 180-4 - Secure Hash Standard
ただし、ハッシュ関数だけでは「誰が作った値なのか」までは分かりません。
ここが、MACやデジタル署名につながる重要なポイントです。
ここから先のコード例では、すべての処理を暗記する必要はありません。
まずは、次の3点だけ意識して読むと理解しやすくなります。
| 見るポイント | 注目すること |
|---|---|
| ハッシュ | データが少し変わると、指紋のような値も変わる |
| MAC | メッセージだけでなく、共有鍵も使って確認用の値を作る |
| デジタル署名 | 秘密鍵で署名し、公開鍵で検証する |
3.1 まずは全体像を見る
ハッシュ、MAC、デジタル署名の関係をざっくり並べると、次のようになります。
| 技術 | 鍵を使うか | 確認できること | 確認しにくいこと |
|---|---|---|---|
| ハッシュ関数 | 使わない | データが変わったかどうか | 誰が作ったか |
| MAC | 共有鍵を使う | 共有鍵を知る相手が作ったこと、データが変わっていないこと | 第三者への証明 |
| デジタル署名 | 秘密鍵・公開鍵を使う | 秘密鍵の持ち主が署名したこと、データが変わっていないこと | 中身を隠すこと |
図にすると、次のような関係です。
ここで押さえておきたいのは、3つの技術がまったく別々に存在しているわけではないことです。
ハッシュ関数は、MACやデジタル署名の中でも使われることがあります。
たとえばHMACは、暗号学的ハッシュ関数と共有秘密鍵を組み合わせるMACです。
NIST FIPS 198-1でも、HMACは暗号学的ハッシュ関数と共有秘密鍵を組み合わせて使うメッセージ認証の仕組みとして説明されています。
参考: NIST FIPS 198-1 - The Keyed-Hash Message Authentication Code (HMAC)
また、デジタル署名でも、署名対象のデータをそのまま扱うのではなく、内部でハッシュ処理が関係する方式があります。
ただし、署名方式によって処理の詳細は異なるため、実装するときは、利用するライブラリや標準仕様に従うことが大切です。
3.2 ハッシュ関数:データの指紋を作る
まずは、一番シンプルなハッシュ関数から見ていきます。
ハッシュ関数は、入力データから固定長の値を作ります。
たとえば、"pay:1000:alice" という支払いメッセージから、SHA-256でハッシュ値を作るとします。
Pythonでは、標準ライブラリの hashlib を使って次のように確認できます。
import hashlib
# 支払い内容を表すメッセージ
message = b"pay:1000:alice"
# SHA-256でメッセージのハッシュ値を計算する
# hexdigest() により、人間が読みやすい16進数文字列に変換する
digest = hashlib.sha256(message).hexdigest()
print(digest)
ここで、支払い金額が 1000 から 10000 に変わった場合を考えます。
import hashlib
# 元のメッセージ
message = b"pay:1000:alice"
# 改ざんされたメッセージ
# 金額が 1000 から 10000 に変わっている
tampered_message = b"pay:10000:alice"
# それぞれのハッシュ値を計算する
original_digest = hashlib.sha256(message).hexdigest()
tampered_digest = hashlib.sha256(tampered_message).hexdigest()
print("元のハッシュ値:", original_digest)
print("変更後のハッシュ値:", tampered_digest)
print("一致するか:", original_digest == tampered_digest)
メッセージが少し変わるだけでも、ハッシュ値は大きく変わります。
この性質により、ハッシュ値は「データが変わったかどうか」を確認する材料になります。
ただし、ここで注意が必要です。
ハッシュ関数は、基本的に誰でも計算できます。
つまり、攻撃者がメッセージを書き換えたあと、その書き換え後のメッセージに対するハッシュ値を新しく計算することもできてしまいます。
そのため、ハッシュ値だけでは次のような確認はできません。
- そのハッシュ値を誰が作ったのか
- 本当に信頼できる相手から届いたのか
- 第三者に対して「この人が作った」と示せるのか
💡 豆知識
ハッシュ関数は「暗号化」と呼ばれることがありますが、厳密には元に戻すための処理ではありません。
暗号化は鍵があれば復号できますが、ハッシュ値は元データを復元する目的で作るものではありません。
そのため、「ハッシュを復号する」という表現は、暗号技術の文脈では少し不自然です。
3.3 MAC:共有鍵を使って、誰でも作れる問題を防ぐ
ハッシュ関数だけでは、誰でも値を作れてしまいます。
そこで、共有鍵を使って「鍵を知っている相手だけが作れる確認用の値」を作るのがMACです。
ここでは代表例として、HMACを見てみます。
Pythonでは標準ライブラリの hmac を使えます。
import hashlib
import hmac
import secrets
# 実際のシステムでは、共有鍵は安全な場所で管理する
# ここではデモ用にランダムな32バイトの鍵を生成する
secret_key = secrets.token_bytes(32)
# 送信したいメッセージ
message = b"pay:1000:alice"
# HMACタグを作成する
# 第3引数に hashlib.sha256 を指定して、内部でSHA-256を使う
tag = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print("HMACタグ:", tag)
受信側は、同じ共有鍵を使ってHMACタグをもう一度計算し、受信したタグと一致するかを確認していきます。
import hashlib
import hmac
import secrets
# 送信者と受信者が共有している秘密鍵
secret_key = secrets.token_bytes(32)
# 元のメッセージ
message = b"pay:1000:alice"
# 送信者が作成したHMACタグ
sent_tag = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# 受信側で、同じメッセージと共有鍵からHMACタグを再計算する
calculated_tag = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# HMAC値の比較には compare_digest() を使う
# == による単純比較より、タイミング攻撃の影響を受けにくくするため
is_valid = hmac.compare_digest(sent_tag, calculated_tag)
print("検証結果:", is_valid)
Python公式ドキュメントでも、検証処理で hexdigest() の出力を外部から受け取った値と比較する場合、== ではなく hmac.compare_digest() の利用が推奨されています。
これは、比較処理にかかる時間差から情報が漏れる可能性を減らすためです。
参考: Python Documentation - hmac
では、メッセージが途中で書き換えられた場合はどうなるでしょうか。
import hashlib
import hmac
import secrets
# 共有鍵を生成する
secret_key = secrets.token_bytes(32)
# 正しいメッセージ
message = b"pay:1000:alice"
# 送信者が正しいメッセージに対してHMACタグを作る
sent_tag = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# 攻撃者がメッセージを書き換えたと仮定する
# ただし、攻撃者は共有鍵を知らないため、正しいHMACタグを作り直せない
tampered_message = b"pay:10000:alice"
# 受信側は、受け取ったメッセージに対してHMACタグを再計算する
calculated_tag = hmac.new(secret_key, tampered_message, hashlib.sha256).hexdigest()
# 送られてきたタグと、受信側で再計算したタグを比較する
is_valid = hmac.compare_digest(sent_tag, calculated_tag)
print("検証結果:", is_valid)
この場合、メッセージが変わっているため、検証結果は失敗します。
ハッシュ関数だけの場合と違い、HMACでは共有鍵が必要です。
そのため、共有鍵を知らない攻撃者は、書き換え後のメッセージに対する正しいタグを作ることが難しくなります。
ただし、ここにも注意点があります。
MACを検証できる人は、基本的に同じ共有鍵を持っています。
そのため、第三者から見ると「送信者が作ったのか、受信者が自分で作ったのか」を区別しにくい場合があります。
この考え方から、MACは当事者間の確認には向いていますが、第三者への証明にはあまり向いていません。
3.4 デジタル署名:公開鍵で確認できるようにする
第三者にも確認してもらいたい場合は、デジタル署名が向いています。
デジタル署名では、署名を作るための 秘密鍵 と、署名を確認するための 公開鍵 を使います。
秘密鍵は署名者だけが守り、公開鍵は検証したい人に渡せます。
NIST FIPS 186-5では、デジタル署名はデータの不正な変更を検出し、署名者の身元を認証するために使われると説明されています。
また、受信者は署名されたデータについて、その署名が主張された署名者によって生成されたことを第三者に示す証拠として利用できます。
参考: NIST FIPS 186-5 - Digital Signature Standard
ここでは、Pythonの cryptography ライブラリを使って、Ed25519による署名のイメージを見ます。
Ed25519は、EdDSAというデジタル署名アルゴリズムの一種です。RFC 8032では、EdDSAがEdwards曲線に基づく署名方式として説明されています。
参考: RFC 8032 - Edwards-Curve Digital Signature Algorithm
注意
次のコードは、仕組みを理解するための最小例です。
本番環境では、秘密鍵の保存、鍵のローテーション、公開鍵の信頼確認、例外処理、ライブラリのバージョン管理などを含めて設計する必要になります。
# 事前に cryptography ライブラリをインストールしておく
# pip install cryptography
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 署名者が秘密鍵を生成する
# 実際のシステムでは、秘密鍵は安全な場所に保存する
private_key = Ed25519PrivateKey.generate()
# 公開鍵は、署名を検証する相手に渡せる
public_key = private_key.public_key()
# 署名したいメッセージ
message = b"software-release:v1.0.0"
# 秘密鍵で署名を作る
signature = private_key.sign(message)
print("署名のバイト長:", len(signature))
# 公開鍵で署名を検証する
# 検証に成功した場合、例外は発生しない
try:
public_key.verify(signature, message)
print("署名は正しいです")
except InvalidSignature:
print("署名は正しくありません")
次に、メッセージが途中で書き換えられた場合を見ます。
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 秘密鍵と公開鍵を準備する
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# 署名者が正しいメッセージに署名する
message = b"software-release:v1.0.0"
signature = private_key.sign(message)
# 攻撃者がメッセージを書き換えたと仮定する
tampered_message = b"software-release:v9.9.9"
# 公開鍵で、改ざん後のメッセージと元の署名を検証する
try:
public_key.verify(signature, tampered_message)
print("署名は正しいです")
except InvalidSignature:
print("署名は正しくありません")
この場合、署名は元のメッセージに対して作られているため、改ざん後のメッセージでは検証に失敗します。
デジタル署名の特徴は、検証に公開鍵を使えることです。
公開鍵を持っている人なら、署名が正しいかを確認できます。
そのため、ソフトウェア配布、証明書、ブロックチェーンのように、多くの人が検証する場面に向いています。
3.5 3つの違いをコードの観点で整理する
ここまでのコード例をもとに、ハッシュ、MAC、デジタル署名の違いをもう一度整理していきます。
| 技術 | コードで見た処理 | 重要なポイント |
|---|---|---|
| ハッシュ | hashlib.sha256(message) |
誰でも計算できる。データの変化は見つけやすいが、作成者は分からない。 |
| HMAC | hmac.new(secret_key, message, hashlib.sha256) |
共有鍵が必要。鍵を知る相手同士で確認できる。 |
| デジタル署名 |
private_key.sign(message) / public_key.verify(...)
|
秘密鍵で署名し、公開鍵で検証する。第三者にも確認してもらいやすい。 |
この違いは、利用場面を考えると分かりやすくなります。
たとえば、ファイルのダウンロードページにハッシュ値が掲載されている場合、手元のファイルが途中で壊れていないかを確認する材料になります。
しかし、攻撃者がファイルと一緒に掲載ハッシュ値も書き換えられる状況では、それだけではそれだけでは足りません。
サーバー同士のAPI通信では、共有鍵を使ったHMACで「共有鍵を知る相手から届いたメッセージか」を確認できます。
ただし、第三者に「このサーバーが送った」と証明する用途には向きにくいです。
ソフトウェア配布では、開発元の秘密鍵で署名し、利用者が公開鍵や証明書で検証することで、多くの利用者が同じように確認できます。
3.6 実装例を見るときの注意
ここまで短いコードを見てきましたが、暗号技術では 自分でアルゴリズムを作らない ことが大切です。
HMACの内部処理は、単に hash(key + message) のような形ではありません。
HMACでは、鍵の処理や内側・外側のハッシュ処理などが定義されています。
そのため、実装では自作せず、標準ライブラリや信頼できる暗号ライブラリを使うべきです。
また、デジタル署名でも、秘密鍵の生成・保存・署名・検証には注意が必要です。
署名アルゴリズムの選択だけでなく、公開鍵をどう信頼するか、秘密鍵をどこに保存するか、鍵が漏えいしたときにどう失効するかまで考える必要になります。
NIST SP 800-57 Part 1 Rev.5では、暗号鍵材料の管理や鍵の保護、鍵管理機能に関する一般的なガイダンスが整理されています。
参考: NIST SP 800-57 Part 1 Rev.5 - Recommendation for Key Management
💡 豆知識
暗号技術では、「アルゴリズムを知っていること」と「安全に使えること」は別です。
たとえば、SHA-256、HMAC、Ed25519という名前を知っていても、鍵管理や検証処理を間違えると安全性は崩れます。
実務では、標準仕様・ライブラリ・運用設計をセットで見ることが大切です。
3.7 この章のまとめ
この章では、ハッシュ、MAC、デジタル署名の関係を整理しました。
ハッシュ関数は、データの指紋を作る技術です。
データが変わったかどうかを確認する材料になりますが、誰が作った値なのかは分かりません。
MACは、共有鍵を使ってメッセージの確認用タグを作ります。
共有鍵を知っている相手同士で、メッセージが改ざんされていないかを確認できます。
デジタル署名は、秘密鍵で署名を作り、公開鍵で検証します。
公開鍵で検証できるため、第三者にも確認してもらいたい場面に向いています。
次の章では、まずMACに焦点を当てます。
API通信やサーバー間通信を例にしながら、「同じ秘密を知っている相手同士の確認」とはどういうことなのかを詳しく見ていきます。
4. MAC:同じ秘密を知っている相手との確認
前の章では、ハッシュ、MAC、デジタル署名の関係を整理しました。
ハッシュ関数はデータの「指紋」を作れますが、それだけでは 誰が作った値なのか までは分かりません。
そこで登場するのがMACです。
MACは、共有鍵を使って「このメッセージは、同じ秘密を知っている相手が作った可能性が高い」と確認するための仕組みです。
ここでいう共有鍵は、送信者と受信者だけが知っている秘密の値です。
たとえるなら、関係者だけが知っている合言葉のようなものです。
ただし、実際のMACは、合言葉をメッセージにそのまま書き込むわけではありません。
共有鍵とメッセージを入力として、検証用の短い値を計算します。この検証用の値は、MACタグ や単に タグ と呼ばれることがあります。
NIST FIPS 198-1では、HMACは暗号学的ハッシュ関数を使ったメッセージ認証の仕組みであり、共有秘密鍵と組み合わせて使うと説明されています。
参考: NIST FIPS 198-1 - The Keyed-Hash Message Authentication Code (HMAC)
また、NISTのMessage Authentication Codesプロジェクトでは、汎用的なMACアルゴリズムとして、HMAC、KMAC、CMACが整理されています。
参考: NIST - Message Authentication Codes
4.1 身近な例:APIリクエストに「確認用の印」を付ける
MACが使われる場面として分かりやすいのが、API通信です。
たとえば、自分のWebサービスが決済サービスに対して、次のようなリクエストを送る場面を考えます。
POST /payments
amount=1000
to=alice
決済サービス側としては、次のようなことを確認したいはずです。
- 金額が途中で書き換えられていないか
- 本当に連携先のサービスから送られたものか
- 過去のリクエストをコピーして再送したものではないか
このとき、送信側と受信側が事前に同じ共有鍵を持っていれば、リクエスト内容からMACタグを作れます。
この流れで、受信側は「共有鍵を知っている相手が作った可能性が高いリクエストだ」と判断できます。
ここで押さえておきたいのは、MACタグを見ただけで元の共有鍵が分かるわけではないことです。
受信側は、同じ共有鍵と同じメッセージからタグを再計算し、受信したタグと一致するかを確認していきます。
4.2 MACの基本的な処理の流れ
MACの考え方を、かなり単純化すると次のようになります。
MACタグ = MAC(共有鍵, メッセージ)
受信側は、受け取ったメッセージに対して同じ計算を行います。
受信側で再計算したMACタグ = MAC(共有鍵, 受け取ったメッセージ)
そして、送られてきたMACタグと、受信側で再計算したMACタグが一致するかを確認していきます。
この仕組みにより、受信側は次のことを確認できます。
| 確認したいこと | MACで確認できること |
|---|---|
| 内容が変わっていないか | メッセージが変わるとMACタグも変わるため、改ざんを検出できる |
| 共有鍵を知る相手から来たか | 正しい共有鍵を知らないと、有効なMACタグを作るのが難しい |
| 第三者にも証明できるか | 共有鍵を持つ人ならタグを作れるため、第三者証明には向きにくい |
最後の行が、MACを理解するうえで大切です。
MACは、当事者同士の確認には便利ですが、第三者に対して「この人が作った」と示す用途には向きにくいです。
4.3 HMACの内部イメージ
MACの代表例が、HMACです。
HMACは、ハッシュ関数と共有鍵を組み合わせてMACタグを作ります。
RFC 2104では、HMACは暗号学的ハッシュ関数を使ったメッセージ認証の仕組みとして説明されています。
参考: RFC 2104 - HMAC: Keyed-Hashing for Message Authentication
HMACの内部処理を、かなり簡略化して書くと、次のような二段階のハッシュ処理になっています。
HMAC(K, message) = H((K' xor opad) || H((K' xor ipad) || message))
ここで、各記号は次のような意味です。
| 記号 | 意味 |
|---|---|
K |
元の共有鍵 |
K' |
ハッシュ関数のブロック長に合わせて調整した鍵 |
message |
認証したいメッセージ |
H |
SHA-256などの暗号学的ハッシュ関数 |
ipad / opad
|
HMAC仕様で使われる固定パターン |
| ` |
この式だけ見ると少し難しく感じますが、ここで覚えておきたいことはシンプルです。
HMACは、単純に hash(key + message) とするだけの仕組みではありません。
鍵を調整し、内側と外側の2段階でハッシュ処理を行うように設計されています。
そのため、HMACを使うときは自分で似た処理を作るのではなく、言語やライブラリが提供しているHMAC実装を使うべきです。
💡 豆知識
HMACは「ハッシュ関数に鍵を足しただけ」と説明されることがあります。
入口としては分かりやすい説明ですが、実際には単純な文字列連結ではなく、仕様で決められた形で鍵とハッシュ関数を組み合わせています。
そのため、SHA256(secret + message)のような自作方式で代用しないことが大切です。
4.4 PythonでAPIリクエスト風のHMACを作る
ここでは、APIリクエストにHMACを付ける雰囲気を、Pythonで確認していきます。
次のコードは、実際の本番システムをそのまま作るものではありません。
ただし、MACを使うときに「何をMACの対象に含めるのか」を考える練習になります。
import hashlib
import hmac
import json
import secrets
import time
# 実際のシステムでは、共有鍵は環境変数やシークレット管理サービスで安全に管理する
# ここではデモ用にランダムな32バイトの共有鍵を生成する
shared_key = secrets.token_bytes(32)
# APIリクエストの情報を用意する
method = "POST"
path = "/payments"
body = {
"amount": 1000,
"to": "alice",
}
# リプレイ攻撃対策の材料として、現在時刻とnonceを含める
# nonceは「一度だけ使う値」のイメージ
# 実運用では、同じnonceが再利用されていないかをサーバー側で記録・確認する
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
# JSONの順序や空白の違いでMACが変わらないよう、表現を揃える
# sort_keys=True によりキー順を固定し、separatorsで余計な空白をなくす
canonical_body = json.dumps(body, sort_keys=True, separators=(",", ":"))
# MACの対象にする文字列を作る
# メソッド、パス、本文、時刻、nonceを含めることで、何に対するMACなのかを明確にする
signing_input = "\n".join([
method,
path,
canonical_body,
timestamp,
nonce,
]).encode("utf-8")
# HMAC-SHA256でMACタグを作る
mac_tag = hmac.new(shared_key, signing_input, hashlib.sha256).hexdigest()
print("MAC対象データ:")
print(signing_input.decode("utf-8"))
print("MACタグ:", mac_tag)
この例では、MACの対象に次の情報を含めています。
| 含めた情報 | 考え方 |
|---|---|
| HTTPメソッド |
GET と POST の取り違えを防ぐため |
| パス | 別APIへの転用を防ぐため |
| 本文 | 金額や送金先の改ざんを検出するため |
| タイムスタンプ | 古いリクエストの再利用を検出しやすくするため |
| nonce | 同じリクエストの使い回しを検出しやすくするため |
特に重要なのは、MACの対象に何を含めるか です。
金額だけをMACの対象にして、送金先やAPIパスを含めていなければ、思わぬ使い回しが起きる可能性があります。
4.5 受信側でHMACを検証する
次に、受信側でHMACを検証する例を見ます。
実際のシステムでは、nonceをデータベースやRedisなどに保存し、再利用されていないかを確認していきます。
ここでは、説明を簡単にするために、Pythonの set を使って使用済みnonceを管理します。
import hashlib
import hmac
import json
import time
# 使用済みnonceを記録するための集合
# 実運用では、プロセス再起動で消えないストレージを使う
used_nonces = set()
def build_signing_input(method, path, body, timestamp, nonce):
"""MACの対象にするデータを同じ形式で組み立てる関数。"""
# 送信側と受信側でJSON表現がずれるとMAC検証に失敗するため、表現を固定する
canonical_body = json.dumps(body, sort_keys=True, separators=(",", ":"))
# MAC対象の項目と順番を固定する
return "\n".join([
method,
path,
canonical_body,
timestamp,
nonce,
]).encode("utf-8")
def verify_hmac_request(shared_key, method, path, body, timestamp, nonce, received_tag):
"""受信したAPIリクエストのHMACを検証する関数。"""
# タイムスタンプが数値として扱えるか確認する
try:
request_time = int(timestamp)
except ValueError:
return False
# 古すぎるリクエストを拒否する
# ここでは例として5分を許容範囲にする
now = int(time.time())
if abs(now - request_time) > 300:
return False
# 同じnonceがすでに使われていたら、再送の可能性があるため拒否する
if nonce in used_nonces:
return False
# 受信した内容から、送信側と同じ形式でMAC対象データを組み立てる
signing_input = build_signing_input(method, path, body, timestamp, nonce)
# 受信側でも同じ共有鍵を使ってMACタグを再計算する
expected_tag = hmac.new(shared_key, signing_input, hashlib.sha256).hexdigest()
# タイミング攻撃の影響を減らすため、compare_digest()で比較する
if not hmac.compare_digest(expected_tag, received_tag):
return False
# 検証に成功したnonceを記録し、同じnonceの再利用を防ぐ
used_nonces.add(nonce)
return True
このコードで注目したいのは、HMACの検証だけでなく、タイムスタンプとnonceも確認している点です。
MACは、メッセージの改ざん検知や共有鍵を知る相手の確認に役立ちます。
しかし、過去に送られた正しいリクエストをそのまま再送された場合、そのリクエストに付いているMACタグ自体は正しいままです。
そのため、実際のAPI設計では、MACに加えて、タイムスタンプ、nonce、リクエストID、有効期限などを組み合わせる必要になります。
RFC 7515では、JWSやJWEにおけるリプレイ攻撃対策として、一意なメッセージ識別子を完全性保護された内容に含め、受信側が過去に受信・処理していないことを確認する方法が挙げられています。
参考: RFC 7515 - JSON Web Signature (JWS)
4.6 MACを使うときに注意したいこと
MACは便利ですが、使えば自動的に安全になるわけではありません。
特に、次の点には注意が必要です。
| 注意点 | 内容 |
|---|---|
| 共有鍵を安全に管理する | 共有鍵が漏えいすると、攻撃者も正しいMACタグを作れるようになる |
| MACの対象を明確にする | メソッド、パス、本文、時刻、nonceなど、何を保護するのかを決める |
| 表現を揃える | JSONのキー順や空白の違いで検証に失敗しないよう、正規化する |
| リプレイ攻撃対策を入れる | タイムスタンプ、nonce、リクエストIDなどを組み合わせる |
| 比較方法に注意する | HMAC値の比較には hmac.compare_digest() のような安全な比較関数を使う |
| 第三者証明には使いにくい | 共有鍵を持つ人ならMACを作れるため、外部への証明にはデジタル署名が向く |
Python公式ドキュメントでは、HMAC値を比較するときに == ではなく hmac.compare_digest() を使うことが推奨されています。
参考: Python Documentation - hmac
また、暗号技術では鍵管理が非常に大切です。
NIST SP 800-57 Part 1 Rev.5では、暗号鍵材料の管理、鍵の保護、鍵管理機能などに関する一般的なガイダンスが整理されています。
参考: NIST SP 800-57 Part 1 Rev.5 - Recommendation for Key Management
💡 豆知識
MACは「署名」と似た使われ方をすることがあります。
たとえば、APIドキュメントで「リクエストに署名する」と書かれていても、実際にはHMACのような共有鍵ベースのMACを指している場合があります。
暗号技術として厳密に見ると、MACとデジタル署名は鍵の持ち方が違うため、第三者への証明力も変わります。
4.7 MACの種類をざっくり整理する
ここまでHMACを中心に見てきましたが、MACには複数の方式があります。
NISTのMessage Authentication Codesプロジェクトでは、承認済みの汎用MACアルゴリズムとして、HMAC、KMAC、CMACが挙げられています。
参考: NIST - Message Authentication Codes
| 種類 | ざっくりした特徴 | 関連資料 |
|---|---|---|
| HMAC | ハッシュ関数と共有鍵を組み合わせるMAC | FIPS 198-1、RFC 2104 |
| KMAC | SHA-3由来の関数に基づくMAC | NIST SP 800-185 |
| CMAC | AESなどのブロック暗号に基づくMAC | NIST SP 800-38B |
KMACは、NIST SP 800-185で定義されているSHA-3由来の関数群に含まれるMACです。
参考: NIST SP 800-185 - SHA-3 Derived Functions
CMACは、NIST SP 800-38Bで定義されているブロック暗号ベースのMACです。
参考: NIST SP 800-38B - Recommendation for Block Cipher Modes of Operation: The CMAC Mode for Authentication
この記事では、初学者向けにHMACを中心に説明しています。
ただし、実際のシステムでは、利用するプロトコル、標準、ライブラリ、既存システムとの互換性によって、使われる方式が変わります。
補足
NISTは2025年6月に、FIPS 198-1をFIPSシリーズから撤回し、HMAC仕様をSP 800-224へ移す提案を公表しています。
また、SP 800-224は初期公開ドラフトとして公開されており、FIPS 198-1の内容を取り込みつつ更新する位置づけになっています。
これはHMACという考え方が不要になるという意味ではなく、仕様文書の整理・更新に関する動きです。
参考: NIST - Proposed Withdrawal of FIPS 198-1, Keyed-Hash Message Authentication Code (HMAC)
参考: NIST SP 800-224 Initial Public Draft - HMAC
4.8 この章のまとめ
この章では、MACについて詳しく見てきました。
MACは、共有鍵を使ってメッセージの正しさを確認する仕組みです。
特にHMACは、ハッシュ関数と共有鍵を組み合わせる代表的なMACであり、API通信やサーバー間通信のような場面で理解しやすい例になります。
ただし、MACは「共有鍵を持っている相手同士の確認」に向いた仕組みです。
共有鍵を持つ人ならMACタグを作れるため、第三者に対して「この人が作った」と証明する用途には向きにくいです。
また、MACだけではリプレイ攻撃を自動的に防げません。
過去の正しいリクエストを再送される可能性があるため、タイムスタンプ、nonce、リクエストIDなどを組み合わせて設計する必要になります。
次の章では、デジタル署名に焦点を当てます。
秘密鍵で署名し、公開鍵で検証することで、なぜ第三者にも確認してもらいやすくなるのかを見ていきます。
5. デジタル署名:第三者にも確認してもらえる署名
前の章では、MACについて見てきました。
MACは、共有鍵を知っている相手同士で「このメッセージは正しいか」を確認する仕組みでした。
ただし、MACには苦手な場面があります。
それは、第三者にも確認してもらいたい場面 です。
たとえば、次のような状況を考えてみます。
- 公式サイトからダウンロードしたソフトウェアが、本当に公式が配布したものか確認したい
- Webサイトの証明書が、信頼できる認証局によって発行されたものか確認したい
- ブロックチェーン上の取引が、秘密鍵の持ち主によって作られたものか確認したい
- 電子的な文書について、後から「この人が確認した」と示したい
このような場面では、確認する人が1人とは限りません。
世界中の利用者、ブラウザ、サーバー、ブロックチェーンのノードなど、多くの人やシステムが検証者になることがあります。
ここで使いやすいのが、デジタル署名 です。
5.1 デジタル署名を身近な例で考える
デジタル署名は、よく「電子的なサイン」や「電子的な印鑑」のように説明されます。
ただし、ここでいう署名は、PDFに手書きサインの画像を貼ることではありません。
暗号技術としてのデジタル署名は、次のような仕組みです。
- 署名者だけが持つ 秘密鍵 で署名を作る
- 署名されたデータと署名を公開する
- 検証者は 公開鍵 を使って、署名が正しいか確認する
図にすると、次のような流れになります。
ここで押さえておきたいのは、署名を作る鍵と、署名を確認する鍵が違う という点です。
MACでは、作る側も確認する側も同じ共有鍵を使いました。
一方、デジタル署名では、署名を作るための秘密鍵と、署名を確認するための公開鍵を分けます。
この違いによって、公開鍵を持つ人なら誰でも署名を検証できるようになります。
NIST FIPS 186-5では、デジタル署名はデータの不正な変更を検出し、署名者の身元を認証するために使われると説明されています。
また、署名されたデータの受信者は、その署名が主張された署名者によって生成されたことを第三者に示す証拠として利用できると説明されています。
参考: NIST FIPS 186-5 - Digital Signature Standard
5.2 デジタル署名で確認できること
デジタル署名で確認できることは、主に次の3つです。
| 確認できること | ざっくり言うと | 例 |
|---|---|---|
| 完全性 | 署名後にデータが変わっていないこと | ソフトウェアの配布ファイルが改ざんされていないか |
| 署名者の確認 | 対応する秘密鍵の持ち主が署名したこと | 公式の秘密鍵で署名された更新ファイルか |
| 第三者への証明 | 公開鍵を使って他の人にも確認してもらえること | 署名付き文書や証明書を第三者が検証する |
ここで、MACとの違いがはっきり出ます。
MACでは、共有鍵を持つ人ならMACタグを作れます。
そのため、第三者から見ると「送信者が作ったのか、受信者が作ったのか」を区別しにくい場合があります。
一方、デジタル署名では、署名を作る秘密鍵と、検証する公開鍵が分かれています。
秘密鍵を持っていない人は、正しい署名を作ることが難しいため、公開鍵を使って「この署名は対応する秘密鍵で作られたものか」を確認できます。
💡 豆知識
デジタル署名の「署名」は、見た目をまねするものではありません。
手書きサインは見た目を似せて偽造される可能性がありますが、デジタル署名では、秘密鍵を持っていないと正しい署名を作ることが難しいように設計されています。
そのため、「見た目のサイン」ではなく「公開鍵で検証できる確認データ」と考えると分かりやすいです。
5.3 Pythonでデジタル署名を試す
ここでは、Pythonの cryptography ライブラリを使って、Ed25519による署名と検証を試します。
Ed25519は、EdDSAというデジタル署名方式の一種です。
RFC 8032では、EdDSAはEdwards曲線を使うデジタル署名方式として説明され、edwards25519やedwards448向けの推奨パラメータ、実装例、テストベクトルが示されています。
参考: RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
また、Pythonの cryptography ライブラリの公式ドキュメントでも、Ed25519の署名生成と検証の例が示されています。
参考: cryptography documentation - Ed25519 signing
注意
以下のコードは学習用の最小例です。
本番環境では、秘密鍵の保存、鍵のローテーション、公開鍵の配布方法、署名対象の設計、エラー処理などを別途きちんと考える必要になります。
事前にライブラリが入っていない場合は、次のようにインストールします。
pip install cryptography
まずは、メッセージに署名し、公開鍵で検証する例です。
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 署名者が秘密鍵を生成する
# 実運用では、毎回生成するのではなく、安全に保管された秘密鍵を使う
private_key = Ed25519PrivateKey.generate()
# 検証者に渡すための公開鍵を取り出す
# 公開鍵は署名の検証に使うため、秘密鍵とは異なり公開できる
public_key = private_key.public_key()
# 署名したいメッセージを用意する
# ここではソフトウェア更新の通知をイメージする
message = b"update_package: app_v1.2.3.zip"
# 秘密鍵でメッセージに署名する
signature = private_key.sign(message)
print("署名:", signature.hex())
# 公開鍵で署名を検証する
# 検証に成功した場合、例外は発生しない
try:
public_key.verify(signature, message)
print("検証成功: メッセージは署名後に変更されていません")
except InvalidSignature:
print("検証失敗: メッセージまたは署名が正しくありません")
このコードでは、秘密鍵で message に署名し、公開鍵でその署名を検証しています。
検証に成功するということは、少なくとも「この公開鍵に対応する秘密鍵で署名されたメッセージであり、署名後にメッセージが変わっていない」と判断できる、という意味です。
5.4 メッセージを少し変えると検証に失敗する
デジタル署名は、署名対象のデータと強く結びついています。
そのため、署名後にメッセージを少しでも変えると、検証に失敗します。
次のコードでは、元のメッセージでは検証に成功し、改ざんしたメッセージでは検証に失敗することを確認していきます。
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 秘密鍵と公開鍵を用意する
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# 元のメッセージ
original_message = b"pay:1000yen:to:store-A"
# 元のメッセージに対して署名する
signature = private_key.sign(original_message)
# 攻撃者が金額を書き換えたと仮定する
tampered_message = b"pay:10000yen:to:store-A"
def verify_message(public_key, signature, message):
"""公開鍵で署名を検証する関数。"""
try:
# 署名がmessageに対して正しければ例外は発生しない
public_key.verify(signature, message)
return True
except InvalidSignature:
# メッセージや署名が変わっている場合はここに入る
return False
print("元のメッセージ:", verify_message(public_key, signature, original_message))
print("改ざん後のメッセージ:", verify_message(public_key, signature, tampered_message))
この例では、1000yen を 10000yen に変えるだけで検証に失敗します。
つまり、デジタル署名は「署名者が確認した内容」と「受け取った内容」が同じかどうかを確認するために使えます。
ただし、ここで注意したいのは、デジタル署名は中身を隠しているわけではないという点です。
pay:1000yen:to:store-A という文字列は、署名されていても読めます。
中身を読まれたくない場合は、暗号化も別途考える必要になります。
5.5 実サービスではどこで使われるのか
デジタル署名は、いろいろな場面で使われます。
ここでは、代表的な例を整理していきます。
| 利用場面 | 何を確認しているか |
|---|---|
| ソフトウェア配布 | 配布ファイルが正規の作成者によって署名されたものか |
| TLS証明書 | Webサイトの公開鍵が正しい主体に結びついているか |
| 電子文書 | 文書が署名後に変更されていないか |
| ブロックチェーン | 取引が秘密鍵の持ち主によって作られたものか |
| パッケージ管理 | 配布されるパッケージやメタデータが改ざんされていないか |
たとえば、Webサイトの証明書では、公開鍵が「このドメインや組織に対応するものだ」と信頼できる形で示される必要になります。
RFC 5280では、公開鍵証明書は公開鍵の値と主体を結びつけるデータ構造であり、その結びつきは信頼された認証局が証明書にデジタル署名することで示されると説明されています。
参考: RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and CRL Profile
ここで押さえておきたいのは、公開鍵だけを見ても「その公開鍵が本当に誰のものか」は分からないという点です。
たとえば、攻撃者が自分で作った公開鍵を「これは公式の公開鍵です」と言って配布したら、利用者はだまされるかもしれません。
そのため、実際のシステムでは、証明書、認証局、信頼チェーン、鍵の失効確認などが関係します。
この記事では詳しく扱いませんが、デジタル署名を安全に使うには「公開鍵をどう信頼するか」も大切です。
5.6 署名対象を明確にする
デジタル署名を使うときは、何に署名するのか がとても大切です。
たとえば、ソフトウェア更新の通知に署名する場合を考えます。
ファイル名だけに署名していて、バージョン、ファイルサイズ、ハッシュ値、配布元URLなどを署名対象に含めていなければ、思わぬすり替えが起きる可能性があります。
そのため、実際の設計では、署名対象に含める情報を明確にします。
| 署名対象に含めたい情報 | 考え方 |
|---|---|
| ファイル名 | どのファイルに対する署名か分かるようにする |
| バージョン | 古いバージョンへの差し替えを検出しやすくする |
| ファイルのハッシュ値 | ファイル本体が変わっていないか確認する |
| 配布元 | 別の場所から取得したファイルとの取り違えを防ぐ |
| タイムスタンプ | いつ作られた署名なのか判断しやすくする |
次のコードは、ソフトウェア更新メタデータをJSONとして整形し、その内容に署名する例です。
import json
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 署名者が秘密鍵を用意する
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# ソフトウェア更新メタデータの例
metadata = {
"name": "sample-app",
"version": "1.2.3",
"file": "sample-app-1.2.3.zip",
"sha256": "0123456789abcdef",
"published_at": "2026-06-26T12:00:00+09:00",
}
# JSONは空白やキー順の違いでバイト列が変わるため、表現を固定する
# sort_keys=Trueでキー順を固定し、separatorsで余計な空白をなくす
canonical_metadata = json.dumps(
metadata,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
# 固定したJSON表現に対して署名する
signature = private_key.sign(canonical_metadata)
# 検証側も同じルールでJSONをバイト列に変換してから検証する
try:
public_key.verify(signature, canonical_metadata)
print("検証成功: メタデータは署名後に変更されていません")
except InvalidSignature:
print("検証失敗: メタデータまたは署名が正しくありません")
この例では、JSONの表現を固定してから署名しています。
これは、署名が「データの意味」ではなく、実際には バイト列 に対して行われるためです。
同じJSONに見えても、キーの順番や空白が違えば、署名対象のバイト列は変わります。
そのため、署名対象を作るときは、どの形式で、どの順番で、どの項目を含めるかを明確にする必要になります。
5.7 デジタル署名を使うときに注意したいこと
デジタル署名は強力ですが、使えば自動的に安全になるわけではありません。
特に、次の点に注意が必要です。
| 注意点 | 内容 |
|---|---|
| 秘密鍵を安全に管理する | 秘密鍵が漏えいすると、攻撃者が正しい署名を作れる |
| 公開鍵をどう信頼するか決める | 公開鍵が偽物なら、署名検証に成功しても意味がない |
| 署名対象を明確にする | 重要な項目を署名対象に含めないと、すり替えが起きる可能性がある |
| 古い署名の再利用に注意する | 古いバージョンや期限切れデータを再利用される可能性がある |
| 署名と暗号化を混同しない | 署名は中身を隠す技術ではない |
| 法律上の電子署名と混同しない | 技術的な署名と法的な効力は別に考える必要がある |
特に重要なのは、公開鍵をどう信頼するか です。
デジタル署名の検証は、正しい公開鍵を使っていることが前提になります。
もし偽物の公開鍵を信じてしまうと、攻撃者が自分の秘密鍵で作った署名を「正しい署名」として検証できてしまいます。
そのため、証明書やPKIのように、公開鍵と主体を結びつける仕組みが重要になります。
💡 豆知識
デジタル署名では「公開鍵だから公開してよい」と言いますが、これは「何でも信じてよい」という意味ではありません。
公開鍵は公開してよい一方で、「その公開鍵が誰のものなのか」を確認する仕組みが必要です。
ここを間違えると、正しい署名検証をしているつもりでも、偽物の相手を信じてしまう可能性があります。
5.8 デジタル署名の種類をざっくり整理する
デジタル署名にも複数の方式があります。
この記事では詳細な数学には入りませんが、代表的な方式をざっくり整理しておきます。
| 種類 | ざっくりした特徴 | 関連資料 |
|---|---|---|
| RSA署名 | RSAに基づく歴史の長い署名方式 | RFC 8017、NIST FIPS 186-5 |
| ECDSA | 楕円曲線暗号に基づく署名方式 | NIST FIPS 186-5 |
| EdDSA | Edwards曲線に基づく署名方式 | RFC 8032、NIST FIPS 186-5 |
| ML-DSA | 耐量子暗号として標準化された署名方式 | NIST FIPS 204 |
| SLH-DSA | ハッシュベースの耐量子署名方式 | NIST FIPS 205 |
NIST FIPS 186-5では、RSA、ECDSA、EdDSAを含むデジタル署名方式が扱われています。
参考: NIST FIPS 186-5 - Digital Signature Standard
また、NISTは耐量子暗号の標準として、ML-DSAをFIPS 204、SLH-DSAをFIPS 205として公開しています。
参考: NIST FIPS 204 - Module-Lattice-Based Digital Signature Standard
参考: NIST FIPS 205 - Stateless Hash-Based Digital Signature Standard
ここで押さえておきたいのは、「デジタル署名」という1つの言葉の中にも、複数の方式があるという点です。
実際にどの方式を使うかは、利用するプロトコル、標準、ライブラリ、互換性、将来の移行方針によって変わります。
今回の記事では、MACとの違いを理解することが主目的なので、各署名方式の詳しい数学的な仕組みには入りません。
署名方式ごとの違いは、別記事で深掘りするとよさそうです。
5.9 この章のまとめ
この章では、デジタル署名について見てきました。
デジタル署名は、秘密鍵で署名を作り、公開鍵で検証する仕組みです。
これにより、データが署名後に変わっていないことや、対応する秘密鍵で署名されたことを確認できます。
MACとの大きな違いは、検証に共有鍵を使わないことです。
公開鍵で検証できるため、第三者にも確認してもらいやすいという特徴があります。
ただし、デジタル署名も万能ではありません。
署名は中身を隠す技術ではなく、秘密鍵の管理や公開鍵の信頼、署名対象の設計が大切です。
次の章では、ここまで整理したMACとデジタル署名を、どのように使い分けるべきかを比較します。
6. MACとデジタル署名の使い分け
ここまで、4章ではMAC、5章ではデジタル署名をそれぞれ見てきました。
MACは、共有鍵を知っている相手同士でメッセージの正しさを確認する仕組みでした。
デジタル署名は、秘密鍵で署名を作り、公開鍵で検証する仕組みでした。
では、実際にシステムを設計するときは、どちらを使えばよいのでしょうか。
この章では、MACとデジタル署名を「どちらが優れているか」ではなく、どの場面に向いているか という視点で整理していきます。
6.1 まず判断軸を見る
MACとデジタル署名を使い分けるときは、最初に次の4つを確認すると考えやすいです。
| 判断軸 | MACが向きやすい場合 | デジタル署名が向きやすい場合 |
|---|---|---|
| 検証する人 | 共有鍵を持つ限られた相手 | 公開鍵を持つ多くの人 |
| 鍵の配り方 | 安全に共有鍵を渡せる | 秘密鍵は渡さず、公開鍵だけ配りたい |
| 第三者への証明 | 基本的に不要 | 必要になる |
| 主な利用例 | API通信、内部システム、サーバー間通信 | ソフトウェア署名、証明書、電子契約、ブロックチェーン |
この表で特に大切なのは、検証者が誰か です。
たとえば、自分たちの管理する2つのサーバーだけで確認できればよいなら、MACが自然な選択肢になります。
一方で、世界中の利用者や第三者にも確認してもらう必要があるなら、デジタル署名の方が向いています。
ここで「共有鍵を安全に管理・共有できるか」を確認しているのは、MACでは同じ鍵を作成側と検証側の両方が持つためです。
共有鍵が漏えいすると、攻撃者も正しいMACを作れるようになってしまいます。
一方、デジタル署名では、署名を作る秘密鍵は署名者だけが守ります。
検証側には公開鍵を渡せばよいため、検証者が多い場面でも使いやすくなります。
6.2 API通信・内部システムではMACが向きやすい
MACが向きやすい代表例は、API通信や内部システム間の通信です。
たとえば、注文管理システムと決済システムが連携している場面を考えます。
注文管理システムが決済システムに「この注文について決済を実行してください」とリクエストを送るとします。
このとき、決済システムは次のようなことを確認したくなります。
- リクエスト内容が途中で書き換えられていないか
- 本当に注文管理システムから送られてきたものか
- 古いリクエストをコピーして再送したものではないか
このような場面では、注文管理システムと決済システムだけが共有鍵を持ち、その共有鍵を使ってMACを作る設計が考えられます。
ここでMACが向いている考え方は、検証する相手が限られているからです。
注文管理システムと決済システムのように、関係者が明確で、共有鍵を安全に配布・管理できるなら、MACは分かりやすい選択肢になります。
HMACは、暗号学的ハッシュ関数と共有秘密鍵を組み合わせるメッセージ認証の仕組みとして、NIST FIPS 198-1で説明されています。
参考: NIST FIPS 198-1 - The Keyed-Hash Message Authentication Code (HMAC)
ただし、MACを付けるだけで安全になるわけではありません。
過去の正しいリクエストを攻撃者がコピーして、もう一度送ってくる可能性があります。
そのため、実際のAPI通信では、MAC対象に次のような値を含めることがあります。
| 含める情報 | 目的 |
|---|---|
| HTTPメソッド |
GET と POST の取り違えを防ぐ |
| パス | 別のAPIへの使い回しを防ぐ |
| リクエスト本文 | 本文の改ざんを検出する |
| タイムスタンプ | 古いリクエストを拒否しやすくする |
| nonce | 同じリクエストの再利用を検出しやすくする |
ここでいうnonceは、一度だけ使う値のことです。
「毎回違う受付番号」を付けておき、同じ番号がもう一度来たら怪しいと判断するイメージです。
6.3 ソフトウェア配布ではデジタル署名が向きやすい
一方、デジタル署名が向きやすい代表例は、ソフトウェア配布です。
たとえば、公式サイトからアプリをダウンロードしたとします。
このとき利用者は、次のようなことを確認したくなります。
- このファイルは本当に公式が配布したものなのか
- ダウンロード途中で改ざんされていないか
- 攻撃者が用意した偽物のファイルではないか
この場面で、利用者一人ひとりに共有鍵を配るのはあまり現実的ではありません。
もし全利用者に同じ共有鍵を配ってしまうと、その鍵を知っている誰でも正しいMACを作れてしまいます。
そこで、デジタル署名が使いやすくなります。
配布元は秘密鍵でファイルやメタデータに署名します。
利用者は公開鍵や証明書を使って、その署名を検証します。
このように、検証者が多い場面では、公開鍵で検証できるデジタル署名が自然です。
NIST FIPS 186-5では、デジタル署名はデータの不正な変更の検出や署名者の認証に使われ、署名されたデータの受信者が第三者に対する証拠として利用できると説明されています。
参考: NIST FIPS 186-5 - Digital Signature Standard
💡 豆知識
ソフトウェアの署名は、「このアプリは絶対に安全です」と保証するものではありません。ここで確認できる範囲は、あくまで署名と対象データに関する部分です。
主に確認できるのは、「この署名に対応する秘密鍵で署名されたこと」と「署名後に対象データが変わっていないこと」です。
署名者そのものが信頼できるか、署名鍵が漏えいしていないか、アプリの中身に脆弱性がないかは、別の観点として考える必要になります。
6.4 Webサイトの証明書ではデジタル署名が重要になる
ブラウザでHTTPSサイトを開くときにも、デジタル署名は大切です。
HTTPSでは、通信内容を暗号化するだけでなく、「接続先のサーバーが本当にそのドメインのサーバーなのか」を確認する必要になります。
ここで使われるのが、証明書と認証局の仕組みです。
かなり単純化すると、次のような流れになります。
証明書は、公開鍵とWebサイトの情報を結びつけるためのデータです。
ただし、誰でも勝手に「これは本物の証明書です」と言えてしまうと意味がありません。
そこで、信頼された認証局が証明書に署名します。
ブラウザは、その署名や証明書のチェーンを確認することで、接続先を信頼してよいか判断します。
RFC 5280では、X.509証明書や証明書失効リストのプロファイルが定義されており、証明書や失効リストが署名によって扱われることが説明されています。
参考: RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and CRL Profile
ここでも、共有鍵を使うMACではなく、公開鍵で検証できるデジタル署名が重要になります。
なぜなら、ブラウザや利用者は世界中に存在し、あらかじめ全員で共有鍵を持つことは現実的ではないからです。
6.5 ブロックチェーンではデジタル署名が中心になる
ブロックチェーンでも、デジタル署名は重要な役割を持ちます。
たとえば、ある利用者が送金トランザクションを作る場合を考えます。
利用者は、自分の秘密鍵でトランザクションに署名します。
ネットワーク上の参加者は、対応する公開鍵やアドレス情報を使って、その署名を検証します。
この場面でも、MACではなくデジタル署名が向いています。
考え方は、検証する人が限定された相手ではないからです。
ブロックチェーンでは、多数の参加者が「この取引は秘密鍵の持ち主によって作られたものか」を検証します。
もしMACを使うなら、検証者全員が共有鍵を持つ必要になります。
しかし、それでは共有鍵を持つ誰でも正しいMACを作れてしまうため、「誰が作った取引なのか」を確認する仕組みとしては向きません。
デジタル署名では、秘密鍵は本人だけが持ち、検証は公開鍵側で行えます。
この性質が、多数の参加者が検証するブロックチェーンの仕組みと相性がよいです。
6.6 JWTやJWSでは名前に注意する
APIやログイン周りを学んでいると、JWTやJWSという言葉を見かけることがあります。
ここで少し注意したいのが、JWSという名前に Signature と入っていても、公開鍵暗号のデジタル署名だけを意味するとは限らない という点です。
RFC 7515では、JWSはJSONベースのデータ構造を使って、デジタル署名またはMACでコンテンツを保護する仕組みとして説明されています。
参考: RFC 7515 - JSON Web Signature (JWS)
つまり、JWSでは次のような両方のケースがあり得ます。
| 例 | 使う鍵 | 見方 |
|---|---|---|
| HMAC系のアルゴリズム | 共有鍵 | MACとして考える |
| RSA・ECDSA・EdDSA系のアルゴリズム | 秘密鍵・公開鍵 | デジタル署名として考える |
初学者の段階では、名前だけで判断せず、どのアルゴリズムを使っているか と 鍵の持ち方がどうなっているか を確認するのが大切です。
💡 豆知識
「署名」という言葉は、文脈によって少し広く使われることがあります。
たとえば、APIの説明で「リクエストに署名する」と書かれていても、実際にはHMACのようなMACを指している場合があります。
暗号技術として正確に整理したいときは、「共有鍵を使っているのか」「秘密鍵・公開鍵を使っているのか」を見ると判断しやすくなります。
6.7 使い分けを身近な例でまとめる
ここまでの内容を、身近な例で整理すると次のようになります。
| 場面 | 向いている技術 | 考え方 |
|---|---|---|
| 自社サービス内のサーバー間API通信 | MAC | 検証者が限られており、共有鍵を管理しやすい |
| Webhookの送信元確認 | MAC | サービス提供者と受信者の間で共有鍵を使える場合がある |
| 公式ソフトウェアの配布 | デジタル署名 | 多数の利用者が公開鍵で検証できる |
| HTTPSの証明書検証 | デジタル署名 | 認証局による署名をブラウザが検証する |
| ブロックチェーンの送金 | デジタル署名 | 多数の参加者が公開鍵ベースで検証する |
| 電子契約や重要文書の確認 | デジタル署名 | 第三者への証明が必要になる場合がある |
この表は、あくまで学習用の整理です。
実際のシステムでは、既存プロトコル、ライブラリ、規格、運用体制、法的要件なども含めて判断する必要になります。
ただ、最初の判断としては、次のように考えると分かりやすいです。
- 関係者だけで確認できればよいなら、MACが候補になる
- 多くの人や第三者に検証してもらいたいなら、デジタル署名が候補になる
6.8 パフォーマンスだけで選ばない
MACは、一般にデジタル署名よりも処理が軽い場面が多いです。
そのため、内部通信や高頻度なAPIリクエストでは、MACが使いやすいことがあります。
ただし、「MACの方が軽いから全部MACでよい」と考えるのは危険です。
大切なのは、性能だけでなく、確認したい性質に合っているかです。
| 観点 | 確認したいこと |
|---|---|
| 性能 | 大量のリクエストを処理できるか |
| 鍵管理 | 共有鍵や秘密鍵を安全に管理できるか |
| 検証者 | 誰が検証するのか |
| 証明性 | 第三者に説明する必要があるか |
| 運用 | 鍵の更新・失効・ローテーションができるか |
たとえば、世界中に配布するソフトウェアをMACだけで検証しようとすると、共有鍵を広く配る必要が出てきます。
しかし、共有鍵を広く配るほど、その鍵は秘密ではなくなっていきます。
このような場面では、多少コストが高くても、公開鍵で検証できるデジタル署名の方が設計として自然です。
逆に、内部API通信のように、検証者が限られていて共有鍵を安全に管理できるなら、MACの方がシンプルに使える場合があります。
6.9 この章のまとめ
この章では、MACとデジタル署名の使い分けを整理しました。
MACは、共有鍵を知っている相手同士で確認する仕組みです。
そのため、API通信や内部システムのように、検証者が限られている場面に向いています。
デジタル署名は、秘密鍵で署名を作り、公開鍵で検証する仕組みです。
そのため、ソフトウェア配布、証明書、ブロックチェーンのように、多くの人や第三者が検証する場面に向いています。
大切なのは、MACとデジタル署名を「どちらが強いか」で比べるのではなく、どのような確認をしたいのか から選ぶことです。
次の章では、MACとデジタル署名を学ぶときに混同しやすいポイントを整理していきます。
7. よくある誤解
前の章では、MACとデジタル署名の使い分けを見ました。
ここまでで、MACとデジタル署名はどちらも「メッセージの正しさを確認する技術」だという点が見えてきました。
ただし、このあたりの技術は言葉が似ているため、実装や設計の場面で誤解しやすい部分があります。
この章では、初学者が特につまずきやすいポイントを整理していきます。
細かいアルゴリズムよりも、まずは 「何ができて、何ができないのか」 をはっきり分けて理解することを目指します。
7.1 誤解1:署名すれば中身も隠れる
最初に注意したいのは、MACやデジタル署名は暗号化ではない という点です。
MACやデジタル署名を付けると、メッセージが途中で書き換えられていないかを確認できます。
しかし、メッセージの中身を読めないようにするわけではありません。
たとえば、次のような注文データを考えます。
{
"user_id": "user_123",
"item": "security_book",
"price": 3000
}
このデータにMACやデジタル署名を付けると、受信側は「この注文データが途中で変わっていないか」を確認できます。
しかし、注文データそのものが暗号化されていなければ、内容は読めます。
| 技術 | 主な役割 | 中身を隠すか |
|---|---|---|
| 暗号化 | 内容を第三者に読まれにくくする | 隠す |
| MAC | 共有鍵を持つ相手から来たか、改ざんされていないかを確認する | 隠さない |
| デジタル署名 | 秘密鍵の持ち主が署名したか、改ざんされていないかを確認する | 隠さない |
NIST FIPS 186-5では、デジタル署名はデータの不正な変更を検出し、署名者の身元を認証するために使われると説明されています。
つまり、デジタル署名の中心的な役割は「隠すこと」ではなく「確認すること」です。
参考: NIST FIPS 186-5 - Digital Signature Standard
💡 豆知識
「署名」という言葉から、封筒に封をするようなイメージを持つかもしれません。
しかし、暗号技術としての署名は、封筒というより「改ざんされたら分かる確認印」に近いです。
中身を隠したい場合は、署名とは別に暗号化を考える必要になります。
7.2 誤解2:MACがあれば第三者にも証明できる
MACは、共有鍵を使ってメッセージの正しさを確認する仕組みです。
ここでいう共有鍵は、送信者と受信者の両方が知っている秘密です。
この性質は、API通信のように当事者が限られている場面では便利です。
しかし、第三者への証明という意味では弱点になります。
たとえば、AさんとBさんだけが同じ共有鍵を持っているとします。
あるメッセージに正しいMACが付いていた場合、Bさんは「この共有鍵を知っている誰かが作った」と確認できます。
しかし、第三者のCさんから見ると、次のどちらなのか判断しにくくなります。
- AさんがMACを作った
- Bさんが自分でMACを作った
どちらも同じ共有鍵を持っているためです。
JWSの仕様であるRFC 7515でも、JWSはデジタル署名またはMACによってコンテンツを保護する仕組みとして定義されています。
その一方で、MACとデジタル署名では第三者への証明性が異なるため、どちらを使っているかを意識する必要になります。
参考: RFC 7515 - JSON Web Signature (JWS)
第三者にも「この人が作った」と示したい場合は、秘密鍵で署名し、公開鍵で検証できるデジタル署名の方が向いています。
7.3 誤解3:デジタル署名を検証できれば、すべて信頼できる
デジタル署名の検証に成功すると、「署名対象のデータが署名後に変わっていないこと」や「対応する秘密鍵で署名されたこと」を確認できます。
ただし、それだけで 内容そのものが安全 だと決まるわけではありません。
たとえば、ソフトウェアにデジタル署名が付いていたとします。
署名を検証できれば、少なくとも署名後にファイルが変わっていないことを確認する材料になります。
しかし、次のような問題は別に考える必要になります。
| 確認したいこと | 署名だけで分かるか |
|---|---|
| ファイルが署名後に改ざんされていないか | 分かる |
| 対応する秘密鍵で署名されたか | 分かる |
| ソフトウェアに脆弱性がないか | 分からない |
| 署名者の運用が安全か | 署名だけでは分からない |
| 秘密鍵が漏えいしていないか | 署名だけでは分からない |
つまり、署名は重要な確認材料ですが、「署名があるから安全性まで全面的に確認できる」と考えるのは危険です。
これは、現実の印鑑にも少し似ています。
印鑑が本物でも、契約内容そのものが妥当かどうかは別に確認する必要になります。
デジタル署名も同じで、署名の検証と、内容の安全性・妥当性の確認は分けて考える必要になります。
7.4 誤解4:公開鍵があれば、すぐ本人確認できる
デジタル署名では、公開鍵を使って署名を検証します。
そのため、「公開鍵があれば本人確認できる」と考えたくなります。
しかし、ここには大切な前提があります。
それは、その公開鍵が本当に本人のものだと信頼できること です。
もし攻撃者が用意した公開鍵を、正規の公開鍵だと勘違いしてしまったらどうなるでしょうか。
その場合、攻撃者の秘密鍵で作られた署名を、攻撃者の公開鍵で正しく検証できてしまいます。
この問題を避けるために、証明書やPKIの仕組みが使われます。
Webサイトの証明書では、公開鍵とドメイン名などの情報を結びつけ、その証明書に認証局が署名します。
RFC 5280では、X.509証明書や証明書失効リストのプロファイルが定義されており、証明書や失効リストが署名によって扱われることが説明されています。
参考: RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List Profile
デジタル署名を正しく使うには、署名アルゴリズムだけでなく、公開鍵をどう信頼するかも大切です。
7.5 誤解5:MACや署名があればリプレイ攻撃も防げる
MACやデジタル署名は、メッセージが改ざんされていないかを確認できます。
しかし、過去の正しいメッセージをもう一度送る攻撃 を自動的に防ぐわけではありません。
このような攻撃を、リプレイ攻撃と呼びます。
たとえば、次のようなAPIリクエストを考えます。
POST /payment
amount=1000&to=shop_A
このリクエストに正しいMACが付いていたとしても、攻撃者が通信内容をコピーして、後からもう一度送った場合を考えます。
メッセージ自体は過去に本物として作られたものなので、MACの検証だけでは通ってしまう可能性があります。
そのため、実際の設計では、MACや署名の対象に次のような情報を含めることがあります。
| 対策に使う値 | 目的 |
|---|---|
| タイムスタンプ | 古すぎるリクエストを拒否する |
| nonce | 同じ値の再利用を検出する |
| 一意なリクエストID | 過去に処理済みか確認する |
| 有効期限 | 一定時間を過ぎたメッセージを無効にする |
RFC 7515でも、JWSやJWEを使うアプリケーションがリプレイ攻撃を防ぐ方法として、一意なメッセージ識別子を完全性保護された内容に含め、受信者が過去に受信または処理していないことを確認する考え方が説明されています。
参考: RFC 7515 - JSON Web Signature (JWS), Replay Protection
💡 豆知識
MACや署名は「このメッセージは本物っぽいか」を見る仕組みです。
それに対して、リプレイ攻撃対策は「その本物のメッセージが、今使われてよいものか」を見る仕組みです。
似ているようで、確認しているポイントが少し違います。
7.6 誤解6:署名付きJWTの中身は見えない
JWTを学んでいると、「署名付きJWT」という言葉が出てきます。
ここで誤解しやすいのが、署名されていることと、暗号化されていることは別 という点です。
RFC 7519では、JWTのクレームはJWSのペイロードとしてデジタル署名またはMACで完全性保護される場合もあれば、JWEの平文として暗号化される場合もあると説明されています。
参考: RFC 7519 - JSON Web Token (JWT)
つまり、署名付きJWTは改ざん検知には使えますが、暗号化されていない場合、中身はBase64URLで表現されているだけです。
秘密情報を入れてよい、という意味ではありません。
次のコードは、JWT風の文字列からヘッダーとペイロードをBase64URLデコードするだけの学習用例です。
ここでは署名検証はしていません。実際のシステムでは、デコード結果を信用する前に署名検証や有効期限確認などが必要です。
import base64
import json
# 学習用のJWT風文字列です。
# 実際のアクセストークンや秘密情報をコードに貼り付けないでください。
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsInJvbGUiOiJ1c2VyIn0.dummy_signature"
# JWTは「ヘッダー.ペイロード.署名」の3部分に分かれることが多いです。
header_b64, payload_b64, signature_b64 = token.split(".")
def base64url_decode(data: str) -> bytes:
"""Base64URL文字列をデコードするため、必要なパディングを補ってから復号する。"""
padding = "=" * (-len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
# ヘッダーとペイロードをデコードします。
# これは「中身を読む」だけであり、「正しいJWTだと検証する」処理ではありません。
header = json.loads(base64url_decode(header_b64))
payload = json.loads(base64url_decode(payload_b64))
print(header)
print(payload)
出力例は次のようになります。
{'alg': 'HS256', 'typ': 'JWT'}
{'sub': 'user_123', 'role': 'user'}
このように、署名付きJWTでも中身が読める場合があります。
JWTにパスワード、秘密鍵、APIキー、個人情報などを安易に入れてはいけません。
7.7 誤解7:ライブラリを使っていれば安全
暗号技術では、自作アルゴリズムを避け、標準的なライブラリを使うことが大切です。
ただし、ライブラリを使っていれば自動的に安全、というわけではありません。
たとえば、次のようなミスがあると、強いアルゴリズムを使っていても危険になります。
| よくあるミス | なぜ危険か |
|---|---|
| 署名検証を省略する | 改ざんされたデータを信じてしまう |
| 検証前のJWTペイロードを信用する | 攻撃者が作った値を使ってしまう可能性がある |
| 共有鍵や秘密鍵をソースコードに直接書く | GitHubなどから漏えいする可能性がある |
| 署名対象に重要な項目を含めない | 重要な値だけ差し替えられる可能性がある |
| アルゴリズム指定を適切に検証しない | 想定外の方式で検証してしまう可能性がある |
| 鍵の更新・失効手順がない | 漏えい時に被害を止めにくい |
NIST SP 800-57 Part 1 Rev.5では、暗号鍵材料の管理、鍵の保護、鍵管理機能、暗号利用時に考えるべき課題について一般的なガイダンスが示されています。
参考: NIST SP 800-57 Part 1 Rev.5 - Recommendation for Key Management
暗号技術は、アルゴリズムだけで完結しません。
鍵管理、検証処理、エラー処理、ログ、運用ルールまで含めて安全性を考える必要になります。
7.8 この章のまとめ
この章では、MACとデジタル署名を学ぶときに混同しやすいポイントを整理しました。
| 誤解 | 正しくは |
|---|---|
| 署名すれば中身も隠れる | MACや署名は主に改ざん検知・認証のための技術であり、暗号化とは別 |
| MACがあれば第三者にも証明できる | MACは共有鍵を使うため、第三者への証明には向きにくい |
| 署名検証できればすべて安全 | 署名は重要な確認材料だが、内容の安全性や鍵の信頼性は別に確認する |
| 公開鍵があれば本人確認できる | その公開鍵が本当に本人のものかを確認する仕組みが必要 |
| MACや署名でリプレイ攻撃も防げる | タイムスタンプ、nonce、一意なIDなどの対策が別途必要 |
| 署名付きJWTの中身は見えない | 暗号化されていないJWTの中身は読める場合がある |
| ライブラリを使えば安全 | 鍵管理、署名対象、検証処理、運用を誤ると危険 |
MACとデジタル署名は、どちらも便利な技術です。
しかし、「何を守れるのか」と同じくらい、「何は守れないのか」を理解することが大切です。
次の章では、ここまでの内容を踏まえて、実装・運用上で特に注意すべき点を整理していきます。
8. 実装・運用上の注意点
前の章では、MACとデジタル署名で誤解しやすいポイントを整理しました。
ここまでで、「MACやデジタル署名は、メッセージの正しさを確認するための技術だ」という点を整理してきました。
ただし、実際に使うときは、アルゴリズムだけを知っていてもそれだけでは足りません。
HMACやEd25519のような標準的な方式を使っていても、鍵の扱い、署名対象、検証処理、リプレイ攻撃対策を間違えると、安全性は大きく下がります。
この章では、実装や運用で特に気をつけたい点を、具体例とコードを交えながら整理していきます。
この章のコードは、理解を助けるための学習用サンプルです。
実システムでは、利用するフレームワーク、クラウド環境、鍵管理サービス、監査要件に合わせて設計してください。
この章では、特に次の観点を確認していきます。
| 観点 | 確認したいこと |
|---|---|
| 鍵管理 | 共有鍵・秘密鍵を安全に保存し、漏えい時に更新できるか |
| 署名対象 | 守りたい項目がMAC・署名の対象に含まれているか |
| 検証処理 | 安全な比較関数や例外処理を使っているか |
| リプレイ対策 | タイムスタンプ、nonce、一意なIDで再送を検出できるか |
| ログ | 秘密鍵やトークンをログに出していないか |
8.1 まず「何を守りたいのか」を決める
MACやデジタル署名を使う前に、最初に決めるべきことは「何を守りたいのか」です。
たとえば、同じAPIリクエストでも、守りたいものは一つではありません。
| 守りたいこと | 必要になる対策の例 |
|---|---|
| 通信内容を盗み見られたくない | TLSなどで通信経路を保護する |
| リクエスト本文を改ざんされたくない | HMACやデジタル署名の対象に本文を含める |
| URLやHTTPメソッドを差し替えられたくない | メソッド、パス、クエリも署名対象に含める |
| 古いリクエストを再送されたくない | タイムスタンプ、nonce、一意なIDを使う |
| 第三者にも証明したい | MACではなくデジタル署名を検討する |
| 鍵漏えい時の被害を抑えたい | 鍵の用途分離、更新、失効手順を用意する |
ここで押さえておきたいのは、MACや署名を付けること自体を目的にしない ことです。
守りたい項目が署名対象に含まれていなければ、検証に成功しても、アプリケーションとしては危険な状態になることがあります。
たとえば、APIリクエストの本文だけにHMACを付けていた場合、URLのパスやクエリが署名対象に入っていなければ、別のエンドポイントへ差し替えられるリスクを考える必要になります。
そのため、実装前に「どの値が変わると困るのか」を整理しておくことが大切です。
8.2 共有鍵・秘密鍵をソースコードに直接書かない
MACでは共有鍵、デジタル署名では秘密鍵が大切です。
これらが漏えいすると、攻撃者が正しいMACを作ったり、本人になりすまして署名したりできる可能性があります。
NIST SP 800-57 Part 1 Rev.5では、暗号鍵材料の管理や保護、鍵管理機能、鍵管理上の課題について一般的なガイダンスが示されています。
参考: NIST SP 800-57 Part 1 Rev.5 - Recommendation for Key Management
また、OWASPのCryptographic Storage Cheat Sheetでも、暗号鍵をアプリケーションのソースコードにハードコードしないことが示されています。
参考: OWASP Cryptographic Storage Cheat Sheet
たとえば、次のように鍵をコードへ直接書くのは避ける必要があります。
import hmac
import hashlib
# 悪い例:
# 共有鍵をソースコードに直接書いている。
# GitHubなどに誤って公開すると、この鍵を使ってMACを作られる可能性がある。
SECRET_KEY = b"hardcoded-secret-key"
message = b"amount=1000&to=shop_a"
mac_tag = hmac.new(SECRET_KEY, message, hashlib.sha256).hexdigest()
print(mac_tag)
学習用であっても、鍵はコードと分けて扱う習慣を付けた方が安全です。
たとえば、ローカルの検証では環境変数から読み込む形にできます。
import os
import hmac
import hashlib
# 学習用の例:
# 共有鍵を環境変数から読み込む。
# 実行前に、例: export HMAC_SECRET_KEY="local-learning-secret" のように設定する。
# 実運用では、クラウドのシークレット管理サービスやKMSの利用も検討する。
secret = os.environ.get("HMAC_SECRET_KEY")
if secret is None:
raise RuntimeError("HMAC_SECRET_KEY が設定されていません。実行前に環境変数を設定してください。")
# 環境変数は文字列として読み込まれるため、MAC計算に使う前にbytesへ変換する。
secret_key = secret.encode("utf-8")
message = b"amount=1000&to=shop_a"
mac_tag = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
print(mac_tag)
ただし、環境変数に入れればそれだけで安全、というわけではありません。
OWASP Secrets Management Cheat Sheetでは、シークレットの保管、提供、監査、ローテーション、アクセス制御などを含めた管理が重要だと説明されています。
参考: OWASP Secrets Management Cheat Sheet
💡 豆知識
.envファイルはローカル開発では便利ですが、誤ってGitにコミットすると秘密情報が漏えいします。
.gitignoreに入れるだけでなく、リポジトリに秘密情報が混入していないかを確認する仕組みも大切です。
8.3 署名対象をはっきり決める
MACやデジタル署名で特に重要なのが、何をMAC・署名の対象にするか です。
たとえば、APIリクエストで次のような情報があるとします。
- HTTPメソッド:
POST - パス:
/payments - リクエスト本文:
{"amount":1000,"to":"shop_a"} - タイムスタンプ:
2026-06-26T12:00:00Z - nonce:
9f1c...
このとき、本文だけをMAC対象にすると、パスやメソッドの差し替えを検知できない可能性があります。
そのため、重要な項目をまとめて「正規化した文字列」を作り、その全体にMACや署名を付ける設計がよく使われます。
import hashlib
import hmac
import json
secret_key = b"example-shared-secret-for-learning"
method = "POST"
path = "/payments"
timestamp = "2026-06-26T12:00:00Z"
nonce = "request-0001"
body = {
"amount": 1000,
"to": "shop_a",
}
# JSONは空白やキー順の違いで文字列表現が変わることがある。
# 学習用として、キー順と区切り文字を固定して正規化する。
canonical_body = json.dumps(
body,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
# 本文そのものではなく、本文のハッシュ値を署名対象に入れる。
# 大きな本文を扱うときも、固定長の値として扱いやすくなる。
body_hash = hashlib.sha256(canonical_body).hexdigest()
# 署名対象にしたい項目を、順序を決めて連結する。
# 実運用では、仕様としてこの形式を送信側・受信側で厳密に合わせる必要がある。
signing_string = "\n".join([
method,
path,
timestamp,
nonce,
body_hash,
]).encode("utf-8")
# 正規化した文字列全体に対してHMACを計算する。
mac_tag = hmac.new(secret_key, signing_string, hashlib.sha256).hexdigest()
print(signing_string.decode("utf-8"))
print(mac_tag)
この例のポイントは、MACを計算する前に、何を守る対象にするかを明確にしている ことです。
特にAPI通信では、本文だけでなく、HTTPメソッド、パス、クエリ、タイムスタンプ、nonce、本文のハッシュなどを含めるかどうかを慎重に設計する必要になります。
この設計が曖昧だと、送信側と受信側で検証結果が合わなかったり、重要な項目だけを差し替えられたりする可能性があります。
💡 豆知識
「署名対象をどう文字列化するか」は、地味ですがとても大切です。
JSONの空白、キー順、改行コード、URLエンコードの扱いが少し違うだけで、送信側と受信側のMAC値が一致しないことがあります。
8.4 MACの比較には == ではなく compare_digest() を使う
HMACを検証するときは、自分で計算したMAC値と、相手から送られてきたMAC値を比較します。
このとき、単純に == で比較するよりも、タイミング攻撃を意識した比較関数を使う方が安全です。
Python公式ドキュメントでは、検証処理で外部から与えられたダイジェストと比較するとき、== ではなく hmac.compare_digest() を使うことが推奨されています。
参考: Python Documentation - hmac
import hmac
import hashlib
secret_key = b"example-shared-secret-for-learning"
message = b"amount=1000&to=shop_a"
# 送信側が作ったと仮定するMAC値
received_mac = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# 受信側でも同じ鍵とメッセージからMAC値を計算する。
expected_mac = hmac.new(secret_key, message, hashlib.sha256).hexdigest()
# 悪い例:
# 動作はするが、検証用の比較としては推奨されない。
print(received_mac == expected_mac)
# 良い例:
# タイミング攻撃への耐性を意識した比較関数を使う。
is_valid = hmac.compare_digest(received_mac, expected_mac)
print(is_valid)
この例では結果は同じ True になります。
しかし、セキュリティ用途の比較では「同じ結果になるか」だけでなく、「比較処理から余計な情報が漏れないか」も考える必要になります。
8.5 リプレイ攻撃対策を別途入れる
MACやデジタル署名は、メッセージが改ざんされていないことを確認するために役立ちます。
しかし、それだけでは 過去の正しいメッセージをもう一度送る攻撃 を防げません。
このような攻撃は、リプレイ攻撃と呼ばれます。
たとえば、「1,000円を支払う」という正しいリクエストにMACが付いていたとします。
攻撃者がその通信をコピーして後から再送できる場合、MAC検証には成功してしまう可能性があります。
なぜなら、メッセージ自体は改ざんされていないからです。
RFC 7515では、リプレイ攻撃への対策として、一意なメッセージ識別子を完全性保護された内容に含め、受信者が過去に処理していないことを確認する方法が説明されています。
参考: RFC 7515 - JSON Web Signature (JWS)
学習用に、タイムスタンプとnonceを確認する簡単な例を示します。
from datetime import datetime, timezone, timedelta
# 学習用のnonce保存領域です。
# 実運用では、複数サーバーで共有できるDBやキャッシュ、期限付きストレージなどを検討します。
used_nonces = set()
# 許容する時刻ずれを5分に設定する。
# 実運用では、システム要件や時刻同期の状態に合わせて調整する。
ALLOWED_SKEW = timedelta(minutes=5)
def verify_freshness(timestamp_text: str, nonce: str) -> bool:
"""タイムスタンプとnonceを使って、古いリクエストや再送を簡易チェックする。"""
now = datetime.now(timezone.utc)
# ISO 8601形式の時刻文字列をdatetimeへ変換する。
# ここでは末尾のZをUTCとして扱う。
request_time = datetime.fromisoformat(timestamp_text.replace("Z", "+00:00"))
# 時刻が古すぎる、または未来すぎる場合は拒否する。
if abs(now - request_time) > ALLOWED_SKEW:
return False
# すでに使われたnonceなら、再送の可能性があるため拒否する。
if nonce in used_nonces:
return False
# 初めて見たnonceなら記録する。
used_nonces.add(nonce)
return True
# 実行時点の現在時刻を使って、検証用のタイムスタンプを作る。
# 固定日時を使うと、記事を読んだ時点によっては「古すぎるリクエスト」と判定されるためです。
current_timestamp = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
print(verify_freshness(current_timestamp, "request-0001"))
print(verify_freshness(current_timestamp, "request-0001")) # 同じnonceなのでFalseになる想定
このコードでは、1回目は現在時刻のタイムスタンプと未使用のnonceなので True になり、2回目は同じnonceを再利用しているため False になる想定です。
このコードは考え方を示すための簡易例です。
実際には、サーバーが複数台ある場合のnonce共有、保存期間、時刻同期、障害時の扱いなども考える必要になります。
MACや署名は「メッセージが正しい形で作られているか」を確認できます。
一方で、「そのメッセージが今使われてよいものか」は、タイムスタンプ、nonce、有効期限、一意なIDなどで別途確認する必要になります。
8.6 デジタル署名では公開鍵の信頼も確認する
デジタル署名では、公開鍵で署名を検証します。
しかし、ここで忘れてはいけないのが、その公開鍵は本当に署名者のものなのか という点です。
もし攻撃者が自分の公開鍵を「公式の公開鍵です」と見せてきた場合、その公開鍵で攻撃者の署名を検証できてしまいます。
署名検証そのものは成功しても、確認している相手を間違えている状態です。
この問題を解決するために、Webの世界では証明書や認証局を使うPKIが重要になります。
RFC 5280では、X.509証明書や証明書失効リストの形式が定義されており、証明書には主体と公開鍵の対応関係などが含まれます。
参考: RFC 5280 - Internet X.509 Public Key Infrastructure Certificate and CRL Profile
記事の前半では「公開鍵で検証できる」と説明しました。
ただし、実務ではそれに加えて、次のような点を確認する必要になります。
| 確認すること | なぜ重要か |
|---|---|
| 公開鍵の入手元 | 攻撃者が差し替えた公開鍵を使わないため |
| 証明書チェーン | 信頼できる認証局までつながるか確認するため |
| 証明書の有効期限 | 期限切れの証明書を使わないため |
| 失効状態 | 漏えい・不正利用された証明書を信頼しないため |
| ホスト名や主体情報 | 本当に接続先や署名者と対応しているか確認するため |
デジタル署名は「公開鍵があれば検証できる」仕組みです。
しかし、その公開鍵をどう信頼するか まで含めて考えないと、安全な設計にはなりません。
8.7 エラー処理とログにも注意する
MACやデジタル署名の検証に失敗したとき、エラー処理やログの出し方にも注意が必要です。
たとえば、検証に失敗したときに、次のような情報をそのままログへ出すのは危険な場合があります。
- 共有鍵
- 秘密鍵
- APIキー
- Authorizationヘッダー
- 実際のアクセストークン
- 個人情報を含むリクエスト本文
一方で、まったくログを残さないと、攻撃や障害を調査できません。
そのため、ログには秘密情報そのものではなく、次のような情報を残す方が扱いやすいです。
| ログに残す候補 | 目的 |
|---|---|
| リクエストID | 問題の通信を追跡するため |
| 送信元の識別子 | どのクライアントで失敗したか確認するため |
| 検証失敗の考え方分類 | MAC不一致、期限切れ、nonce再利用などを切り分けるため |
| 発生時刻 | 攻撃や障害の時系列を追うため |
| 対象エンドポイント | どのAPIで失敗したか確認するため |
たとえば、次のように、秘密情報を直接出さずに検証失敗を記録する形が考えられます。
import logging
logger = logging.getLogger(__name__)
def log_verification_failure(request_id: str, client_id: str, reason: str, path: str) -> None:
"""検証失敗時に、秘密情報を含めずに調査用ログを残す。"""
logger.warning(
"request verification failed: request_id=%s client_id=%s reason=%s path=%s",
request_id,
client_id,
reason,
path,
)
# 例: MAC不一致を検出した場合
log_verification_failure(
request_id="req-20260626-0001",
client_id="client-a",
reason="mac_mismatch",
path="/payments",
)
ログは、セキュリティ監視やインシデント対応で大切です。
ただし、秘密情報をログに出してしまうと、ログ基盤そのものが漏えい経路になる可能性があります。
8.8 鍵の用途を分ける
同じ鍵を複数の用途に使い回すと、どこか一つで漏えいしたときの影響範囲が広がります。
また、ある用途では安全な使い方でも、別の用途では危険になる可能性があります。
たとえば、次のような使い回しは避ける必要があります。
| 避けたい使い回し | 考え方 |
|---|---|
| 本番環境と開発環境で同じ鍵を使う | 開発環境から本番の鍵が漏れる可能性がある |
| 署名用の秘密鍵を別用途にも使う | 署名鍵の責務が曖昧になる |
| 複数クライアントで同じHMAC共有鍵を使う | どのクライアントから漏れたか切り分けにくい |
| 長期間同じ鍵を更新せずに使う | 漏えい時の影響期間が長くなる |
OWASP Key Management Cheat Sheetでは、鍵のライフサイクル、鍵の侵害・復旧、鍵の保管、鍵共有などを含む暗号鍵管理の観点が整理されています。
参考: OWASP Key Management Cheat Sheet
暗号鍵は、作った瞬間だけでなく、作成、配布、保存、利用、更新、失効、廃棄までを含めて管理する必要になります。
8.9 実装前に確認したいチェックリスト
最後に、MACやデジタル署名を使う前に確認したいことをまとめます。
| 確認項目 | チェック内容 |
|---|---|
| 目的 | 機密性、完全性、認証、第三者証明のどれを守りたいか |
| 技術選択 | 当事者間の確認ならMAC、第三者検証が必要ならデジタル署名を検討しているか |
| 鍵管理 | 共有鍵・秘密鍵をソースコードに直接書いていないか |
| 鍵の用途 | 環境別・用途別・クライアント別に鍵を分けているか |
| 署名対象 | メソッド、パス、本文、時刻、nonceなど、重要な項目を含めているか |
| 正規化 | JSON、URL、改行、文字コードの扱いを送信側・受信側で合わせているか |
| 比較方法 | HMAC検証で compare_digest() のような安全な比較を使っているか |
| リプレイ対策 | タイムスタンプ、nonce、一意なID、有効期限を設計しているか |
| 公開鍵の信頼 | デジタル署名で公開鍵の入手元や証明書チェーンを確認しているか |
| エラー処理 | 検証失敗時に安全に拒否し、秘密情報をログに出していないか |
| 鍵漏えい対応 | 鍵の更新・失効・再発行手順を用意しているか |
このチェックリストを見ると、MACやデジタル署名は「コードを数行書けば終わり」ではないことが分かります。
暗号技術を安全に使うには、実装だけでなく、運用や障害対応まで含めて考える必要になります。
8.10 この章のまとめ
この章では、MACやデジタル署名を実装・運用するときの注意点を整理しました。
特に重要なのは、次の点です。
- 鍵をソースコードに直接書かない
- 何をMAC・署名の対象にするかを明確にする
- HMACの比較には
compare_digest()のような安全な比較関数を使う - MACや署名だけでリプレイ攻撃を防げると考えない
- デジタル署名では、公開鍵をどう信頼するかまで考える
- 検証失敗時のログに秘密情報を出さない
- 鍵の用途分離、更新、失効まで含めて設計する
MACやデジタル署名は、正しく使えばとても強力な仕組みです。
一方で、使い方を誤ると「検証しているつもりでも、守りたいものを守れていない」状態になります。
次の章では、ここまでの内容をまとめ、MACとデジタル署名をどのように理解すればよいかを振り返ります。
9. まとめ
ここまで、MACとデジタル署名の違いを、身近な例から順番に整理してきました。
最初は、スマホ決済、API通信、ソフトウェア更新のような場面から考えました。
どの場面でも大切だったのは、単にデータを送ることではありません。
- 内容が途中で書き換えられていないか
- 信頼できる相手から届いたものか
- 必要に応じて第三者にも確認してもらえるか
このような確認を支える代表的な仕組みが、MACとデジタル署名でした。
9.1 MACは「共有鍵を知っている相手との確認」
MACは、送信者と受信者が同じ共有鍵を持ち、その鍵を使ってメッセージの正しさを確認する仕組みです。
たとえるなら、関係者だけが知っている合言葉を使って、
「これは仲間内で正しく作られたメッセージだ」と確認するようなイメージです。
代表例であるHMACについて、NIST FIPS 198-1では、暗号学的ハッシュ関数と共有秘密鍵を組み合わせて使うメッセージ認証の仕組みとして説明されています。
参考: NIST FIPS 198-1 - The Keyed-Hash Message Authentication Code (HMAC)
MACは、次のような場面に向いています。
| 使いやすい場面 | 考え方 |
|---|---|
| API通信 | サーバーとクライアントが共有鍵を持てるため |
| サーバー間通信 | 関係するシステムが限定されるため |
| 内部システムのメッセージ検証 | 第三者に証明するより、当事者間で確認できればよい場合が多いため |
一方で、MACは第三者への証明にはあまり向きません。
なぜなら、MACを作る側も検証する側も同じ共有鍵を持っているため、外部の第三者から見ると「誰がそのMACを作ったのか」を切り分けにくいからです。
9.2 デジタル署名は「第三者にも確認してもらえる署名」
デジタル署名は、秘密鍵で署名を作り、公開鍵で検証する仕組みです。
たとえるなら、本人だけが押せる印鑑と、誰でも確認できる印鑑証明のような関係に近いです。
ただし、実際のデジタル署名は、手書きサインの画像や印鑑の写真ではありません。
秘密鍵を使って作られ、公開鍵で検証できるデータです。
NIST FIPS 186-5では、デジタル署名はデータの不正な変更を検出し、署名者の身元を認証するために使われると説明されています。
さらに、署名されたデータの受信者は、その署名が主張された署名者によって生成されたことを第三者に示す証拠として利用できます。
参考: NIST FIPS 186-5 - Digital Signature Standard
デジタル署名は、次のような場面に向いています。
| 使いやすい場面 | 考え方 |
|---|---|
| ソフトウェア配布 | 多くの利用者が、配布元の正しさを確認したいため |
| TLS証明書 | ブラウザなどが、接続先や証明書の信頼性を確認するため |
| 電子契約 | 後から署名の事実を確認したい場面があるため |
| ブロックチェーン | 多数のノードが、取引の署名を検証するため |
デジタル署名では、公開鍵で検証できる点が大きな特徴です。
ただし、その公開鍵が本当に署名者のものかを確認する仕組みも必要になります。
この点は、証明書やPKIを学ぶときに重要になります。
9.3 もう一度、違いを整理する
最後に、MACとデジタル署名の違いをもう一度まとめます。
| 比較項目 | MAC | デジタル署名 |
|---|---|---|
| 鍵の考え方 | 共有鍵 | 秘密鍵と公開鍵 |
| 作成できる人 | 共有鍵を持つ人 | 秘密鍵を持つ人 |
| 検証できる人 | 共有鍵を持つ人 | 公開鍵を持つ人 |
| 向いていること | 当事者間のメッセージ確認 | 第三者にも確認してもらえる署名 |
| 向きにくいこと | 第三者への証明 | 公開鍵の信頼確認や鍵管理が必要 |
| 主な利用例 | API通信、内部通信 | 証明書、ソフトウェア署名、ブロックチェーン |
この表だけを見ると、デジタル署名の方が便利に見えるかもしれません。
しかし、実際には「どちらが上位互換か」ではなく、目的によって使い分けることが大切です。
関係者だけで高速に検証できればよいなら、MACが自然な選択になることがあります。
一方、第三者や多数の利用者にも確認してもらう必要があるなら、デジタル署名が向いています。
9.4 ただし、MACや署名だけで安全が完成するわけではない
この記事で繰り返し確認したように、MACやデジタル署名はとても重要な技術です。
しかし、それだけで安全なシステムが完成するわけではありません。
特に、次の点は忘れないようにしたいです。
| 注意点 | なぜ重要か |
|---|---|
| MACや署名は暗号化ではない | 中身を隠したい場合は別途暗号化が必要になるため |
| 署名対象を明確にする | 重要な項目が検証対象から漏れると危険なため |
| リプレイ攻撃対策を入れる | 正しい過去のメッセージを再送される可能性があるため |
| 鍵を安全に管理する | 共有鍵や秘密鍵が漏れると前提が崩れるため |
| 公開鍵の信頼を確認する | 攻撃者の公開鍵を信じると、署名検証の意味がなくなるため |
| ログに秘密情報を出さない | ログ基盤から鍵やトークンが漏れる可能性があるため |
暗号技術では、アルゴリズムだけでなく、使い方と運用が大切です。
強い方式を使っていても、鍵をソースコードに直接書いたり、署名対象を誤ったり、リプレイ攻撃対策を入れ忘れたりすると、守りたいものを守れない可能性があります。
NIST SP 800-57 Part 1 Rev.5では、暗号鍵の生成、保管、利用、更新、失効などを含む鍵管理の考え方が整理されています。
参考: NIST SP 800-57 Part 1 Rev.5 - Recommendation for Key Management
9.5 学習の次のステップ
この記事では、MACとデジタル署名の違いを中心に整理しました。
ただし、暗号技術全体で見ると、まだ多くの関連テーマがあります。
今後、次のようなテーマを学ぶと、理解がさらに深まります。
| 次に学ぶテーマ | 本記事とのつながり |
|---|---|
| ハッシュ関数 | MACや署名の前提になる「データの指紋」を理解できる |
| HMACの詳しい仕組み | MACがどのように共有鍵とハッシュ関数を組み合わせるか分かる |
| JWT / JWS | APIやログインでMAC・署名がどう使われるか分かる |
| TLS証明書とPKI | 公開鍵をどう信頼するか理解できる |
| ブロックチェーンの署名 | 取引の正しさを多数のノードがどう検証するか分かる |
| 鍵管理 | 暗号技術を安全に運用するための前提を学べる |
| 耐量子デジタル署名 | 将来の暗号移行を考える入口になる |
JWSについては、RFC 7515で、JSONベースのデータ構造を使って、デジタル署名またはMACによってコンテンツを保護する仕組みとして定義されています。
参考: RFC 7515 - JSON Web Signature (JWS)
💡 豆知識
JWSは “JSON Web Signature” という名前ですが、仕様上はデジタル署名だけでなくMACも見ていきます。
そのため、名前だけで「これは公開鍵署名だ」と判断せず、実際にどのアルゴリズムと鍵の持ち方を使っているかを見ることが大切です。
9.6 最後に
MACとデジタル署名は、どちらも「メッセージの正しさを確認する」ための大切な技術です。
ただし、役割は同じではありません。
MACは、共有鍵を知っている相手同士で確認する仕組みです。
デジタル署名は、秘密鍵で署名し、公開鍵で検証することで、第三者にも確認してもらいやすい仕組みです。
この違いを理解しておくと、API通信、JWT、ソフトウェア署名、証明書、ブロックチェーンなどを学ぶときに、暗号技術の役割がかなり見えやすくなります。
暗号技術は、難しい数式やアルゴリズムから入ると少し遠く感じるかもしれません。
しかし、身近なサービスの裏側で「何を確認しているのか」から考えると、少しずつ整理しやすくなります。
この記事が、MACとデジタル署名を学ぶための入口になればうれしいです。