跳轉到

6.1 HMAC 與訊息驗證碼#

概念說明#

MAC(Message Authentication Code,訊息驗證碼):發送方與接收方共享金鑰 K,對訊息 M 計算標籤 T = MAC(K, M)。接收方重算比對,即可確認兩件事:

  1. 完整性:M 未被竄改(竄改後重算的 T 對不上)。
  2. 真實性:訊息來自持有 K 的人(第三者無 K 便算不出有效 T)。

注意 MAC 不具不可否認性:收方也有 K,拿到 (M, T) 無法向第三人證明是「對方」算的——兩人都算得出來。需要不可否認就要數位簽章(6.2 節)。

HMAC 是最通用的 MAC 構造,由 Bellare、Canetti、Krawczyk 於 1996 年提出:

HMAC(K, M) = H((K ⊕ opad) || H((K ⊕ ipad) || M))

其中 ipad/opad 是固定常數。雙層雜湊的設計:內層把金鑰「拌進」雜湊、外層再包一次,使底層雜湊(即使是 MD 結構,見 5.3 節)的弱點難以被利用——HMAC 對長度延伸免疫,且其安全性證明只需底層雜湊是「偽隨機函數」這個較弱的假設。

演算法步驟#

  1. 金鑰 K 若長於雜湊區塊大小,先雜湊之;不足則補零至區塊大小。
  2. 內層:h1 = H((K ⊕ ipad) || M)。
  3. 外層:T = H((K ⊕ opad) || h1)。
  4. 驗證:重算 T 並常數時間比對(絕不可用 ==,見下方)。

Python 實作#

import hmac, hashlib, os

key = os.urandom(32)
msg = b"GET /pay?to=alice&amount=100"

tag = hmac.new(key, msg, hashlib.sha256).hexdigest()
print("MAC:", tag)

# 驗證:常數時間比對,防止時序攻擊
ok = hmac.compare_digest(
    hmac.new(key, msg, hashlib.sha256).hexdigest(), tag)
print("驗證通過:", ok)

# 手寫 HMAC(教學,理解結構用;實務請用 hmac 模組)
block = 64
k = key + b"\x00" * (block - len(key))
inner = hashlib.sha256(bytes(x ^ 0x36 for x in k) + msg).digest()
outer = hashlib.sha256(bytes(x ^ 0x5c for x in k) + inner).hexdigest()
assert outer == tag

# 金鑰派生:HMAC 也是 HKDF 的積木
prk = hmac.new(b"salt", key, hashlib.sha256).digest()
okm = hmac.new(prk, b"info\x01", hashlib.sha256).digest()
print("HKDF 衍生金鑰:", okm.hex()[:32])

安全性分析#

  • 必要屬性:不可偽造(EUF-CMA 概念——即使攻擊者能大量取得任意訊息的 MAC,也造不出新訊息的有效 MAC)。256 位元 HMAC-SHA256 的偽造需約 2^256 次,且線上偽造有更嚴格的界。
  • 時序攻擊:用 == 逐字比對,第一個不同字元立刻返回,攻擊者測量比對時間可逐位元組猜出正確 MAC。hmac.compare_digest 常數時間比對是必備寫法。
  • 重播攻擊:MAC 只保證訊息完整性與來源,不防重播——舊訊息 (M, T) 可以原樣重發。防禦需在訊息中加時間戳、序號或挑戰值(nonce),讓每則訊息唯一。
  • 密碼雜湊不能用 HMAC 湊:HMAC 快,字典攻擊照樣飛快;密碼儲存請用 Argon2/bcrypt/PBKDF2(9.3 節)。
  • 與 AEAD 的關係:若你已用 AES-GCM 或 ChaCha20-Poly1305(4.2、4.3 節),機密性與完整性一次到位,不必另加 HMAC;HMAC 用於 AEAD 不適用的場合(如長期金鑰派生、非加密的完整性需求)。

練習#

  1. 實作「重播攻擊」示範:Alice 對轉帳訊息做 MAC,Eve 錄下後原樣重發三次,討論如何用序號/時間戳防禦。
  2. 寫一個計時實驗:比較 == 與 compare_digest 比對錯誤 MAC 的耗時差異,解釋為何逐位元組比較會洩漏資訊。
  3. 用手寫 HMAC 程式碼驗證:把底層換成 MD5,觀察 HMAC-MD5 的輸出仍正常——並說明為什麼 MD5 的碰撞弱點並不直接讓 HMAC-MD5 崩潰(但現行標準仍禁止使用)。