HMAC 的核心原理步骤
K1 = K xor ipad
K2 = K xor opad
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)。
神奇的事情
func TestName(t *testing.T) {
msg := []byte{1}
key := []byte{1}
// - 直接使用hamc
hm := hmac.New(sha3.New256, key)
hm.Write(msg)
res := hm.Sum(nil)
fmt.Println(base64.StdEncoding.EncodeToString(res))
3jqRM4tcGbNTsWxMfYwbU43p/Tlg6kz9Qiq932eG5yA=
// - 简单是实现 hamc
bs := sha3.New256().BlockSize()
innerHash := sha3.New256()
ipad := make([]byte, bs)
copy(ipad, key)
for i := range ipad {
ipad[i] ^= 0x36
}
innerHash.Write(ipad)
innerHash.Write(msg)
innerHashRes := innerHash.Sum(nil)
opad := make([]byte, bs)
copy(opad, key)
for i := range opad {
opad[i] ^= 0x5c
}
outHash := sha3.New256()
outHash.Write(opad)
outHash.Write(innerHashRes)
outHashRes := outHash.Sum(nil)
fmt.Println(base64.StdEncoding.EncodeToString(outHashRes))
3jqRM4tcGbNTsWxMfYwbU43p/Tlg6kz9Qiq932eG5yA=
}
Go hmac的实现
package hmac
import (
"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
}