ハッシュ、改ざん検知の符号、署名

レッスン 1-3

長さ拡張攻撃を知ると、HMAC がなぜ要るのかが分かります。

目安 60 分 トラック 1 基盤の 3 本目

読み終えたらただのハッシュで改ざん検知の符号を作ってはいけない理由を説明できる

この頁の流れ

  1. 1なぜ通るのか
  2. 2脆いコードと直したコード
  3. 3ログでの見え方と検知
  4. 4直し方と確かめ方
  5. 5関わる事例
  6. 6確認クイズ
  7. 7説明課題
  8. 8一次資料
  9. 9次へ

秘密の値とデータをつないでハッシュを取るだけの印は、秘密を知らない相手にも書き足しを許します。HMAC が 2 回に分けてハッシュを取る理由、印の比べ方、JWT で受け入れる方式をこちらで決める理由、署名の鍵の預け方、記録での見え方と、直ったことを試験で確かめる方法までを扱います。

出典を確かめた日 2026年10月27日

なぜ通るのか

取引先から届く通知 (Webhook、出来事を知らせる外からの呼び出し) に、改ざんされていない印を付けてもらうことにしたとします。取り決めは「共有した秘密の値と本文をつなぎ、SHA-256 のハッシュを取って送る」です。受け取った側も同じ計算をして、合えば本物とみなします。

秘密を知らない攻め手には、正しい印は作れないように見えます。ところが、この取り決めでは、攻め手は本文の後ろに好きなデータを書き足し、それに合う印を作れます。秘密の値を知らないままです。

ハッシュと署名の仕組みそのもの (指紋を作る、秘密鍵で付けて公開鍵で確かめる) は、コンピュータサイエンスの回「公開鍵暗号方式」で扱います。このレッスンは、それを改ざん検知に使ったときに、どこで破れるかに絞ります。

長さ拡張攻撃

SHA-256 は、データを決まった大きさの塊に分け、塊ごとに内側の状態を更新していき、最後の状態をそのまま結果として出します。結果がそのまま内側の状態なので、結果を知っている人は、そこから計算を続けられます。

そのため、「秘密の値 + 本文」のハッシュと、秘密の値の長さを知っていれば、「秘密の値 + 本文 + 埋め草 + 書き足し」のハッシュを、秘密の値を知らずに計算できます。これを長さ拡張攻撃と呼びます。通知の本文の後ろに「金額をもう一度」のような項目を足し、受け手が後ろの値を採る作りなら、偽の通知が本物として通ります。

HMAC は何を変えたか

HMAC は、RFC 2104 が定めた、ハッシュと秘密の鍵で改ざん検知の符号を作る方式です。計算は 2 段です。

段何をするか
内側鍵に決まった値 (ipad) を混ぜた物と本文をつなぎ、ハッシュを取る
外側鍵に別の値 (opad) を混ぜた物と、内側の結果をつなぎ、もう一度ハッシュを取る

外に出るのは外側の結果だけで、内側の状態は外から見えません。だから、出てきた印から計算を続けても、鍵を混ぜた外側の段をやり直せず、書き足しに合う印は作れません。「ハッシュに秘密を混ぜれば印になる」という素朴な作りが通らない理由を、構造で塞いだのが HMAC です。

改ざん検知の符号と署名の違い

HMAC は、送る側と受ける側が同じ鍵を持ちます。受ける側も印を作れるので、第三者に「この通知は取引先が作った」と示すことはできません。署名は、秘密鍵で付けて公開鍵で確かめるので、確かめる側には印を作れません。

方式鍵向く所
HMAC両者が同じ秘密の鍵を持つ2 者の間の通知、API の呼び出しの印
署名付ける側だけが秘密鍵を持つ多くの相手に配る物 (更新の配布、ログインのトークン)

署名で守るなら、守りの重さは秘密鍵 1 本にかかります。鍵が盗まれれば、誰でも本物の印を付けられます。そして署名が示すのは「誰が付けたか」であって、「中身が安全か」ではありません。

脆いコードと直したコード

ラボの中の小さな関数で比べます。1 組目は Python に付いている hashlib と hmac だけで動きます。2 組目は JWT (署名付きの JSON のトークン) を読む PyJWT という部品を使う例です。どちらも外とは通信しません。

ハッシュに秘密をつなぐか、HMAC を使うか

脆いコード 秘密をつないでハッシュし、== で比べる

Python

def verify(secret, body, sig):
    mac = hashlib.sha256(
        secret + body).hexdigest()
    return mac == sig
直したコード HMAC を作り、一定の時間で比べる

Python

def verify(secret, body, sig):
    mac = hmac.new(
        secret, body,
        hashlib.sha256).hexdigest()
    return hmac.compare_digest(mac, sig)

直した方は 2 つ変えています。1 つは印の作り方を HMAC にしたことです。もう 1 つは比べ方です。== は違う文字が見つかった所で比べるのをやめるので、どこまで合っていたかが応答の時間に出るおそれがあります。Python の説明書は、外から届いた印と比べるときは compare_digest を使い、時間の差から推し量られるのを減らすよう勧めています。

印の方式をトークンから読むか、こちらで決めるか

脆いコード 方式をトークンの頭から読む

Python

def read_token(token, key):
    alg = jwt.get_unverified_header(
        token)["alg"]
    return jwt.decode(
        token, key, algorithms=[alg])
直したコード 受け入れる方式をこちらで決める

Python

def read_token(token, public_key):
    return jwt.decode(
        token, public_key,
        algorithms=["RS256"])

JWT は、頭の部分に「どの方式で印を付けたか」を書きます。脆い方は、その申告をそのまま信じています。JWT の使い方の指針 RFC 8725 は、方式を「none」(印なし) に書き換えると印を確かめずに通してしまう部品があったこと、公開鍵の方式を HMAC の方式に書き換え、公開鍵を HMAC の鍵として使わせる攻撃があったことを挙げています。そして、部品は呼ぶ側に受け入れる方式を決めさせ、それ以外を使ってはならないとしています。今の部品の多くは既定でこの穴の一部を塞いでいますが、方式を決めるのは呼ぶ側の仕事として、コードに書いておきます。

ログでの見え方と検知

印の確かめに失敗した呼び出しは、理由と送り手を付けて記録に残します。たとえば次のような行です。

印が合わないときの記録

記録

webhook signature mismatch
  sender=shop-a status=401

ふだん、正しい取引先からの通知で印が合わないことはほとんどありません。守る側は次を見張ります。

見る物なぜ効くか
送り手ごとの印の失敗の数急に増えたら、偽の通知か、鍵の取り違えを疑う
受け入れる方式に無いトークン「none」や想定外の方式は、それだけで攻めの試みの印になる
コードの中のハッシュと秘密のつなぎsha256(secret + ...) の形は、書いた時点で見つけられる
署名の鍵の作られた日と、印に使われた鍵退役したはずの鍵で付いた印は、鍵が盗まれた疑いになる

最後の行は、関わる事例で見る失敗そのものです。記録には、届いた印や鍵の値そのものは残さず、失敗したことと送り手だけを残します。

直し方と、直ったことの確かめ方

自分の PC の中だけで行います。外のサービスや、他人の機械には向けません。

  1. 作業用のフォルダに、脆い verify と、それを呼ぶ小さな受け口の関数を置く
  2. ラボ用の秘密の値で本文に印を付け、脆い verify が通すことを確かめる
  3. 関数を直したコードに置き換え、同じ本文と、HMAC で付け直した印で通ることを確かめる
  4. 下の試験を足す。1 つ目は RFC 4231 の試験用の値で、HMAC-SHA256 を正しく計算していることを固める
  5. 直す前のコード (つないでハッシュ) に戻すと、1 つ目の試験が落ちることも確かめる
  6. 終わったら、作業用のフォルダごと消す
直ったことを固める試験

Python

KEY = b"Jefe"
MSG = b"what do ya want for nothing?"
MAC = ("5bdcc146bf60754e6a04242608"
       "9575c75a003f089d2739839dec"
       "58b964ec3843")

def test_rfc4231_vector():
    assert verify(KEY, MSG, MAC)

def test_tampered_body():
    bad = MSG + b"!"
    assert not verify(KEY, bad, MAC)

試験は「印の作り方が HMAC である」ことを、公開された試験用の値で固定します。あとで誰かが、ハッシュに秘密をつなぐ書き方に戻しても、ここで止まります。

あわせて、署名の鍵の扱いを点検します。鍵は使える機械と人を絞った置き場に預け、入れ替えの手順を自動にします。確かめる側は、受け入れる鍵と方式を一覧で持ち、一覧に無い物で付いた印は通しません。

関わる事例

2023年夏、Storm-0558 と呼ばれる攻め手が、Microsoft の Exchange Online の複数の組織のメールに入りました。米国のサイバー安全審査委員会 (CSRB) の2024年3月20日の報告書によると、使われたのは、Microsoft が2016年に作った個人向けのサービスの署名の鍵で印を付けた、偽の認証のトークンでした。この鍵は個人向けの画面で使えるトークンにしか印を付けられないはずでしたが、企業向けのメールにも通ってしまいました。鍵は2021年3月に退役させる予定でしたが、延期されたまま残っていました。報告書は、鍵の自動の入れ替えの仕組みを後回しにした判断を問題として挙げています。

SolarWinds の事件では、CISA の注意喚起 AA20-352A によると、攻め手が手を加えた部品が開発の工程に入り込み、正規のコード署名の証明書で署名されてから配られました。署名は「誰が付けたか」を示しますが、署名する前に中身が書き換えられていれば、書き換えられた物に本物の印が付きます。ビルドの工程を守る話は「サプライチェーン攻撃」のレッスンで扱います。

確認クイズ

1 問目

通知の印を「共有した秘密の値と本文をつないだ SHA-256 のハッシュ」で作っていました。何が起きえますか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

2 問目

JWT を確かめるコードを書きます。受け入れる方式はどう決めるのがよいですか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書けたら、このレッスンは修了です。

  • ただのハッシュで改ざん検知の符号を作ってはいけない理由
模範解答を見る

SHA-256 のようなハッシュは、結果がそのまま計算の途中の状態なので、結果から計算を続けられる。だから「秘密 + 本文」のハッシュを印にすると、秘密を知らない相手でも本文に書き足して、それに合う印を作れる。HMAC は鍵を混ぜた外側の段でもう一度ハッシュを取り、内側の状態を外に出さないので、この書き足しを防げる。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

次のレッスンは「TLS 1.3 の中身」です。ここで扱った暗号、改ざん検知、署名を組み合わせて、通信の相手を確かめ、途中で読まれず書き換えられない通り道をどう作るのかを扱います。

次のレッスン 1-4 TLS 1.3 の中身

このレッスンにつながる物

トラック 1 基盤の中で

前のレッスン 1-2 暗号とその失敗

学んだことを次につなげる

ゆめさくセキュリティは、攻撃の仕組みを知り、自分の PC の中で壊して直し、守る側の判断を身につけるための学びの場です。

ラボはすべて自分の PC の中 (127.0.0.1) で閉じます。自分が管理していない相手に試すことは扱いません。