一覧SC-06-2 · ハッシュとメッセージ認証

HMACによるメッセージ認証

鍵の保有者、認証するバイト列、再利用を止める状態を分けて設計します。

説明

HMACは共有秘密鍵とメッセージから認証タグを計算します。鍵を知らない相手による本文の作り替えを検出できますが、本文の暗号化や再送の拒否を自動で行うものではありません。

注文メッセージへタグを付け、本文・鍵・表現を変えます。その後、同じ要求を再送し、署名対象の要求IDと使用済み記録が一回性にどう関わるかを確認します。

鍵の保有者、認証するバイト列、再利用を止める状態を分けて設計します。

1共有鍵でタグを作る

教材の公開鍵文字列は秘密として使えません。ここでは共有鍵が同じである場合の計算を観察します。

from sc06 import * message = b"id=7&amount=100" t = tag(message)本文にHMAC-SHA-256を付ける print(message) print(t.hex())

本文はそのまま読めます。HMACは本文の秘匿ではなく、鍵を使った照合値の生成です。

2受信側で同じ鍵を使う

受信側は受領本文からタグを再計算し、受領したタグと比較します。

print(verify(message,t)受領本文とタグを検証する)

Trueです。比較には一定時間で比較するcompare_digestを使っています。鍵共有者は同じタグを生成できます。

3金額を変更する

タグを保ったまま本文の100を900へ変えます。

changed = b"id=7&amount=900" print(verify(changed,t)改変した金額を元のタグと照合する)

Falseです。正しいタグは元の本文のバイト列に対応しており、新しい金額には対応しません。

4別の鍵で検証する

本文とタグが同じでも、共有鍵が一致しなければ検証できません。

print(verify(message,t,b"different-public-example-key"別の公開試料鍵を指定する))

Falseです。鍵を取り違えた場合と、本文・タグが改変された場合は、検証結果だけでは原因を特定できません。

5意味が同じでもバイト列を変える

パラメーターの順序を入れ替えます。アプリが同じ意味と扱っても、HMACの入力バイト列は変わります。

print(verify(b"amount=100&id=7"同じ値を別の順序で表現する,t)) print(message.hex())

Falseです。送受信で同じ正規化規則を使うか、受領した元のバイト列を認証対象にする必要があります。

6同じ要求は再び検証できる

本文とタグをそのまま再送します。

print(verify(message,t),verify(message,t)同じ要求を2回検証する)

どちらもTrueです。HMACが正しいことと、まだ実行していない要求であることは別の条件です。

7認証した要求IDを一度だけ受け入れる

要求IDはHMAC対象に含まれています。検証後にIDを取り出し、受付記録と業務更新を一体で確定する設計が必要です。ここでは単一処理の集合で受付を表します。

seen = set() request_id =認証対象の本文からIDを取り出す message.split(b"&")[0] accepted = verify(message,t) and request_id not in seen if accepted: seen.add(request_id)初回受付を記録する print(accepted, sorted(seen)) print(verify(message,t) and request_id not in seen)再送を拒否する

最初だけTrueで、再提示はFalseです。並行する実システムでは、検査と登録の競合を一意制約やトランザクションで防ぎます。

8鍵共有者なら別の本文にもタグを作れる

正当な受信側も共有鍵を知っています。金額900の新しいタグを作って検証します。

new_tag = tag(changed)鍵保有者として新しい本文へタグを作る print(verify(changed,new_tag))

Trueです。HMACだけでは共有者のどちらが作ったかを第三者へ証明できません。秘密鍵と公開検証鍵を分ける電子署名とはこの点が異なります。

まとめ

HMACは鍵と正確なバイト列に対応する認証タグです。

本文の暗号化と再送拒否は別に設計します。

鍵共有者は全員タグを作れるため、作成者の第三者向け証明にはなりません。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sc06 import * message = b"id=7&amount=100" t = tag(message) print(message) print(t.hex())
b'id=7&amount=100'
6947f8de1629535f641b730388f5108db3fa14828c323825b97cef0295d33ff6
2
print(verify(message,t))
True
3
changed = b"id=7&amount=900" print(verify(changed,t))
False
4
print(verify(message,t,b"different-public-example-key"))
False
5
print(verify(b"amount=100&id=7",t)) print(message.hex())
False
69643d3726616d6f756e743d313030
6
print(verify(message,t),verify(message,t))
True True
7
seen = set() request_id = message.split(b"&")[0] accepted = verify(message,t) and request_id not in seen if accepted: seen.add(request_id) print(accepted, sorted(seen)) print(verify(message,t) and request_id not in seen)
True [b'id=7']
False
8
new_tag = tag(changed) print(verify(changed,new_tag))
True

演習完了