HTTP请求结合HMAC增加安全性

前言

本文主要是分享一个挺不错的,可行的,结合 hmac 增加 http 请求安全性的方案。

🍃 小贴士: HMAC 之前我写过文章了,不了解的小伙伴可以去看看。

小目标:看完之后要能用自己的语言描述一遍 HMAC 签名方案的设计思路。

正文

问题背景

在传统的 HTTP 请求中,我们常常面临这样的安全挑战问题:

  • 数据篡改:攻击者可以在传输过程中修改请求内容
  • 重放攻击:合法的请求被恶意重复发送
  • 身份伪造:无法确认请求的真正来源

虽然 HTTPS 解决了传输过程中的加密问题,但它无法防止【重放攻击】,也无法验证业务数据的【完整性】。

🤔 为什么?

✍️ 因为 HTTPS 只保障传输过程的安全,而【重放攻击】和【业务数据完整性】是针对已接收的合法报文进行的攻击,需要在应用层通过时序戳、流水号、数字签名等额外机制来防护。

补充知识

在网络通信的七层或四层(TCP/IP)模型中,应用层位于最顶层,直接为用户的应用程序提供网络服务(如 HTTP、API 接口),其下方是负责可靠传输的传输层(如 TCP/TLS)和负责寻址路由的网络层等。

HTTPS 是在传输层之上对通信管道进行加密和身份认证,它确保了数据在传输过程中不被窃听或篡改。然而,一旦数据被目标服务器正确接收,HTTPS 的使命就结束了,它无法辨别一个被完整接收的请求是来自用户的"一次合法操作",还是攻击者截获后重放的重复请求,也无法验证业务逻辑参数(如订单金额、用户 ID)在应用层逻辑中是否被恶意篡改。

因此,必须在应用层设计额外的安全机制,如签名、时效验证和非重复令牌,来防御此类针对业务逻辑的攻击。

HMAC 签名方案设计

下面介绍一个基于 【HMAC】 的 HTTP 请求签名方案,能有效解决上述问题:

核心思路

每个 HTTP 请求都携带一个基于请求内容和共享密钥计算出的 HMAC 签名,服务端通过验证签名来确保请求的合法性和完整性。

签名生成步骤

  1. 准备签名要素

    • 时间戳(防止重放攻击)
    • 随机数(进一步增强唯一性)
    • 请求方法(GET/POST/PUT/DELETE)
    • 请求路径
    • 请求参数(URL 参数 + Body 参数)
    • 共享密钥(服务端和客户端预先约定)
  2. 构造待签名字符串

    text 复制代码
    待签名字符串 = 时间戳 + "|" + 随机数 + "|" + 请求方法 + "|" + 请求路径 + "|" + 排序后的参数字符串
  3. 计算 HMAC 签名

    javascript 复制代码
    signature = crypto.createHmac('sha256', secretKey).update(待签名字符串).digest('hex')

请求头设置

将签名相关信息放入 HTTP 头中:

http 复制代码
X-Auth-Timestamp: 1697012400000
X-Auth-Nonce: abc123def456
X-Auth-Signature: 7a8f9b3c4d5e6f...

服务端验证流程

服务端收到请求后的验证过程:

  1. 基础检查

    • 检查时间戳是否在允许的时间窗口内(如 ±5 分钟)
    • 检查随机数是否在近期使用过(防止重放),这个一般如何实现呢?一个可行的操作:服务端维护一个最近使用的随机数缓存,每次验证时检查随机数是否已存在于缓存中。如果存在,拒绝请求;如果不存在,将随机数加入缓存。
  2. 重新计算签名

    • 按照相同的规则构造待签名字符串
    • 使用相同的密钥计算 HMAC
  3. 签名比对

    • 比较计算出的签名与请求头中的签名是否一致
    • 一致则通过验证,不一致则拒绝请求

实际应用示例

以下是一个 Node.js 的简单实现:

javascript 复制代码
// 客户端签名生成
function generateSignature(secretKey, method, path, params, timestamp, nonce) {
    // 对参数按key排序并序列化
    const sortedParams = Object.keys(params)
        .sort()
        .map(key => `${key}=${params[key]}`)
        .join('&')

    const stringToSign = `${timestamp}|${nonce}|${method}|${path}|${sortedParams}`

    return crypto.createHmac('sha256', secretKey).update(stringToSign).digest('hex')
}

// 服务端验证
function verifySignature(request, secretKey) {
    const {headers, method, path, query, body} = request
    const timestamp = headers['x-auth-timestamp']
    const nonce = headers['x-auth-nonce']
    const receivedSignature = headers['x-auth-signature']

    // 检查时间窗口
    if (Math.abs(Date.now() - parseInt(timestamp)) > 5 * 60 * 1000) {
        return false // 超过5分钟
    }

    // 检查随机数是否重复(需要维护一个近期nonce缓存)
    if (nonceCache.has(nonce)) {
        return false
    }
    nonceCache.add(nonce)

    // 合并所有参数
    const allParams = {...query, ...body}
    const calculatedSignature = generateSignature(secretKey, method, path, allParams, timestamp, nonce)

    return crypto.timingSafeEqual(Buffer.from(calculatedSignature, 'hex'), Buffer.from(receivedSignature, 'hex'))
}

这个方案的优势

  1. 防篡改:任何对请求内容的修改都会导致签名验证失败
  2. 防重放:时间戳 + 随机数机制有效防止请求被重复使用
  3. 身份认证:只有拥有正确密钥的客户端才能生成有效签名
  4. 性能高效:HMAC 计算相比非对称加密性能更好
  5. 易于实现:方案简单,各种编程语言都有现成的 HMAC 库

注意事项

  • 密钥管理:密钥需要安全存储和定期轮换
  • 时间同步:确保客户端和服务端时间基本同步
  • 随机数存储:服务端需要维护一个短期的随机数缓存
  • 敏感信息:即使有签名保护,敏感数据仍应通过 HTTPS 传输

最后

这个 HMAC 签名方案在实际项目中表现相当不错,既保证了安全性,又不会对性能造成太大影响。特别适合用在内部 API、微服务间调用等需要较强身份认证和防篡改的场景。

当然,没有绝对的安全方案,安全不是一劳永逸的事情,世界上没有绝对安全的系统,只能通过不断的完善和升级来提高安全等级。

这边回答下文章开头的目标:用自己的语言描述一遍 HMAC 签名方案的设计思路。

【✍️ 回答】HMAC 签名方案通过在请求中包含基于请求内容和共享密钥计算出的签名,服务端通过验证签名来确保请求的合法性和完整性。

相关链接

相关推荐
想用offer打牌12 小时前
MCP (Model Context Protocol) 技术理解 - 第二篇
后端·aigc·mcp
崔庆才丨静觅12 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
KYGALYX13 小时前
服务异步通信
开发语言·后端·微服务·ruby
Hello.Reader13 小时前
Flink ZooKeeper HA 实战原理、必配项、Kerberos、安全与稳定性调优
安全·zookeeper·flink
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅13 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法14 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
智驱力人工智能14 小时前
小区高空抛物AI实时预警方案 筑牢社区头顶安全的实践 高空抛物检测 高空抛物监控安装教程 高空抛物误报率优化方案 高空抛物监控案例分享
人工智能·深度学习·opencv·算法·安全·yolo·边缘计算