如何在 Python 中安全验证 HMAC 签名

老宇酱_3819

老宇酱_3819

2026-07-04

296人浏览

原创

本文详解 Python 中 HMAC 签名验证的正确实践:强调使用原始字节载荷、避免 JSON 序列化或字符串拼接导致的格式偏差,并推荐使用 hmac.compare_digest() 防御定时攻击。

本文详解 python 中 hmac 签名验证的正确实践:强调使用原始字节载荷、避免 json 序列化或字符串拼接导致的格式偏差,并推荐使用 `hmac.compare_digest()` 防御定时攻击。

在 Webhook 安全验证场景中,HMAC(Hash-based Message Authentication Code)是验证请求来源真实性与数据完整性的核心机制。许多开发者(如 Sumsub、Plaid 等平台)会在 HTTP 请求头中携带 X-Signature,并要求服务端用预共享密钥(secret key)对原始请求体(raw payload) 重新计算 HMAC,再比对结果。常见失败原因并非算法错误,而是载荷预处理方式不一致——例如手动拼接键值对、误用 json.dumps()、或对布尔值/空格/换行符等做非标准转换。

✅ 正确做法:直接使用原始字节流(raw bytes)计算 HMAC
Webhook 的真实 payload 是服务器发送的原始二进制数据(如 b'{\n "applicantId": "...", "sandboxMode": true\n}'),而非 Python 字典对象。任何中间转换(如 json.dumps(payload)、','.join(...) 或 .lower() 处理布尔值)都会引入格式差异,导致签名不匹配。

以下为生产就绪的验证函数示例:

Shadows Python Sensei
Shadows Python Sensei

Python 最佳实践助手——代码规范、设计模式、性能优化、测试与类型注解。适用于编写或审查 Python 代码。

下载
import hmac
import hashlib
from flask import request  # 示例基于 Flask;其他框架请替换为对应获取 raw body 的方式

def validate_webhook_signature(
    secret_key: str,
    signature_header: str = "X-Signature",
    algorithm_header: str = "X-Payload-Digest-Alg",
    allowed_algorithms: dict = None
) -> bool:
    """
    验证 Webhook 请求的 HMAC 签名(防御定时攻击)

    :param secret_key: 预共享密钥(字符串)
    :param signature_header: 包含期望签名的请求头名
    :param algorithm_header: 指定哈希算法的请求头名(如 'HMAC_SHA256_HEX')
    :param allowed_algorithms: 算法映射字典,键为 header 值,值为 hashlib 函数
    :return: 签名有效返回 True,否则抛出异常
    """
    if allowed_algorithms is None:
        allowed_algorithms = {
            "HMAC_SHA1_HEX": hashlib.sha1,
            "HMAC_SHA256_HEX": hashlib.sha256,
            "HMAC_SHA512_HEX": hashlib.sha512,
        }

    # 1. 获取算法标识
    algo_name = request.headers.get(algorithm_header)
    if not algo_name or algo_name not in allowed_algorithms:
        raise ValueError(f"Unsupported or missing digest algorithm: {algo_name}")

    hash_func = allowed_algorithms[algo_name]

    # 2. 获取原始请求体(关键!必须是 bytes)
    # ✅ 正确:获取未解码的原始字节(保留换行、空格、大小写等所有细节)
    raw_payload = request.get_data()  # type: bytes
    if not raw_payload:
        raise ValueError("Empty payload received")

    # 3. 计算 HMAC(使用 bytes 密钥 + raw_payload bytes)
    key_bytes = secret_key.encode('utf-8')
    computed_hmac = hmac.new(key_bytes, raw_payload, hash_func).hexdigest()

    # 4. 获取请求头中的签名(通常为 hex 字符串)
    expected_signature = request.headers.get(signature_header)
    if not expected_signature:
        raise ValueError(f"Missing required header: {signature_header}")

    # 5. ✅ 安全比对:使用 hmac.compare_digest() 防御定时攻击
    if not hmac.compare_digest(computed_hmac, expected_signature):
        # 调试时可临时启用(上线前务必关闭)
        # print(f"[DEBUG] Expected: {expected_signature!r}")
        # print(f"[DEBUG] Computed: {computed_hmac!r}")
        # print(f"[DEBUG] Payload (first 100 chars): {raw_payload[:100]!r}")
        raise ValueError("HMAC signature validation failed")

    return True

# 使用示例(Flask 视图中)
@app.route('/webhook', methods=['POST'])
def handle_webhook():
    try:
        validate_webhook_signature(secret_key="TZUQlLdW-E5VM7nbcByTbyQx9G_")
        # ✅ 签名已通过验证,现在可安全解析 JSON
        payload = request.get_json()
        # ... 处理业务逻辑
        return {"status": "ok"}, 200
    except ValueError as e:
        return {"error": str(e)}, 400

⚠️ 关键注意事项:

  • 绝不手动构造 payload 字符串:json.dumps(payload) 默认无空格、sort_keys=False,而实际请求可能含缩进、换行或不同布尔字面量(true vs True)。在线工具显示的 "sandboxMode": true 是 JSON 格式规范写法,Python json.dumps() 输出 true(小写),但若原始请求含 True(首字母大写)或引号包裹("true"),则完全不兼容——因此唯一可靠输入是原始 request.get_data() 字节流。
  • 编码一致性:secret_key.encode('utf-8') 和 request.get_data() 均为 bytes,无需额外 decode/encode。若误用 request.get_data(as_text=True) 再 encode,可能因系统默认编码或 BOM 引入不可见差异。
  • 安全比对必须用 hmac.compare_digest():普通 == 比较在遇到前缀匹配时会提前退出,攻击者可通过响应时间差异推断签名字符,compare_digest() 保证恒定时间执行。
  • 验证通过后再解析:仅在 HMAC 校验成功后,才调用 request.get_json() 解析 JSON,防止恶意篡改的无效 JSON 引发异常或 DoS。

总结:HMAC 验证的本质是「比特级一致性校验」。保持载荷字节原样、密钥字节原样、哈希算法一致,即可 100% 复现签名。任何试图“标准化” payload 文本格式的尝试,都是偏离协议的危险操作。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

python

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1571

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

3764

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1589

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

21557

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2667

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

2707

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1103

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

576

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2103

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程