算法HMAC的使用步骤

1 简介

本文详细分析HMAC通过密码和订单数据生成签名的步骤,并展示使用go实现一个例子展示HMAC如何计算和校验的

2 HMAC(Hash-based Message Authentication Code)概述

HMAC 是基于哈希算法的一种消息认证码,它通过一个密钥和一个消息(数据)来生成一个签名(哈希值)。该签名是消息内容的“指纹”,可以确保消息的完整性和身份验证。只有知道密钥的双方才能计算和验证签名。

HMAC的基本计算步骤如下:

使用一个密钥和消息数据结合起来。

通过哈希算法(如 SHA-256)计算一个哈希值。

得到的哈希值就是HMAC签名。

接收方用相同的密钥对消息重新计算HMAC签名,并与发送方提供的签名进行对比,若匹配则验证通过。

3 HMAC 生成签名的步骤

准备数据:消息和密钥。

生成密钥的内外填充(Inner and Outer padding):HMAC算法会使用特定的填充方式,将密钥填充成一个固定长度的值,以适应哈希算法。

计算HMAC:首先计算内层哈希,然后计算外层哈希,最终得到HMAC签名。

HMAC 计算流程:

对消息 message 和密钥 key 进行组合。

使用 SHA-256 等哈希算法生成消息的哈希值。

计算内外两次哈希的结果,生成最终的签名。

4 实现 HMAC 签名计算和验证

下面是一个使用 Go 语言实现的例子,展示如何生成和验证 HMAC 签名。

代码实现

package main

import (

"crypto/hmac"

"crypto/sha256"

"encoding/base64"

"fmt"

"log"

)

// 生成 HMAC 签名

func generateHMACSignature(message, secret string) (string, error) {

// 创建一个 HMAC 对象,使用 SHA-256 哈希算法和 secret 密钥

mac := hmac.New(sha256.New, []byte(secret))

// 写入消息内容

_, err := mac.Write([]byte(message))

if err != nil {

return "", err

}

// 计算哈希并将其编码为 Base64 字符串

signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

return signature, nil

}

// 验证 HMAC 签名

func verifyHMACSignature(message, signature, secret string) bool {

// 使用相同的密钥生成签名

expectedSignature, err := generateHMACSignature(message, secret)

if err != nil {

log.Println("Error generating signature:", err)

return false

}

// 将生成的签名与传入的签名进行比较

return hmac.Equal([]byte(expectedSignature), []byte(signature))

}

func main() {

// 示例消息和密钥

message := "OrderID=12345&Amount=199.99&CustomerID=67890"

secret := "supersecretkey"

// 生成 HMAC 签名

signature, err := generateHMACSignature(message, secret)

if err != nil {

log.Fatalf("Error generating HMAC signature: %v", err)

}

// 打印生成的签名

fmt.Println("Generated HMAC Signature:", signature)

// 验证 HMAC 签名

valid := verifyHMACSignature(message, signature, secret)

if valid {

fmt.Println("HMAC Signature is valid!")

} else {

fmt.Println("HMAC Signature is invalid!")

}

// 模拟一个错误的签名

invalidSignature := "invalid_signature"

invalid := verifyHMACSignature(message, invalidSignature, secret)

if invalid {

fmt.Println("Invalid signature was accepted, this should not happen!")

} else {

fmt.Println("Correctly rejected invalid signature.")

}

}

代码解析

生成HMAC签名(generateHMACSignature 函数)

输入:消息 (message) 和密钥 (secret)。

创建 HMAC 对象:使用 Go 内置的 hmac.New 函数与指定的哈希算法(这里使用 SHA-256)以及密钥来创建一个 HMAC 对象。

写入消息:使用 mac.Write 方法将消息写入 HMAC 对象。

计算 HMAC 签名:使用 mac.Sum(nil) 计算最终的 HMAC 签名并返回。

返回 Base64 编码:HMAC 签名是一个二进制数据,我们将其编码为 Base64 格式,以便于传输或存储。

验证HMAC签名(verifyHMACSignature 函数)

输入:消息 (message)、签名 (signature) 和密钥 (secret)。

重新生成签名:使用相同的消息和密钥重新计算 HMAC 签名。

比较签名:将生成的签名与接收到的签名进行比较,若一致则验证通过,返回 true,否则返回 false。

示例输出

生成一个签名并打印出来:

Generated HMAC Signature: cXkdVxGeK9S+PjV9phkT+7eb9Gg2RlR3zKX4BvIHqys=

验证签名是否有效:

HMAC Signature is valid!

模拟传入错误签名并验证:

Correctly rejected invalid signature.

HMAC如何保障不可抵赖性

密钥绑定签名:

HMAC签名使用了密钥(用户的密码或共享密钥)和消息(订单数据)进行计算。签名和数据是紧密绑定的,无法单独伪造签名而不改变数据,也无法篡改数据而不使签名失效。

用户身份验证:

因为签名的生成和验证依赖于一个私密的密钥,只有持有正确密钥的用户(或系统)才能计算正确的签名,因此系统能够确认是该用户(或系统)执行了操作,从而防止了用户否认的行为。

HMAC如何避免重放攻击

时间戳或唯一标识符:

为了避免重放攻击,HMAC通常会与时间戳或唯一标识符(如订单ID)一起使用。这使得每次请求都有一个唯一的签名,即使是相同的数据,由于时间戳的不同,每次生成的签名也不同。

验证时间有效性:

服务器可以根据时间戳判断请求是否在有效时间窗口内(例如5分钟)。如果时间戳过旧,则拒绝该请求,防止攻击者重放旧请求。

5 总结

HMAC通过结合消息内容和密钥生成一个唯一的签名,使得只有持有正确密钥的用户才能生成有效签名,从而保证了消息的完整性和不可抵赖性。

同时,通过结合时间戳和签名,HMAC可以防止重放攻击,因为每个签名都会与时间或唯一标识符绑定,避免重复使用相同的签名。