解决跨语言HMAC签名验证不一致:JSON字符串化差异与标准化实践

解决跨语言HMAC签名验证不一致:JSON字符串化差异与标准化实践

本文深入探讨了在跨语言(如pythontypescript)进行hmac签名验证时,因json字符串化方式差异导致验证失败的常见问题。文章详细分析了问题根源,并提供了一套基于typescript的健壮解决方案,通过标准化json对象的字符串表示,确保了签名数据在不同语言环境下的完全一致性,从而实现可靠的hmac验证。

引言

HMAC(Keyed-Hash Message Authentication Code)签名验证是确保API请求或Webhook数据完整性和真实性的重要安全机制。它通过共享密钥对数据进行哈希计算,生成一个签名,接收方使用相同的密钥和数据再次计算签名,并与发送方提供的签名进行比对。然而,在实际开发中,当发送方和接收方使用不同编程语言实现HMAC验证时,一个常见的陷阱是由于数据序列化(特别是JSON字符串化)方式的细微差异,导致签名验证失败。本文将以Python和TypeScript为例,详细剖析这一问题并提供一套通用的解决方案。

HMAC签名验证基础

HMAC签名的基本流程如下:

共享密钥(Secret Key):发送方和接收方预先共享一个秘密密钥。待签名数据(Data to Sign):将要验证的数据(通常包括时间戳和请求体等)拼接成一个字符串。哈希算法(Hashing Algorithm):选择一个加密哈希算法(如SHA256)。计算签名:使用共享密钥、待签名数据和哈希算法计算HMAC值。比较签名:接收方使用相同的方法计算签名,并与发送方提供的签名进行安全比较。

以下是Python中一个典型的HMAC签名验证函数示例:

import hmacimport hashlibimport jsondef verify_signature(key, timestamp, provided_signature, payload):  key_bytes = bytes.fromhex(key)  payload_str = json.dumps(payload) # JSON字符串化  data = timestamp + payload_str  signature = hmac.new(key_bytes, data.encode('utf-8'),                       hashlib.sha256).hexdigest()  valid = hmac.compare_digest(provided_signature, signature)  return valid

跨语言验证的陷阱:JSON字符串化差异

问题通常出现在“待签名数据”的构建环节,特别是当数据中包含JSON对象时。Python的json.dumps()和TypeScript(或JavaScript)的JSON.stringify()函数在将JSON对象转换为字符串时,其默认行为可能存在差异,例如:

空格和换行符: 默认情况下,JSON.stringify()会生成紧凑的字符串,不包含多余的空格或换行符。而json.dumps()在某些情况下或不指定separators参数时,可能会在键值对之间、数组元素之间或对象属性之间包含空格。键的顺序: 虽然JSON规范不保证对象的键顺序,但在实践中,不同的JSON库或版本在序列化时可能会以不同的顺序输出键,这会导致最终字符串不同。

即使是单个空格或换行符的差异,也会导致待签名数据字符串完全不同,进而产生不同的HMAC签名,最终导致验证失败。

例如,一个初始的TypeScript尝试可能如下:

import * as crypto from 'crypto';export function verifyCloseSignature(  request: Request,  key: string,  payload: any,) {  const headers = request.headers;  const timestamp = headers.get('close-sig-timestamp');  const providedSignature = headers.get('close-sig-hash');  if (!timestamp || !providedSignature) {    throw new Error('[verifyCloseSignature] Required headers missing');  }  const payloadString = JSON.stringify(payload); // 默认紧凑字符串  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));  hmac.update(timestamp + payloadString);  const calculatedSignature = hmac.digest('hex');  return crypto.timingSafeEqual(    Buffer.from(providedSignature, 'hex'),    Buffer.from(calculatedSignature, 'hex'),  );}

这段TypeScript代码与Python的verify_signature函数在处理payload的字符串化时存在差异,可能导致计算出的签名与Python端不一致。

标准化JSON字符串以确保一致性

解决此问题的关键在于确保发送方和接收方在将JSON对象转换为字符串时,其结果字符串是完全一致的。这意味着TypeScript需要精确地模拟Python端json.dumps()所产生的字符串格式。

根据经验,许多系统在序列化JSON时会采用一种“相对紧凑但保留特定空格”的格式,例如在冒号后保留一个空格,在逗号后保留一个空格,但移除其他不必要的空格和换行。

我们可以创建一个辅助函数来标准化JSON对象的字符串表示。以下是一个使用Ramda库进行函数式编程和正则表达式替换的TypeScript实现:

import { pipe, replace } from 'ramda'; // 假设已安装ramda/** * 将JSON对象标准化为特定格式的字符串。 * 该函数旨在模拟Python json.dumps()在特定场景下产生的字符串格式, * 确保在冒号和逗号后有一个空格,移除其他不必要的空格和换行。 * @param object 待字符串化的JSON对象。 * @returns 标准化后的JSON字符串。 */export const toJSONWithSpaces = pipe(  // 1. 使用 null, 1 参数进行初步字符串化,产生带换行和缩进的字符串  (object: unknown) => JSON.stringify(object, null, 1),  // 2. 将换行符和其后的空格替换为单个空格  replace(/n +/gm, ' '),  // 3. 确保冒号后有一个空格  replace(/:s?/g, ': '), // 匹配冒号后可能有或没有的空格,替换为冒号后一个空格  // 4. 移除开括号后的空格  replace(/{s?/g, '{'),  // 5. 移除闭括号前的空格  replace(/s?}/g, '}'),  // 6. 移除开方括号后的空格  replace(/[s?/g, '['),  // 7. 移除闭方括号前的空格  replace(/s?]/g, ']'),  // 8. 确保逗号后有一个空格  replace(/,s?/g, ', '),);

这个toJSONWithSpaces函数通过一系列的正则表达式替换,将JSON.stringify(object, null, 1)(带缩进和换行)的输出转换为一个精确控制空格的字符串。它确保了在冒号和逗号后有一个空格,同时移除了其他地方的空格,以匹配Python端可能产生的特定格式。

完整的TypeScript签名验证实现

将上述toJSONWithSpaces函数集成到签名验证逻辑中,可以得到一个健壮的TypeScript验证函数:

import * as crypto from 'crypto';import { toJSONWithSpaces } from './utils'; // 假设toJSONWithSpaces在utils.ts中/** * 验证Close API或Webhook签名。 * @param request Express或其他框架的请求对象,包含headers。 * @param key 用于HMAC计算的密钥(十六进制字符串)。 * @param payload 请求体中的JSON数据。 * @returns 签名是否有效。 * @throws 如果缺少必要的签名头部信息。 */export function verifyCloseSignature(  request: Request, // 或 Express.Request 等  key: string,  payload: any,) {  const headers = request.headers;  const timestamp = headers.get('close-sig-timestamp');  const providedSignature = headers.get('close-sig-hash');  if (!timestamp) {    throw new Error('[verifyCloseSignature] Required timestamp header missing');  }  if (!providedSignature) {    throw new Error('[verifyCloseSignature] Required signature header missing');  }  // 使用标准化函数处理payload,确保字符串格式与Python端一致  const cleanedPayload = toJSONWithSpaces(payload);  const hmac = crypto.createHmac('sha256', Buffer.from(key, 'hex'));  hmac.update(timestamp + cleanedPayload); // 使用标准化的payload  const calculatedSignature = hmac.digest('hex');  // 使用crypto.timingSafeEqual进行安全比较,防止时序攻击  return crypto.timingSafeEqual(    Buffer.from(providedSignature, 'hex'),    Buffer.from(calculatedSignature, 'hex'),  );}

通过使用toJSONWithSpaces处理payload,我们确保了timestamp + cleanedPayload这个待签名数据字符串在Python和TypeScript两端是完全相同的,从而解决了HMAC签名验证不一致的问题。

注意事项

JSON键顺序: 尽管上述解决方案主要关注空格,但某些JSON库在序列化时可能会以不同的顺序输出对象键。如果源系统(例如Python)的json.dumps在不指定sort_keys=True的情况下产生了特定但非标准顺序的键,那么目标系统(TypeScript)也需要能够以相同顺序序列化。在大多数情况下,如果源系统是确定性的,并且目标系统也通过某种方式(如先对键进行排序再字符串化)来保证确定性,则可以避免此问题。本教程中的toJSONWithSpaces不直接处理键排序,但其目的是匹配一个已知的Python输出格式。安全性: 在比较签名时,务必使用crypto.timingSafeEqual(Node.js)或类似的安全比较函数。直接使用===或==进行字符串比较可能会存在时序攻击的风险。依赖管理: 上述toJSONWithSpaces函数使用了Ramda库。在实际项目中,你需要安装ramda (npm install ramda 或 yarn add ramda)。如果不想引入外部库,也可以使用纯JavaScript/TypeScript实现类似的字符串替换逻辑。错误处理: 确保对缺失的必要头部信息(如时间戳和签名)进行适当的错误检查和处理。时间戳: 时间戳通常也作为待签名数据的一部分,其格式和精度也需要保持一致。

总结

跨语言HMAC签名验证中的不一致问题,往往源于数据序列化过程中的细微差异,尤其是JSON字符串化时对空格、换行符和键顺序的处理。解决之道在于精确地标准化待签名数据的字符串表示,确保发送方和接收方在计算HMAC时使用完全相同的原始数据。通过本教程提供的TypeScript标准化JSON字符串的实践,开发者可以构建出更加健壮和可靠的跨语言安全验证机制。

以上就是解决跨语言HMAC签名验证不一致:JSON字符串化差异与标准化实践的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1539137.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
MongoDB聚合怎么使用_MongoDB聚合管道功能与JS全栈数据处理教程
上一篇 2025年12月21日 05:43:10
js脚本如何制作随机数生成器_js随机数功能脚本编写与实例
下一篇 2025年12月21日 05:43:29

相关推荐

  • 为什么VSCode的语法高亮有时会失效?

    语法高亮失效通常由语言模式识别错误、扩展冲突或配置问题导致。1. 检查右下角语言模式并手动切换为正确类型,确保文件有正确扩展名;2. 禁用近期安装的扩展或以 code –disable-extensions 启动排查冲突;3. 切换至默认主题并检查 settings.json 是否覆盖颜…

    2026年9月21日
    500
  • 如何为VSCode配置一个高效的PHP开发环境?

    搭建高效PHP开发环境需配置VSCode扩展与工具链:①安装PHP Intelephense实现智能补全;②配置Xdebug实现断点调试;③集成PHP CS Fixer或Prettier实现保存时自动格式化;④利用GitLens和集成终端提升协作与操作效率,一次性配置可长期提升编码质量与开发速度。 …

    2026年9月21日
    000
  • 在Java中变量和常量有什么区别

    变量的值可修改,常量(用final修饰)一旦赋值不可变;变量用于动态数据,常量用于固定值,如PI或配置参数。 在Java中,变量和常量的主要区别在于它们的值能否被修改。变量的值可以在程序运行过程中改变,而常量一旦赋值就不能再更改。 变量(Variable) 变量是用于存储数据的基本单元,其值在程序执…

    2026年9月21日
    100
  • VSCode怎么编译运行视频_VSCode处理视频资源的扩展与操作指南

    VSCode通过扩展和外部工具支持视频处理。推荐使用Code Runner或ffmpeg-kit扩展运行FFmpeg命令,或结合Python(MoviePy/OpenCV)、Node.js(fluent-ffmpeg)等编程方式实现视频格式转换、裁剪等操作,具体工具选择取决于技能栈和需求。 VSCo…

    2026年9月21日
    100
  • Linux如何限制用户执行特定命令

    Linux如何限制用户执行特定命令Linux如何限制用户执行特定命令Linux如何限制用户执行特定命令Linux如何限制用户执行特定命令

    首选sudo进行命令限制,因其灵活且可审计;通过visudo配置精确的用户权限,结合白名单、命令别名和!语法实现允许或拒绝特定命令;同时防范绕过手段如全路径执行、间接调用、脚本执行等,需多层防御并辅以日志监控。 在Linux环境中,限制用户执行特定命令,最直接有效且灵活的方法通常是利用 sudo 权…

    2026年9月21日 用户投稿
    000
  • 在Java中如何使用方法重载

    方法重载允许类中多个同名方法共存,只要参数列表不同即可。例如Calculator类中add方法可接受不同数量、类型或顺序的参数,Java根据传入参数自动匹配对应方法,提升调用灵活性与代码可读性。 方法重载(Overloading)是Java中实现多态的一种方式,它允许在一个类中定义多个同名方法,只要…

    2026年9月21日
    200
  • VSCode的括号着色功能如何帮助你避免语法错误?

    VSCode括号着色功能通过彩色高亮匹配括号,帮助用户直观识别嵌套结构、提升代码可读性,并快速发现遗漏或多余括号,减少语法错误。 VSCode的括号着色功能通过视觉方式帮你快速识别代码中的匹配和嵌套结构,减少语法错误的发生。当你在编写代码时,成对出现的括号(如()、[]、{})会被高亮显示为相同或相…

    2026年9月21日
    000
  • Linux如何创建符号链接和硬链接

    Linux如何创建符号链接和硬链接Linux如何创建符号链接和硬链接Linux如何创建符号链接和硬链接Linux如何创建符号链接和硬链接

    符号链接是快捷方式,指向文件或目录路径,原文件删除后链接失效;2. 硬链接共享同一inode,不能跨文件系统或链接目录;3. 使用ln -s创建符号链接,ln创建硬链接;4. 符号链接可跨分区,硬链接删除原文件后仍可访问数据。 在Linux中,创建符号链接(软链接)和硬链接是管理文件和目录的常用操作…

    2026年9月21日 用户投稿
    000
  • Java中如何将嵌套列表对象转换为扁平化单元素列表

    本文探讨了在java中将包含嵌套列表的对象集合转换为新列表的多种策略,旨在使新列表中每个对象仅包含其嵌套列表中的一个元素。通过详细介绍java 7的传统迭代方法、java 8-15的stream api `flatmap`操作,以及java 16及更高版本的`mapmulti`方法,文章提供了清晰的…

    2026年9月21日
    100
  • Linux如何查看sudo执行的历史记录

    Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录Linux如何查看sudo执行的历史记录

    要追溯sudo执行的命令,需查看系统日志或配置sudo日志;在Ubuntu/Debian中查/var/log/auth.log,CentOS/RHEL中查/var/log/secure,或使用journalctl _COMM=sudo筛选;通过配置/etc/sudoers中的Defaults log…

    2026年9月21日 用户投稿
    300
  • 如何为VSCode安装新的字体?

    先在操作系统安装字体文件,再在VSCode设置中指定字体名称。1. Windows右键安装.ttf/.otf文件,macOS用字体册安装,Linux复制到~/.fonts并运行fc-cache -fv;2. VSCode中通过设置界面或编辑settings.json修改”editor.f…

    2026年9月21日
    000
  • 如何制作抖音点单小程序:全面指南与实用技巧

    引言: 随着移动互联网的飞速发展,抖音已不仅仅是短视频平台,更成为商家连接用户的重要入口。越来越多企业开始关注抖音点单小程序的搭建,以提升服务效率和用户体验。本文将为您系统讲解抖音点单小程序的制作流程,并分享实用技巧与真实案例,助您快速打造专属的小程序,实现流量变现与销售增长。 1. 明确核心需求与…

    2026年9月21日
    200
  • 从 API 响应中提取元素并在 Java 中使用

    本文介绍了如何在 Java 中解析 API 响应,并从中提取特定元素的值。以 JSON 格式的响应为例,演示了如何使用 Jackson 库将 JSON 字符串转换为 Java 对象,并提取所需的数据,例如账户 ID,以便在后续操作中使用。 在 Java 开发中,经常需要与 API 进行交互,并从 A…

    2026年9月21日
    100
  • 访问DeepSeek官方网站 deepseek在线版免费登录

    答案:DeepSeek在线版免费登录入口位于官网https://chat.deepseek.com/sign_in,用户可通过手机号验证码或微信授权登录,新用户免注册,登录后自动创建账户并同步多端数据,支持网页和APP使用。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 De…

    2026年9月21日
    100
  • 如何在Java中配置系统环境变量以运行程序

    正确配置Java环境变量是运行Java程序的前提。1. 安装JDK并记住安装路径,如Windows下为C:Program FilesJavajdk-17,macOS/Linux下为/usr/lib/jvm/jdk-17。2. 设置JAVA_HOME环境变量:Windows在系统变量中新建JAVA_H…

    2026年9月21日
    100
  • MySQL如何高效存储时间日期数据_时区和格式问题处理?

    MySQL如何高效存储时间日期数据_时区和格式问题处理?MySQL如何高效存储时间日期数据_时区和格式问题处理?MySQL如何高效存储时间日期数据_时区和格式问题处理?MySQL如何高效存储时间日期数据_时区和格式问题处理?

    核心策略是统一存储utc时间并由应用层处理时区转换与格式化。1.timestamp适合跨时区场景,自动转换utc且节省空间;2.datetime适合固定日期事件,不随时区变化;3.写入前应用层转utc,读取后转用户本地时间;4.格式化应在应用层完成以提升性能与灵活性;5.避免字符串存储时间,优先使用…

    2026年9月21日 用户投稿
    100
  • Linux系统信息查看命令整理

    答案:掌握Linux系统需从系统信息、资源使用、性能瓶颈、日志分析和用户权限五方面入手。uname、lscpu、free、df、ip、ss等命令用于查看系统软硬件状态;top、htop、vmstat、iostat、iftop等可诊断CPU、内存、磁盘、网络性能瓶颈;/var/log日志文件结合jou…

    2026年9月20日
    100
  • 如何在Java中声明常量数组

    声明常量数组需用static final,但final仅保证引用不可变而非内容不可变。1. 基本类型数组可用static final声明,如public static final int[] DAYS_IN_MONTH = {31,28,…};引用不可改,但元素可修改。2. 为实现内容不…

    2026年9月20日
    100
  • 怎么在VSCode里配置Go语言环境?

    安装Go并配置环境变量后,在VSCode中安装官方Go扩展,通过命令面板安装gopls、delve等必要工具,并设置保存时自动格式化与导入,即可实现代码补全、格式化和调试功能。 在 VSCode 中配置 Go 语言开发环境其实不复杂,只要安装好工具链并正确设置,就能获得代码补全、格式化、调试等完整功…

    2026年9月20日
    100
  • Java从文本文件随机读取并打印指定行数内容

    本文旨在指导读者如何使用java程序从文本文件中高效地读取多组固定行数的内容(如诗歌),并随机选择其中一组进行打印。教程将详细介绍如何利用`files.readalllines`、`random`和`list.sublist`等核心api,实现文件的整体读取、随机索引的生成以及特定内容块的提取与输出…

    2026年9月20日
    100

发表回复

登录后才能评论
关注微信