HMAC、PBKDF2 與 HKDF —— 從零講清帶金鑰雜湊
一句話:像 SHA-256 這樣的裸雜湊誰都能算,所以它證明不了訊息是誰發的 —— HMAC 混入一個秘密金鑰,使只有持有金鑰的人才能產生(或驗證)摘要;PBKDF2 和 HKDF 則複用同一套 HMAC 機器,把秘密變成金鑰:對口令要慢,對已隨機的材料要快。
HMAC 是什麼(RFC 2104)
構造。HMAC 是兩次雜湊,不是一次。金鑰被填充到雜湊的塊大小(MD5/SHA-1/SHA-256 為 64 位元組,SHA-384/512 為 128 位元組);訊息先與“金鑰 XOR 固定內層填充”一起雜湊一次,得到的摘要再與“金鑰 XOR 固定外層填充”雜湊一次。只要底層雜湊是像樣的偽隨機函式,這個雙夾層就被證明是安全的 —— 這也是 HMAC-MD5 至今仍能抵禦攻破裸 MD5 的實用碰撞攻擊的原因(儘管它反正已經退役,見下文)。
金鑰。任意長度都可以:短於塊大小的直接使用(但不足 32 位元組會被人皺眉),更長的會先被雜湊。本頁允許把金鑰按文本、hex 或 Base64 輸入,這樣 64 個 hex 字元的生產金鑰不會被誤當 64 字元的文本口令貼上 —— 輸出行裡記錄讀取方式正是為了這個。
在哪裡會遇到它。JWT 頭部的 HS256 欄位就是對 base64url 編碼的令牌部分做 HMAC-SHA-256(貼上 HS256 金鑰時,本站的JWT 解碼器算的正是它)。Webhook 簽名(Stripe、GitHub)、AWS SigV4、TLS 記錄保護(舊版本)以及 OpenSSH 的近親 umac-64@openssh.com 都屬於同一家族。
為什麼不直接 hash(message || key)?長度擴充套件攻擊:MD5/SHA-1/SHA-2 家族的每種雜湊都允許知道 H(secret || msg) 的攻擊者在沒有秘密的情況下算出 H(secret || msg || padding || anything)。HMAC 的內外分層消滅了這一類攻擊。這是 API 簽名程式碼裡最常見的 DIY 錯誤 —— 也是 HMAC 存在的理由。
PBKDF2 是什麼(RFC 8018)
慢是設計出來的。一個口令可能只有 40 位元的真實熵;攻擊者的 GPU 每秒能試數十億次裸 SHA-256。PBKDF2 把 HMAC(password, salt) 反覆執行 —— iter 次 —— 讓你和攻擊者每次猜測付出同樣的 iter 次 HMAC。這裡的預設值是 HMAC-SHA-256 的 600,000 次迭代,即 OWASP 口令儲存速查表的基線;滑塊上限一千萬,因為隨著硬體提速,安全建議也會不斷上調。
鹽。每口令的隨機鹽讓相同口令的使用者不再雜湊出相同結果,預計算表(彩虹表)也就失效了。鹽不是秘密 —— 它就存在雜湊旁邊 —— 它只需要唯一。16 個隨機位元組是慣例。
已知弱點。PBKDF2 抗 CPU 但不抗記憶體:GPU 和 ASIC 並行化它毫不費力。所以在設計口令雜湊儲存時,現代建議是 scrypt 或 Argon2id(本家族的密碼雜湊生成器頁面覆蓋了它們),而 PBKDF2 仍是相容性與 FIPS-140 場景的正確答案。
HKDF 是什麼(RFC 5869)
先提取,再擴充套件。HKDF 做兩件事。Extract:把可能不均勻的輸入金鑰材料(例如 Diffie–Hellman 共享秘密)與鹽一起壓成一個短的偽隨機金鑰 PRK = HMAC(salt, IKM)。Expand:把 PRK 拉伸到最多 255 個雜湊塊的任意長度,每個輸出塊與前一塊鏈式相連並由 info 欄位標記用途。結果是一個秘密可以安全地派生出加密金鑰、MAC 金鑰和 IV,彼此分離。
不適用於口令。HKDF 假定輸入已有充分熵 —— 它每 32 個輸出位元組只做一次 HMAC,人類口令幾乎瞬間穿過它,這正是攻擊者想要的。用 HKDF 而不是 PBKDF2 處理口令是經典設計錯誤;info 欄位是用途標籤("client traffic key"、"server iv"),不是額外熵。
在哪裡會遇到它。TLS 1.3 從握手秘密經 HKDF 派生它使用的每一個金鑰;WireGuard、Signal、EAP-TLS 1.3 和 JSON Web Encryption(作為直接金鑰協商 KDF)都在同一棵家族樹上。
錨點值 —— 在本頁復現它們
前兩張表是本頁樣例按鈕和自測所用的測試向量(金鑰 "key"、訊息“The quick brown fox jumps over the lazy dog”;然後是 RFC 風格的 16 位元組 0x0b 金鑰與訊息“Hi There”)。全部由獨立原生實現生成,並在本頁複核。
“fox” 的 HMAC,金鑰 = key | 摘要(hex) |
| HMAC-MD5 | 80070713463e7749b90c2dc24911e275 |
| HMAC-SHA-1 | de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9 |
| HMAC-SHA-256 | f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8 |
| HMAC-SHA-384 | d7f4727e2c0b39ae0f1e40cc96f60242d5b7801841cea6fc592c5d3e1ae50700582a96cf35e1e554995fe4e03381c237 |
| HMAC-SHA-512 | b42af09057bac1e2d41708e48a902e09b5ff7f12ab428a4fe86653c73dd248fb82f948a549f7b791a5b41915ee4d1ec3935357e4e2317250d0372afa2ebeeb3a |
| KDF 錨點 | 引數 | 輸出(hex,首位元組) |
| PBKDF2-HMAC-SHA-1 | password / salt,c=4096,32 位元組 | 4b007901b765489abead49d926f721d065a429c12e463f6c4cd79401085b03db |
| PBKDF2-HMAC-SHA-256 | password / salt,c=4096,32 位元組 | c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a |
| HKDF-SHA-256 (RFC 5869 A.1) | IKM=0x0b×22,鹽=00..0c,info=f0..f9,L=42 | 3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865 |
| HKDF-SHA-256 (RFC 5869 A.2) | IKM=00..4f,鹽=60..af,info=b0..ff,L=82 | b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c… |
| HKDF-SHA-256 (RFC 5869 A.3) | IKM=0x0b×22,空鹽與空 info,L=42 | 8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8 |
常見誤區
- 把 HMAC 當加密。HMAC 證明完整性與來源(給定共享秘密);它不隱藏任何東西。沒有金鑰的人仍然能看到完整訊息。
- 把 HMAC 當簽名。數字簽名是公鑰的:一人簽名,任何人可驗證。HMAC 是對稱的:每個驗證者同樣能偽造。不可抵賴需要 RSA/ECDSA(本家族的 RSA 工具釋出後可用),不是 HMAC。
- 金鑰讀取方式不匹配。把 64 字元的 hex 串按文本貼上進金鑰框會變成 64 位元組而不是 32 —— HMAC 完全不同。本頁在輸出行列印讀取方式;兩個工具結果不一致時,先比這一項。
- PBKDF2 迭代次數太低。2000 年時 1,000 次還算合理;今天的 GPU 叢集秒殺它。如果你的系統還在存 1,000 次迭代的雜湊,規劃一次“登入時重雜湊”的遷移吧。
- 對口令用 HKDF。每個輸出塊一次 HMAC 意味著口令瞬間通過 —— 口令用 PBKDF2(或 Argon2id),高熵秘密才用 HKDF。
- 一個派生金鑰處處用。修復是免費的:用不同的
info 標籤派生獨立的金鑰。同一 IKM 不同標籤派生的兩個金鑰在設計上就是獨立的。
常見問題
HMAC-MD5 被破了嗎?作為抗碰撞雜湊,MD5 已經死了,但 HMAC-MD5 的安全性取決於 MD5 作為偽隨機函式的表現,尚無已知實用破解。反正也退役了 —— 除非為了相容舊協議,沒有理由在 HMAC-SHA-256 可用時選它。
HMAC 金鑰該多長?至少等於雜湊輸出長度(SHA-256 為 32 位元組),並由 CSPRNG 生成。短於摘要長度的金鑰允許使用,但每一位不足都直接降低安全性。
PBKDF2 迭代越多就越“安全”嗎,可以無限加?它線性提高攻擊者的成本,但也線性提高你的登入成本。博弈在於選擇使用者能容忍的最大次數 —— OWASP 的數字就是按這個校準的。
為什麼 HKDF 上限是 255 × 雜湊長度?擴充套件迴圈是 T(i) = HMAC(PRK, T(i-1) || info || i),計數器 i 只有一個位元組 —— 255 是最大計數值,這是 RFC 5869 的硬性結構限制,不是策略選擇。
本頁的 PBKDF2 和密碼雜湊生成器有什麼區別?同一演算法,不同工作。本頁派生原始金鑰位元組供你在別處使用;密碼雜湊頁生成的是儲存憑據字串(內嵌鹽與引數),並對照它們驗證貼上的候選口令。