HMAC 流程
HMAC 的核心原理步骤
hash的数据:
H(opad_key || H(ipad_key || message))
密钥处理:
- 首先准备一个密钥 K。
- 为适应哈希函数的输入块大小,会对密钥进行处理:
- 如果 K 的长度等于哈希函数的内部块大小(例如 SHA-256 是 64 字节),则直接使用 K。
- 如果 K 的长度大于内部块大小,则先将 K 用哈希函数 H 哈希一次,生成一个新的、固定长度的密钥(长度等于哈希函数输出长度),然后用 0 补齐这个新哈希值到内部块大小。
- 如果 K 的长度小于内部块大小,则在 K 后面填充 0 字节(0x00),直到其长度等于内部块大小。
处理后的结果称为 K0(填充/截断/哈希后的密钥)。
构建内部输入:
- 创建一个常量 ipad(inner padding)。ipad 是一个字节,其值为 0x36(即二进制 00110110),这个值是根据经验选择的,没有特殊数学意义。
- 将 ipad 重复到与哈希函数内部块大小相同的长度(例如 SHA-256 就是 64 个 0x36 字节)。这称为 ipad_key。
- 将处理后的密钥 K0 与 ipad_key 进行异或(XOR) 操作:K0 XOR ipad_key,得到结果 Si。
- 将待认证的原始消息 M 附加在 Si 之后:Si || M。
执行内部哈希:
- 对上一步构建的内部输入 Si || M 应用哈希函数 H:H(Si || M)。得到一个哈希结果 Hi(内部哈希结果)。
构建外部输入:
- 创建一个常量 opad(outer padding)。opad 是一个字节,其值为 0x5C(即二进制 01011100),同样是根据经验选择的。
- 将 opad 重复到与哈希函数内部块大小相同的长度(例如 SHA-256 就是 64 个 0x5C 字节)。这称为 opad_key。
- 将处理后的密钥 K0 与 opad_key 进行异或(XOR) 操作:K0 XOR opad_key,得到结果 So。
将上一步的内部哈希结果 Hi 附加在 So 之后:So || Hi。
执行外部哈希:
- 对上一步构建的外部输入 So || Hi 应用哈希函数 H:H(So || Hi)。
- 最终结果就是 HMAC 值(即 MAC),通常表示为 HMAC(K, M)。
Go hmac的实现
package hmacimport ("crypto/subtle" // 导入用于常量时间比较的安全库"hash" // 导入哈希接口
)// hmac 是实现 hash.Hash 接口的结构体
type hmac struct {opad, ipad []byte // 存储经过填充处理后的密钥outer, inner hash.Hash // 外层和内层的哈希对象size int // HMAC 结果的长度(字节数)blocksize int // 哈希函数的块大小(字节数)
}// New 函数创建并返回一个新的 HMAC 对象
// 参数:
// h - 哈希函数工厂(如 sha256.New)
// key - HMAC 使用的密钥
func New(h func() hash.Hash, key []byte) hash.Hash {hm := new(hmac) // 创建 hmac 结构体实例hm.blocksize = h().BlockSize() // 获取哈希函数的块大小hm.ipad = make([]byte, hm.blocksize) // 初始化内填充缓冲区hm.opad = make([]byte, hm.blocksize) // 初始化外填充缓冲区// 处理密钥:如果密钥长度大于块大小,先对密钥进行哈希if len(key) > hm.blocksize {h := h() // 创建哈希对象h.Write(key) // 写入密钥key = h.Sum(nil) // 计算哈希值作为新密钥}// 将密钥复制到内填充和外填充缓冲区// 如果密钥长度小于块大小,剩余部分保持为零(0x00)copy(hm.ipad, key)copy(hm.opad, key)// 使用填充值处理密钥(与 0x36 异或得到内密钥)for i := range hm.ipad {hm.ipad[i] ^= 0x36 // 内填充常量 0x36}// 使用填充值处理密钥(与 0x5C 异或得到外密钥)for i := range hm.opad {hm.opad[i] ^= 0x5c // 外填充常量 0x5C}// 创建内层哈希对象hm.inner = h()hm.inner.Write(hm.ipad) // 预先写入内填充密钥(ipad_key)// 创建外层哈希对象hm.outer = h()hm.outer.Write(hm.opad) // 预先写入外填充密钥(opad_key)// 设置结果大小hm.size = hm.outer.Size()return hm
}// Write 通过 io.Writer 接口向 HMAC 添加数据
// 此方法将数据添加到内层哈希计算
func (h *hmac) Write(p []byte) (n int, err error) {return h.inner.Write(p) // 数据被添加到内层哈希中
}// Sum 方法生成并返回 HMAC 值
// 参数:
// in - 可选的字节切片,结果会被追加到这个切片之后
func (h *hmac) Sum(in []byte) []byte {// 计算内层哈希结果: H(ipad_key || message)innerHash := h.inner.Sum(nil)// 将内层哈希结果添加到外层哈希对象中h.outer.Write(innerHash)// 计算并返回最终结果: H(opad_key || H(ipad_key || message))return h.outer.Sum(in)
}// Reset 重置 HMAC 对象,用于计算新的 HMAC
func (h *hmac) Reset() {// 重置内层哈希对象h.inner.Reset()// 重新写入内填充密钥h.inner.Write(h.ipad)// 重置外层哈希对象h.outer.Reset()// 重新写入外填充密钥h.outer.Write(h.opad)
}// Size 返回 HMAC 结果的长度(字节数)
func (h *hmac) Size() int {return h.size
}// BlockSize 返回底层哈希函数的块大小
func (h *hmac) BlockSize() int {return h.blocksize
}// Equal 函数安全地比较两个 HMAC 值是否相等
// 使用恒定时间算法避免时序攻击
func Equal(mac1, mac2 []byte) bool {return subtle.ConstantTimeCompare(mac1, mac2) == 1
}