我想写这篇简短的文章,以解释我在阅读消息身份验证代码(MAC)时的最新学习。
首先,让我们简要解释什么是Mac,以及它们如何对我们有用。
mac(消息身份验证代码)是使用哈希函数和秘密键(它们也称为对称加密算法)的加密算法,以生成所谓的authentication tag
或仅仅是tag
,用于验证该消息(该消息)没有更改)和秘密键知识的消息的真实性(发送者的身份)。
野外最广泛使用的Mac是所谓的HMAC:基于哈希的Mac。就像其名称所暗示的那样,这是一种使用秘密键使用哈希功能的方法。 HMAC可以与SHA128,SHA256,SHA512 ...等一起使用。HMAC函数的结果称为HMAC或TAG。我将在本文中使用Lowercase HMAC。
换句话说,如果您和我同意使用的秘密键和哈希算法要使用的算法,我们可以有效地共享消息,只有我们才能验证的真实性和完整性。没有人可以篡改我们在运输中的消息。
让我们从创建一个示例GO程序开始,该程序创建一些数据的HMAC,然后使用SHA256 HASHINing算法和一个强,随机生成的秘密密钥对其进行验证。
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
)
var secretKey = "randomly generated, secure secret key"
var data = "This is private information"
func hmac(data, secretKey string) string {
hash := hmac.New(sha256.New, []byte(secretKey))
hash.Write([]byte(data))
return hex.EncodeToString(hash.Sum(nil))
}
func verify(data, hmac, secretKey string) bool {
// Recompute the signature
correctHmac := sign(data, secretKey)
// Check if they are equal
return correctHmac == hmac
}
func main() {
hmac := hmac(data, secretKey)
fmt.Printf("Data: %s\nHMAC: %s", data, hmac)
fmt.Println()
correct := verify(data, hmac, secretKey)
fmt.Printf("\nIs HMAC %s correct ? %s", hmac, strconv.FormatBool(correct))
}
这是相同功能的PHP示例:
<?php
const secretKey = "randomly generated, secure secret key";
const data = "This is private information";
function hmac(string $data, string $secretKey): string {
return hash_hmac(algo: 'sha256', data: $data, key: $secretKey);
}
function verify(string $data, string $hmac, string $secretKey): bool {
$correctHmac = sign($data, $secretKey);
return $correctHmac === $hmac;
}
function run() {
$hmac = hmac(data, secretKey);
print sprintf("Data: %s\nHMAC: %s", data, $hmac);
print "\n";
$correct = verify(data, $hmac, secretKey);
print sprintf("Is HMAC %s correct ? %s", $hmac, $correct ? "true" : "false");
}
run();
两个程序输出均为:
Data: This is private information
HMAC: cf824e1bfbc628aca8709483622f9c2aaf7ac9e57d50623d225cc76d8ad578fc
Is HMAC cf824e1bfbc628aca8709483622f9c2aaf7ac9e57d50623d225cc76d8ad578fc correct ? true
我们的代码问题
使用==
比较哈希斯使我们容易受到一种称为“正时攻击”的攻击。因为==比较一旦检测到两个HMAC有所不同,所以它不适合此用例。
因为,攻击者可以通过测量比较需要多长时间来重新创建有效的HMAC。
为了更好地看待这一点,让我们以一个例子为例。假设我们有8位HMAC,每个位都需要1ms
与其他HMAC位进行比较。有效的签名如下:0001 1010
- 如果我向您发送正确的HMAC,那么您应该完全使用
8ms
才能验证它实际上是有效的。 - 如果我现在向您发送
1000 0000
,则您的回复少于1ms
,因为第一个位是不正确的,因为它是不正确的。请记住,1ms
需要验证每一位。 - 如果我向您发送
0001 1001
,您的答复将为6ms
提供,因为前6位是正确的,但最后2位。 - ...等。
这样,我能够通过更新我的锻造HMAC并测量您(秘密密钥所有者)验证它的时间来重新创建有效的HMAC。当然,这是一个过于简化的例子,Mac生成的身份验证标签至少应该是128 bit
足够强大并避免碰撞。
要点是, Mac标签比较/验证应始终在恒定时间内进行。在我们简化的示例中,HMAC是否相等,它应该始终以相同的8ms
返回结果。
请注意,这也适用于数字签名(Mac的非对称兄弟)。还需要在恒定时间内验证签名以避免正时攻击。
解决方案
我有目的地使用两种语言的操作员来说明此问题,但是在加密操作和算法方面,我们应该始终使用良好且知名的库。
- 在GO中,我们可以使用
crypto/hmac
软件包的hmac.Equal()
在恒定时间内验证HMAC。 - 在PHP中,我们可以使用
hash_equals()
函数比较HMAC和哈希。
让我们以正确的方式重写我们的GO&PHP示例。我只需重写verify
的功能,就可以。
func verify(data, signature, secretKey string) bool {
// Recompute the signature
correctSignature := sign(data, secretKey)
// Check if they are equal
return hmac.Equal([]byte(correctSignature), []byte(signature))
}
和PHP示例看起来像这样
function verify(string $data, string $signature, string $secretKey): bool {
$correctSignature = sign($data, $secretKey);
return hash_equals($correctSignature, $signature);
}
如何进行恒定时间比较
让我们深入研究,看看如何实现hmac.Equals()
。源代码显示,它实际上使用了crypto/subtle
软件包中的函数,称为ConstantTimeCompare
,该函数是这样实现的:
func ConstantTimeCompare(x, y []byte) int {
if len(x) != len(y) {
return 0
}
var v byte
for i := 0; i < len(x); i++ {
v |= x[i] ^ y[i]
}
return ConstantTimeByteEq(v, 0)
}
- 如果这两个切片不相等,我们会尽早返回,因为在这种情况下,我们知道使用了完全不同的哈希算法。
- 接下来,创建一个字节变量
v
,它将保持我们的结果。 - 然后,我们在切片上迭代,然后将每个切片元素弄清,结果与变量
v
有关。也就是说,如果我们要比较的两个字节不同,则XOR为1,与v
相比,v
将等于1,并且将在整个循环中保持1。如果两个切片的所有字节相同,则v
仅为0。 - 然后,采取另一种预防措施,我们还使用
ConstantTimeByteEq
以时间常数的方式比较两个字节v
和0。
这两个切片是否相等,该功能需要完全相同的返回时间,这使其正时攻击安全。
就是这样!感谢您的阅读,直到下一集,祝您有美好的一天!