證碼的實(shí)現(xiàn)與實(shí)戰(zhàn))
CPythonhmac模塊完全指南RFC 2104 密鑰哈希消息認(rèn)證碼的實(shí)現(xiàn)與實(shí)戰(zhàn)【免費(fèi)下載鏈接】cpythonThe Python programming language項(xiàng)目地址: https://gitcode.com/GitHub_Trending/cp/cpythonhmac是 Python 標(biāo)準(zhǔn)庫(kù)中實(shí)現(xiàn) HMACKeyed-Hashing for Message Authentication密鑰哈希消息認(rèn)證的模塊其算法依據(jù) RFC 2104同時(shí)兼容 RFC 4231在 API 消息簽名、請(qǐng)求鑒權(quán)、數(shù)據(jù)完整性校驗(yàn)、Webhook 驗(yàn)簽等場(chǎng)景中扮演核心角色。通過(guò)閱讀本文你將掌握hmac.new、hmac.digest、HMAC對(duì)象各方法與屬性以及常量時(shí)間比較函數(shù)compare_digest的正確用法并能結(jié)合 CPython 源碼理解其 OpenSSL/HACL* 加速路徑與純 Python 回退實(shí)現(xiàn)的分層設(shè)計(jì)。本文完全基于當(dāng)前 CPython 倉(cāng)庫(kù)中的 Lib/hmac.py、Modules/_hashopenssl.c、Modules/hmacmodule.c 與其配套測(cè)試 Lib/test/test_hmac.py 展開(kāi)。HMAC 是什么算法標(biāo)準(zhǔn)、適用前提與關(guān)鍵限制HMAC 在標(biāo)準(zhǔn)哈希函數(shù)如 MD5、SHA-1、SHA-256 等之上引入一個(gè)共享密鑰secret key只有同時(shí)掌握密鑰與消息的雙方才能計(jì)算并核對(duì)出相同的認(rèn)證碼因此它可以同時(shí)提供消息完整性與來(lái)源認(rèn)證能力。RFC 2104 定義的 HMAC 構(gòu)造可概括為HMAC(K, m) H((K ⊕ opad) ∥ H((K ⊕ ipad) ∥ m))其中K是經(jīng)過(guò)處理的密鑰若密鑰長(zhǎng)度超過(guò)哈希分組塊大小則先對(duì)其做一次哈希壓縮ipad 0x36、opad 0x5C為兩個(gè)不同的填充常量H為底層哈希函數(shù)∥表示字節(jié)拼接。在 Lib/hmac.py 頂部可以直觀看到這兩個(gè)常量正是以全表平移的形式實(shí)現(xiàn)的trans_5C bytes((x ^ 0x5C) for x in range(256)) trans_36 bytes((x ^ 0x36) for x in range(256))該模塊允許使用任意具有固定摘要長(zhǎng)度的哈希函數(shù)但有一個(gè)顯著限制原文檔與源碼雙重明確擴(kuò)展輸出函數(shù)XOF如 SHAKE-128 / SHAKE-256 不能用于 HMAC。這一點(diǎn)在 Lib/hmac.py 的_is_shake_constructor()中顯式檢測(cè)shake/SHAKE前綴并拋出ValueError(unsupported hash algorithm ...)相關(guān)行為同樣被測(cè)試文件 Lib/test/test_hmac.py 中以test_hmac_new_xof_digestmod命名的用例覆蓋。此外本模塊與提供安全哈希函數(shù)的模塊hashlib是天然搭檔——digestmod參數(shù)接受的摘要名稱須是hashlib.new()能識(shí)別的算法名詳見(jiàn) Doc/library/hashlib.rst。核心 API 一覽原文檔定義的模塊級(jí)入口函數(shù)與HMAC類方法/屬性可按如下表格速覽詳細(xì)用法見(jiàn)后文各節(jié)API類型說(shuō)明引入/變更版本hmac.new(key, msgNone, digestmod)函數(shù)返回新的HMAC對(duì)象digestmod必填3.8 起digestmod必填hmac.digest(key, msg, digest)函數(shù)單發(fā)計(jì)算摘要等價(jià)于HMAC(key, msg, digest).digest()但走優(yōu)化實(shí)現(xiàn)更快3.7 新增HMAC.update(msg)方法增量喂入消息數(shù)據(jù)可多次調(diào)用3.4 起支持任意hashlib兼容類型HMAC.digest()方法返回原始字節(jié)摘要長(zhǎng)度等于digest_size可能含 NUL 等非 ASCII 字節(jié)—HMAC.hexdigest()方法返回兩倍長(zhǎng)度的十六進(jìn)制字符串—HMAC.copy()方法克隆對(duì)象用于高效計(jì)算共享相同前綴的多個(gè)摘要—HMAC.digest_size屬性結(jié)果摘要字節(jié)數(shù)—HMAC.block_size屬性底層哈希算法的內(nèi)部塊大小字節(jié)3.4 新增HMAC.name屬性規(guī)范名稱恒為小寫(xiě)如hmac-md53.4 新增hmac.compare_digest(a, b)函數(shù)抗時(shí)序分析常量時(shí)間的相等比較3.3 新增需要注意從 3.10 開(kāi)始未文檔化的HMAC.digest_cons、HMAC.inner、HMAC.outer屬性已被移除見(jiàn)原文檔 versionchanged 說(shuō)明并且HMAC對(duì)象在 Lib/hmac.py 中通過(guò)__slots__約束為僅有_hmac、_inner、_outer、block_size、digest_size五個(gè)槽位??焖偕鲜窒⒑灻c驗(yàn)簽創(chuàng)建 HMAC 對(duì)象hmac.new()hmac.new(key, msgNone, digestmod)的三個(gè)參數(shù)中keybytes 或 bytearray 類型的密鑰3.4 起支持傳其他類型會(huì)拋TypeErrormsg可選的初始輸入。若提供等價(jià)于隨后自動(dòng)調(diào)用一次update(msg)digestmod必填3.8 起強(qiáng)制可以是三類取值中的任一種適合傳給hashlib.new()的算法名字符串如sha256哈希構(gòu)造器如hashlib.sha256遵循 PEP 247 的模塊或哈希對(duì)象。由于digestmod位置在可選的msg之后當(dāng)你不傳msg時(shí)請(qǐng)務(wù)必用關(guān)鍵字參數(shù)傳入以避免歧義。這一點(diǎn)在源碼 docstring 與構(gòu)造器實(shí)現(xiàn)中都有強(qiáng)調(diào)例如 Lib/hmac.py 在digestmod為空時(shí)會(huì)拋出TypeError(Missing required argument digestmod.)。一個(gè)完整的“簽名 驗(yàn)簽”示例import hashlib import hmac # 雙方共享的密鑰實(shí)際系統(tǒng)中應(yīng)從密鑰管理服務(wù)獲取 secret bshared-secret-key # 發(fā)送方對(duì)消息簽名 def sign(message: bytes, key: bytes) - str: return hmac.new(key, message, hashlib.sha256).hexdigest() # 接收方計(jì)算期望簽名并做常量時(shí)間比對(duì) def verify(message: bytes, expected: str, key: bytes) - bool: computed sign(message, key) return hmac.compare_digest(computed, expected) sig sign(bamount100toalice, secret) print(sig) assert verify(bamount100toalice, sig, secret) # 消息被篡改時(shí)驗(yàn)簽失敗 assert not verify(bamount99999toalice, sig, secret)單發(fā)計(jì)算hmac.digest()如果你的消息已經(jīng)完整地放在內(nèi)存里用hmac.digest(key, msg, digest)比先new()再digest()更快——它在底層使用優(yōu)化的 C/內(nèi)聯(lián)實(shí)現(xiàn)而非逐塊推進(jìn)狀態(tài)機(jī)import hmac # 用法一digest 傳算法名字符串走 C 加速路徑 raw hmac.digest(bkey, bmessage, sha256) # 用法二digest 傳哈希構(gòu)造器 raw2 hmac.digest(bkey, bmessage, hashlib.sha256) assert raw raw2CPython 實(shí)現(xiàn)細(xì)節(jié)原文檔明確指出只有當(dāng)digest是字符串且該算法受 OpenSSL 支持時(shí)才會(huì)使用優(yōu)化的 C 實(shí)現(xiàn)調(diào)用 Modules/_hashopenssl.c 暴露的_hashlib.hmac_digest若密鑰超過(guò) OpenSSLHMAC的INT_MAX大小限制拋OverflowError或算法不被 OpenSSL 支持則按 Lib/hmac.py 的邏輯繼續(xù)嘗試_hmacHACL* 實(shí)現(xiàn)乃至純 Python 回退。HMAC 對(duì)象的方法與屬性詳解update(msg)增量喂入數(shù)據(jù)update(msg)可被重復(fù)調(diào)用其效果等價(jià)于將所有參數(shù)拼接后一次調(diào)用即m.update(a); m.update(b)與m.update(a b)完全等價(jià)。這在處理流式數(shù)據(jù)如分塊讀取大文件后計(jì)算校驗(yàn)碼時(shí)非常有用無(wú)需在內(nèi)存中拼接完整消息。msg可以是任意受hashlib支持的類型3.4 起。digest()與hexdigest()輸出形態(tài)digest()返回原始 bytes長(zhǎng)度為構(gòu)造時(shí)所用摘要算法的digest_size可能包含 NUL 等非 ASCII 字節(jié)不適合直接放進(jìn)文本協(xié)議hexdigest()返回長(zhǎng)度兩倍、僅含十六進(jìn)制字符的字符串適合在電子郵件、HTTP header、URL 等非二進(jìn)制環(huán)境中安全交換摘要。安全性警示原文檔 warning在驗(yàn)證例程中把digest()或hexdigest()的輸出與外部提供的摘要比較時(shí)務(wù)必使用hmac.compare_digest()而非運(yùn)算符以降低時(shí)序攻擊風(fēng)險(xiǎn)詳見(jiàn)下文專節(jié)。copy()克隆 HMAC 對(duì)象copy()返回當(dāng)前對(duì)象的一個(gè)“克隆體”此后對(duì)克隆體的update不會(huì)影響原對(duì)象。這一能力可高效計(jì)算共享同一初始子串的多條消息摘要先統(tǒng)一update公共前綴再?gòu)?fù)制出多個(gè)分支分別喂入不同后綴。h hmac.new(bkey, bheader:, hashlib.sha256) h1 h.copy(); h1.update(bpayload-A) # 只算 header:payload-A h2 h.copy(); h2.update(bpayload-B) # 只算 header:payload-B在 Lib/hmac.py 的實(shí)現(xiàn)中copy()直接通過(guò)__new__繞過(guò)昂貴的__init__對(duì) C 加速路徑調(diào)用self._hmac.copy()對(duì)純 Python 路徑則分別復(fù)制_inner與_outer因此成本很低。對(duì)象屬性digest_size、block_size、name屬性含義示例值digest_size結(jié)果 HMAC 摘要的字節(jié)數(shù)sha256 為 32sha512 為 64block_size底層哈希算法的內(nèi)部塊大小字節(jié)sha256 為 64sha384/sha512 為 128name本 HMAC 的規(guī)范名稱恒為小寫(xiě)hmac-sha256、hmac-md5block_size與name自 3.4 起提供。需要特別指出的是 Lib/hmac.py 中模塊級(jí)存在一個(gè)占位digest_size None其注釋明確告誡HMAC 返回摘要的大小取決于底層哈希模塊應(yīng)使用對(duì)象實(shí)例的digest_size屬性而不是模塊級(jí)的這個(gè)占位值。name屬性為只讀 property在 C 加速路徑直接透?jìng)鞯讓訉?duì)象名稱在純 Python 路徑則拼為fhmac-{self._inner.name}見(jiàn) Lib/hmac.py。compare_digest()抵御時(shí)序攻擊的常量時(shí)間比較驗(yàn)簽環(huán)節(jié)最容易被忽視的漏洞是時(shí)序側(cè)信道普通比較在遇到第一個(gè)不同字節(jié)時(shí)會(huì)短路返回攻擊者可通過(guò)測(cè)量響應(yīng)耗時(shí)逐字節(jié)猜出合法摘要。hmac.compare_digest(a, b)通過(guò)避免基于內(nèi)容的短路行為來(lái)抵御時(shí)序分析適合用于密碼學(xué)場(chǎng)景。用法約束a、b必須為同類型要么都是str僅限純 ASCII例如hexdigest()的輸出要么都是 bytes-like 對(duì)象類型不同如 str 與 bytes 混用、非 ASCII 字符串等都會(huì)拋出TypeError。import hmac hmac.compare_digest(abc123, abc123) # Truehexdigest 場(chǎng)景 hmac.compare_digest(b\x00\xff, b\x00\xff) # Truedigest 場(chǎng)景注意原文檔中的note若a與b長(zhǎng)度不同或發(fā)生錯(cuò)誤理論上時(shí)序仍可能泄露兩者的類型與長(zhǎng)度信息——但絕不會(huì)泄露它們的值。這也是為什么實(shí)踐中常約定固定長(zhǎng)度的摘要格式或先比較長(zhǎng)度再比較內(nèi)容。實(shí)現(xiàn)層面3.10 起該函數(shù)在可用時(shí)內(nèi)部使用 OpenSSL 的CRYPTO_memcmp()。搜索 Modules/_hashopenssl.c 可以看到result | CRYPTO_memcmp(left, right, length);的累加式比對(duì)正是“無(wú)論在哪一位不同都不提前退出”的常量時(shí)間寫(xiě)法在無(wú) OpenSSL 環(huán)境下則回退到_operator._compare_digest見(jiàn) Lib/hmac.py 的導(dǎo)入分支。測(cè)試用例 Lib/test/test_hmac.py 系統(tǒng)性地覆蓋了非法輸入類型、str/bytes/bytearray 混用、以及bytes/str子類——即使子類把__eq__重寫(xiě)成拋異常compare_digest也絕不會(huì)調(diào)用它。源碼級(jí)實(shí)現(xiàn)CPython 的三層加速架構(gòu)hmac并不是一個(gè)“只看 Python 代碼”的純腳本模塊。從 Lib/test/test_hmac.py 的模塊注釋可以清晰看到CPython 為其提供了三種實(shí)現(xiàn)按優(yōu)先級(jí)依次為OpenSSL HMAC使用 OpenSSL 哈希函數(shù)由_hashlib擴(kuò)展提供Modules/_hashopenssl.c中的_hashlib.hmac_new、_hashlib.hmac_digestHACLHMAC*內(nèi)置的 C 實(shí)現(xiàn)Modules/hmacmodule.c實(shí)現(xiàn)_hmac在無(wú) OpenSSL 的構(gòu)建如 WASI、部分受限平臺(tái)上保證仍有余力且可預(yù)測(cè)的性能與安全屬性通用純 Python HMACLib/hmac.py內(nèi)的回退實(shí)現(xiàn)能對(duì)接 OpenSSL/HACL* 的哈希名與構(gòu)造器、PEP 247 模塊以及任意用戶自定義哈希對(duì)象。對(duì)象創(chuàng)建時(shí)的分派邏輯非常直白見(jiàn) Lib/hmac.py若_hashopenssl可用且digestmod為字符串或 OpenSSL 內(nèi)置構(gòu)造器類型優(yōu)先走_(dá)init_openssl_hmac()算法不受 OpenSSL 支持時(shí)拋出UnsupportedDigestmodError并繼續(xù)下探否則若_hmac可用且digestmod為字符串走_(dá)init_builtin_hmac()未知算法拋UnknownHashError再下探最后落入_init_old()純 Python 路徑。單發(fā)函數(shù)hmac.digest()的分派邏輯類似Lib/hmac.py但多了一層超大密鑰保護(hù)OpenSSL 的HMAC將密鑰限制在INT_MAX內(nèi)HACL* 限制在UINT32_MAX內(nèi)一旦越界即優(yōu)雅回退到_compute_digest_fallback()而不是讓 C 層崩潰。純 Python 回退路徑_compute_digest_fallback()Lib/hmac.py是理解 RFC 2104 算法細(xì)節(jié)的最佳教材其關(guān)鍵步驟為blocksize getattr(inner, block_size, 64) if len(key) blocksize: # 密鑰過(guò)長(zhǎng)則先哈希壓縮 key digest_cons(key).digest() key key.ljust(blocksize, b\0) # 補(bǔ)齊到塊大小 inner.update(key.translate(trans_36)) # ipad 0x36 outer.update(key.translate(trans_5C)) # opad 0x5C inner.update(msg) outer.update(inner.digest()) # 外層再包一層 return outer.digest()值得注意的工程細(xì)節(jié)若底層哈希對(duì)象缺少block_size屬性或塊大小小于 16_init_old()會(huì)發(fā)出RuntimeWarning并回退到類默認(rèn)的 64 字節(jié)塊Lib/hmac.py類屬性blocksize 64的注釋說(shuō)明它代表“默認(rèn)塊大小”允許子類重寫(xiě)。實(shí)戰(zhàn)典型應(yīng)用場(chǎng)景場(chǎng)景一Webhook / 開(kāi)放 API 的請(qǐng)求簽名服務(wù)端與調(diào)用方預(yù)共享密鑰調(diào)用方對(duì)規(guī)范化后的請(qǐng)求參數(shù)按固定順序拼接后計(jì)算 HMAC放入X-Signature頭服務(wù)端用compare_digest校驗(yàn)可同時(shí)防篡改與防偽造def make_signature(payload: str, timestamp: str, secret: bytes) - str: # 規(guī)范化待簽字符串務(wù)必與服務(wù)端完全一致 message f{timestamp}.{payload}.encode() return hmac.new(secret, message, sha256).hexdigest() def check_signature(payload: str, timestamp: str, secret: bytes, sig: str) - bool: if not (isinstance(sig, str) and len(sig) 64): # 先校長(zhǎng)度再常量時(shí)間比較 return False return hmac.compare_digest(make_signature(payload, timestamp, secret), sig)場(chǎng)景二增量校驗(yàn)大文件無(wú)需將整個(gè)文件讀入內(nèi)存使用update()流式處理兼顧內(nèi)存與完整性import hmac, hashlib def file_hmac_hex(path: str, secret: bytes, chunk: int 1 20) - str: h hmac.new(secret, digestmodhashlib.sha256) with open(path, rb) as f: while block : f.read(chunk): h.update(block) return h.hexdigest()場(chǎng)景三與標(biāo)準(zhǔn)測(cè)試向量對(duì)照自檢RFC 4231 的 Test Case 120 字節(jié)0x0b密鑰、消息Hi There對(duì)應(yīng) sha256 摘要為b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7——該向量可從測(cè)試文件 Lib/test/test_hmac.py 中直接讀取MD5/SHA-1 向量取自 RFC 2202SHA-2 取自 RFC 4231SHA-3 取自 NIST。你可以用下面代碼驗(yàn)證自己的環(huán)境實(shí)現(xiàn)import hmac, hashlib got hmac.digest(b\x0b * 20, bHi There, sha256).hex() assert got b0344c61d8db38535ca8afceaf0bf12b \ 881dc200c9833da726e9376c2e32cff7 print(RFC 4231 TC1 OK)邊界情況與常見(jiàn)誤區(qū)1. 密鑰類型與長(zhǎng)度key只接受 bytes/bytearray源碼在 Lib/hmac.py 顯式拋TypeError密鑰長(zhǎng)度超過(guò)底層哈希塊大小如 sha256 的 64 字節(jié)時(shí)算法會(huì)先對(duì)密鑰做一次哈希壓縮再參與計(jì)算因此從密碼學(xué)角度“過(guò)長(zhǎng)密鑰”并不會(huì)帶來(lái)額外安全強(qiáng)度反而應(yīng)先經(jīng) KDF 處理。2. 算法塊大小對(duì)照決定密鑰“過(guò)長(zhǎng)”的閾值底層算法block_size字節(jié)digest_size字節(jié)MD5 / SHA-1 / SHA-224 / SHA-2566416 / 20 / 28 / 32SHA-384 / SHA-51212848 / 643. 不能用于 XOFSHAKE-128/256 等擴(kuò)展輸出函數(shù)會(huì)觸發(fā)ValueError因?yàn)?HMAC 需要固定長(zhǎng)度摘要作為中間值與最終輸出對(duì)應(yīng)_is_shake_constructor檢查。4.digestmod必須顯式提供3.8 起缺省會(huì)拋TypeError舊的hmac.new(key, msg)兩參調(diào)用方式不可再依賴默認(rèn) MD5。5. 摘要對(duì)比一定用compare_digest無(wú)論digest()的二進(jìn)制輸出還是hexdigest()的十六進(jìn)制輸出都不要用前者二進(jìn)制中可能混入 NUL 等字符比較行為易踩坑且存在時(shí)序泄露。6. 模塊級(jí)hmac.digest_size是None這是源碼中的顯式占位務(wù)必從實(shí)例讀取真實(shí)值。測(cè)試體系與進(jìn)一步閱讀HMAC 的正確性在 CPython 中受到嚴(yán)格驗(yàn)證Lib/test/test_hmac.py 共 1600 余行通過(guò)import_fresh_module分別以“屏蔽_hashlib/_hmac”純 Python、“只加載_hmac”HACL*等方式對(duì)同一套測(cè)試向量跑三份實(shí)現(xiàn)并專門設(shè)有DigestModTestCaseMixin、HMACCompareDigestTestCase等測(cè)試類覆蓋構(gòu)造缺參、未知算法、超大密鑰bigmemtest、_4G相關(guān)用例、digestmod缺失與非法值等異常路徑。若需進(jìn)一步探究相關(guān)主題可繼續(xù)閱讀Lib/hmac.py模塊完整實(shí)現(xiàn)與三層分派邏輯Modules/_hashopenssl.cOpenSSL 加速路徑hmac_digest/hmac_new/compare_digestModules/hmacmodule.cHACL* 內(nèi)置 HMAC 實(shí)現(xiàn)Lib/test/test_hmac.pyRFC 2202/4231 與 NIST 測(cè)試向量及全部行為測(cè)試Doc/library/hashlib.rst底層安全哈希函數(shù)模塊文檔。綜上hmac模塊以極小的 API 面提供了符合 RFC 2104 標(biāo)準(zhǔn)的消息認(rèn)證能力其正確用法可概括為三句話用new/HMAC做增量計(jì)算、用digest做單發(fā)快算、用compare_digest做驗(yàn)簽比對(duì)。在此基礎(chǔ)上理解其 OpenSSL → HACL* → 純 Python 的三級(jí)實(shí)現(xiàn)能幫助你在部署到無(wú) OpenSSL 的受限平臺(tái)時(shí)依然對(duì)安全性與行為邊界心中有數(shù)?!久赓M(fèi)下載鏈接】cpythonThe Python programming language項(xiàng)目地址: https://gitcode.com/GitHub_Trending/cp/cpython創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考