pin_drop当前位置:知识文库 ❯ 图文
Python hmac.compare_digest详解 - 恒定时间比较防时序攻击
一、概述
hmac.compare_digest() 是 hmac 模块中用于安全比较两个摘要值或字节串的函数。与普通的 == 比较不同,compare_digest() 使用恒定时间算法进行比较,无论两个值是否匹配,比较时间都相同。
这可以有效防止时序攻击(Timing Attack),即攻击者通过测量比较操作的耗时来逐步猜测正确值。在验证 HMAC 签名、密码哈希等安全敏感场景中,必须使用 compare_digest() 替代 ==。
二、语法
代码示例
import hmac
result = hmac.compare_digest(a, b)三、参数说明
四、返回值
返回布尔值:True 表示两个值相等,False 表示不等。
五、代码示例
示例1:基本安全比较
代码示例
import hmac
# 正确的签名
correct_sig = "a1b2c3d4e5f6789012345678abcdef01"
# 验证正确签名
print(f"正确签名验证: {hmac.compare_digest(correct_sig, correct_sig)}")
# 验证错误签名
wrong_sig = "b2c3d4e5f6789012345678abcdef0102"
print(f"错误签名验证: {hmac.compare_digest(correct_sig, wrong_sig)}")
# 验证空签名
print(f"空签名验证: {hmac.compare_digest('', '')}")输出:
代码示例
正确签名验证: True
错误签名验证: False
空签名验证: True示例2:HMAC签名验证流程
代码示例
import hmac
import hashlib
def create_signature(key, message):
"""创建HMAC签名"""
return hmac.new(key, message.encode('utf-8'), hashlib.sha256).hexdigest()
def verify_signature(key, message, signature):
"""安全验证HMAC签名"""
expected = create_signature(key, message)
# 使用compare_digest而非==,防止时序攻击
return hmac.compare_digest(expected, signature)
# 服务器端:创建签名
secret_key = b'server_secret_key'
message = "user_id=1001&action=transfer"
signature = create_signature(secret_key, message)
print(f"消息: {message}")
print(f"签名: {signature}")
# 验证合法请求
is_valid = verify_signature(secret_key, message, signature)
print(f"\n合法请求验证: {'通过' if is_valid else '拒绝'}")
# 验证篡改请求
tampered_message = "user_id=1001&action=delete"
is_valid2 = verify_signature(secret_key, tampered_message, signature)
print(f"篡改请求验证: {'通过' if is_valid2 else '拒绝'}")
# 验证伪造签名
fake_signature = "0" * 64
is_valid3 = verify_signature(secret_key, message, fake_signature)
print(f"伪造签名验证: {'通过' if is_valid3 else '拒绝'}")输出:
代码示例
消息: user_id=1001&action=transfer
签名: a1b2c3d4e5f6789012345678abcdef0123456789abcdef0123456789abcdef01
合法请求验证: 通过
篡改请求验证: 拒绝
伪造签名验证: 拒绝示例3:密码哈希验证
代码示例
import hmac
import hashlib
import os
def hash_password(password):
"""哈希密码"""
salt = os.urandom(16)
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
return f"{salt.hex()}:{key.hex()}"
def verify_password(password, stored_hash):
"""安全验证密码"""
salt_hex, key_hex = stored_hash.split(':')
salt = bytes.fromhex(salt_hex)
computed_key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
# 使用compare_digest安全比较,防止时序攻击
return hmac.compare_digest(computed_key.hex(), key_hex)
# 注册
stored = hash_password("MyPassword123")
print(f"存储哈希: {stored[:30]}...")
# 登录验证
print(f"\n正确密码: {verify_password('MyPassword123', stored)}")
print(f"错误密码: {verify_password('WrongPassword', stored)}")输出:
代码示例
存储哈希: 0102030405060708090a0b0c0d0e0f...
正确密码: True
错误密码: False六、实际应用场景
-
API 签名验证:验证 Web API 请求的 HMAC 签名时,使用
compare_digest()防止攻击者通过时序分析猜测签名。 -
密码验证:比较用户输入的密码哈希与存储的哈希时,使用
compare_digest()防止时序攻击。 -
令牌验证:验证 CSRF 令牌、重置密码令牌等敏感令牌时,使用
compare_digest()确保安全。
七、注意事项
注意1:
compare_digest()仅在比较摘要值时提供恒定时间保证。如果两个参数长度不同,函数会立即返回False,这是可接受的行为。
注意2:两个参数类型必须一致(同为
str或同为bytes),否则会抛出TypeError。
注意3:
compare_digest()防止的是远程时序攻击。如果攻击者无法测量比较操作的耗时(如本地比较),使用==也是安全的。
提示:在所有涉及安全敏感比较的场景中(签名验证、密码验证、令牌验证等),都应使用
compare_digest()替代==,这是安全编程的基本原则。
八、相关方法对比
九、小结
-
hmac.compare_digest()使用恒定时间算法比较两个值,防止时序攻击 -
在验证 HMAC 签名、密码哈希、令牌等安全敏感场景中必须使用
-
两个参数类型必须一致(同为 str 或 bytes)
-
比较时间不依赖值的匹配程度,攻击者无法通过耗时推断正确值
常见问题
什么是时序攻击(Timing Attack)?
时序攻击是一种侧信道攻击。普通的==比较在遇到第一个不匹配的字符时会立即返回,因此比较时间取决于从开头开始有多少字符匹配。攻击者通过大量测量比较操作的耗时,可以逐字节推断出正确的签名值。compare_digest()通过恒定时间比较消除了这种信息泄露。
compare_digest()和==的性能差异大吗?
性能差异非常小。compare_digest()略慢于==,但在绝大多数应用场景中,这个差异可以忽略不计。安全敏感场景中,安全性远比这点性能差异重要。
compare_digest()可以比较不同类型的值吗?
不可以。两个参数必须是相同类型(同为str或同为bytes),否则会抛出TypeError。如果需要比较,请先将类型转换一致。
除了hmac.compare_digest(),Python还有其他恒定时间比较方式吗?
Python 3.3+在operator模块中提供了operator.compare_digest(),功能与hmac.compare_digest()相同。此外,在Django等框架中也有类似的恒定时间比较工具。推荐统一使用hmac.compare_digest(),它是最标准的选择。
练习1
编写一个函数,使用 compare_digest() 验证 HMAC 签名,对比使用 == 验证的安全性差异。
练习2
解释时序攻击的原理:攻击者如何通过测量 == 比较的耗时来逐步猜测正确的签名值。
练习3
编写一个密码验证函数,使用 PBKDF2 计算哈希并用 compare_digest() 安全比较,实现完整的密码注册和登录流程。
本文涉及AI创作
内容由AI创作,请仔细甄别