在本章中,我们将了解消息身份验证码(message authentication codes,MAC),也称为身份验证标签(authentication tags)。MAC用于流行的安全网络协议,如TLS、SSH 和 IPsec,以建立传输数据的完整性和真实性。出于同样的目的,它们也用于专有网络协议,例如金融软件。此外,MAC可用于非网络身份验证加密,正如我们在【第2章“对称加密和解密”】中所演示的那样。MAC的另一个应用是作为一些密钥推导函数(如PBKDF2)的基础。密钥推导将在【第5章“从密码推导加密密钥”】中更详细地介绍。我们将学习如何使用命令行和C代码计算MAC。
第四章 MAC和HMAC技术要求什么是MAC?了解MAC功能安全HMAC——基于哈希的 MACMAC、加密和密码末日原理如何在命令行上计算HMAC如何以编程方式计算HMAC实施hmac程序运行hmac程序总结
本章将包含可以在命令行上运行的命令以及也可以构建和运行的C源代码。对于命令行命令,您将需要带有 OpenSSL 动态库的 openssl
命令行工具。要构建C代码,您需要OpenSSL动态或静态库、库头、C编译器和链接器。
我们将在本章中实施一个示例程序,以练习我们正在学习的内容。该程序的完整源代码可以在这里找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/tree/main/Chapter04.
MAC是一个短比特数组,例如256比特,用于验证(authenticates)消息。消息认证(message authentication)意味着消息的接收者可以验证消息是否来自指定的发送者,并且在传输过程中没有被更改。为了生成MAC,发送方需要一条消息和一个密钥(secret key)。为了验证MAC,接收器需要消息和相同的密钥。MAC是由MAC函数(MAC function)生成的。
MAC和消息摘要之间的区别在于,消息摘要没有防伪保护;然而,MAC确实具有这样的保护。如果消息及其摘要都通过未受保护的网络传输,攻击者可以更改消息并重新计算其摘要,以便更改的摘要与更改的消息相匹配。另一方面,如果消息使用其MAC进行传输,则攻击者无法以相同的方式重新计算更改消息的MAC。这是因为他们没有密钥。因此,人们普遍认为消息摘要只提供消息完整性(integrity),但MAC提供完整性和真实性(authenticity)。
此外,MAC不同于数字签名(digital signatures)。数字签名使用非对称密码学,因此签名者和签名验证者使用同一密钥对的不同密钥。只有签名者才能产生签名,因为只有他们拥有私钥(private key)。因此,数字签名提供了不可否认性(non-repudiation),这意味着签名者不能否认他们拥有他们签名的信息。使用MAC时,发送方和接收方都使用相同的密钥。因此,发送方和接收方都可以为他们拥有的任何信息生成MAC,而第三方很难确定是谁生成了MAC。因此,与数字签名不同,MAC不提供不可否认性。有关数字签名的更多信息,请参阅【第7章“数字签名及其验证”】。
与哈希函数和数字签名函数不同,MAC函数也有不同的安全要求。
如果MAC功能能够抵抗如下所述的伪造攻击列表,则被认为是安全的,这些攻击从难到难依次列出。所描述的所有攻击都表明攻击者不拥有密钥:
当然,与其他加密攻击一样,对MAC的任何攻击只有在没有不可行的计算量的情况下执行时才能被视为成功。
MAC函数的安全性是使用与其他加密函数相同的测量单位(安全位)来测量的。值得注意的是,通常,MAC函数使用另一个具有自己安全属性的底层函数。MAC功能的安全性取决于其底层功能和所使用的密钥的安全性。
有不同类型的MAC函数,在安全网络协议中最常用的是HMAC。
基于哈希的消息认证码(Hash-based Message Authentication Code,HMAC)是由 HMAC function生成的MAC。
HMAC函数使用加密哈希函数和密钥。该函数并不复杂,许多非密码学家都能理解。其定义如下:
xxxxxxxxxx
HMAC(K, message) = H(K' XOR opad ‖ H(K' XOR ipad ‖ message))
在这里,可以理解以下内容:
请注意,K'是从K推导出来的,如下所示:
请注意,哈希函数的内部块大小B与生成的哈希长度不同。通常,哈希长度小于B。例如,SHA-256函数的哈希长度为256位,但其内部块大小为1088位。
值得一提的是,使用特定哈希函数的HMAC函数通常由 HMAC- 前缀和底层哈希函数名称组成的名称调用。例如,使用 SHA-256 函数的 HMAC 函数称为 HMAC-SHA-256 。
HMAC函数产生的HMAC值的长度与底层哈希函数的哈希长度相同。例如,HMAC-SHA-256函数产生一个256位的HMAC。
好奇的读者可能会问:为什么我们需要用嵌套哈希和填充来使HMAC函数复杂化?为什么我们不能假设HMAC实现如下?
xxxxxxxxxx
H(K ‖ message)
简而言之,需要这些复杂性来对抗对HMAC的各种攻击,其中首先包括长度扩展攻击(length extension attack)。长度扩展攻击旨在通过在不知道原始消息和密钥的情况下将攻击者控制的消息附加到原始消息来产生有效的MAC。许多加密哈希函数,包括SHA-256,都容易受到长度扩展攻击。较新的哈希函数家族,如SHA3,不易受到长度扩展攻击,可以与更简单的MAC函数一起使用。例如,对于SHA-3函数,存在它们自己的MAC函数:KECCAK消息认证码(KECCAK Message Authentication Code,KMAC)。
当涉及到安全强度时,不妥协的HMAC函数的安全级别可以计算如下:
xxxxxxxxxx
SecurityLevel = min(K_bits, L)
在这里,可以理解以下内容:
例如,如果使用256位强密钥,HMAC-SHA-256函数将达到其最高安全级别:256位。
那么,密钥的安全强度是多少?本质上,它是密钥的熵。如果密钥是由加密安全的随机生成器生成的,则其安全强度与其长度相同。这意味着一个256位长的密钥具有256位的安全强度。如果怀疑所使用的随机生成器具有弱随机性,例如,每2个生成的比特只有1个熵比特,那么生成一个两倍长(512比特)的密钥以获得相同的安全强度(256比特)是有意义的。但是,如果密钥来自低熵数据源,如密码,该怎么办?好吧,密钥的熵不能超过其来源,并且具有这种密钥的整个HMAC函数的安全级别会降低到密码中的熵量。关于如何从密码中导出密钥的更多信息,请参阅【第5章“从密码中推导加密密钥”】。
HMAC函数可以接受任何长度的密钥。然而,考虑到短于L的密钥是从加密安全的随机生成器中获得的,强烈建议不要使用这些密钥,因为它们会削弱HMAC函数的安全性。可以使用长于L的密钥,但较长的密钥并不能显著提高HMAC函数的安全性。这是因为HMAC函数的输出仍然限于L。因此,最明显的密钥长度就是L。在TLS协议中使用HMAC时,OpenSSL使用L长密钥。
HMAC函数的密钥必须无法猜测,就像对称加密的密钥一样。这意味着密钥必须具有足够的熵。实现这一点的一种方法是使用密码强的伪随机生成器生成密钥。
如前所述,HMAC安全性取决于底层哈希函数的安全性。然而,如果底层哈希函数受到损害,HMAC函数通常比其底层哈希函数安全得多。例如,即使 MD5 哈希函数严重受损,其当前的安全级别为18位,但 HMAC-MD5 函数的安全级别被认为是97位。但是,即使HMAC函数在密码学上比其哈希函数更强,避免任何不必要的风险并使用基于安全哈希函数(例如HMAC-SHA-256)的HMAC函数也是有意义的。
HMAC的实用价值之一是HMAC是TLS、SSH和IPsec协议中使用的MAC类型。在下一节中,我们将解释MAC如何与加密相结合,并用于安全网络协议。
当将MAC与加密相结合时,使用以下方案之一:
Encrypt-then-MAC (EtM)
在这里,明文被加密,然后根据 密文 计算MAC,并与密文一起发送。
接收者根据MAC验证密文,然后解密获得明文。
Encrypt-and-MAC (E&M)
这里,明文是加密的,但MAC是根据 明文 而不是密文计算的。然后,密文和MAC一起发送。
接收者先解密获得明文,然后根据MAC验证明文。
MAC-then-Encrypt (MtE)
这里,MAC是根据 明文 计算的。然后对明文和MAC的连接进行加密。
接收者解密获得MAC和明文,然后用MAC验证明文。
在接收端,EtM方案允许您在解密操作之前检查加密消息的真实性。另外两种方案要求在验证MAC之前对整个明文进行解密。
安全研究人员认为,只要使用强大且不可伪造的MAC功能,EtM是最安全的方案。从安全的角度来看,验证数据真实性并尽快丢弃错误消息是有意义的,特别是如果消息来自不受信任的渠道,如互联网。如果在接收方验证消息真实性之前必须进行解密,则攻击者可以在接收方丢弃消息之前更改传输中的任何消息并攻击加密。
2011年,著名安全研究员Moxie Marlinspike提出了密码末日原理(Cryptographic Doom Principle)。简单地说,如果你在验证收到的消息的MAC之前必须执行任何加密操作,这将不可避免地导致厄运。然后,Moxie提到了一些违反加密末日原则的例子,这些例子使得对加密进行成功攻击成为可能。
这个原则听起来相当大胆,尤其是那些不可避免的部分。在我看来,这应该更多地被视为一项一般性建议,而不是一项硬性规定。
话虽如此,尽管EtM是最安全的方案,但也有可能安全地实现其他两个方案,尽管需要更多的关注和注意。
直到几年前,身份验证(MAC)和加密只能作为单独的操作使用。然而,最近,经过身份验证的加密模式,如AES-GCM或ChaCha20-Poly1305,已经变得流行起来,并正在取代未经身份验证的编码模式。经过身份验证的加密模式将身份验证作为其核心功能之一,不需要单独的身份验证操作,例如对明文或密文运行HMAC函数。如果你使用的是经过身份验证的加密,你不需要考虑EtM和MtE。它没有既定的术语,但你可以说,当使用经过身份验证的加密时,你正在进行 MAC加密(MAC-while-Encrypt)。
如您所见,有不同的方法将身份验证与加密相结合。但是,流行的安全协议中使用了哪些方法?
从历史上看,TLS一直使用MtE方案。TLS协议基于1995年发明的SSL协议,当时EtM方案的优越性尚未得到证实。后来,引入了EtM TLS协议扩展(EtM TLS protocol extension),允许TLS客户端和服务器协商EtM而不是MtE。在TLS 1.2中,协议中添加了经过身份验证的加密密码。最后,在TLS 1.3中,只有经过身份验证的密码仍然是允许的密码。重要的是要记住,即使经过身份验证的密码不需要单独的MAC操作,HMAC仍然在TLS 1.3中用作密钥交换(key exchange)所需的伪随机函数(Pseudorandom Function,PRF)的基础。
与TLS类似,SSH 也是一种旧协议。目前使用的SSH协议版本2于1998年发布。从历史上看,SSH一直在使用E&M方案。然而,SSH中的身份验证方案取决于MAC算法,而较新的MAC算法使用EtM方案。较新的EtM算法优于旧的E&M算法。此外,较新版本的SSH实现支持不需要单独MAC的经过身份验证的密码。
IPsec从一开始就使用EtM,因为它是在20世纪90年代创建的。后来,身份验证密码被添加到IPsec中,类似于TLS和SSH。
我们现在已经了解了MAC和HMAC是什么以及它们是如何使用的。让我们做一些练习,学习如何在命令行上计算HMAC。
现在我们将学习如何在命令行上计算HMAC。为此,我们需要指示 openssl
工具在哪种类型的MAC函数上运行,以及使用哪种底层函数。MAC函数将是 HMAC ,对于底层函数,让我们选择SHA-256 哈希函数。
openssl
工具提供了两种HMAC计算方法:
openssl dgst
子命令。这与我们在上一章中用于计算消息摘要的子命令相同。openssl mac
子命令。OpenSSL 3.0中引入了一个新的子命令。我们将学习如何使用这两种方法。
像往常一样,首先,让我们生成一个示例文件,用作HMAC计算的输入:
xxxxxxxxxx
$ seq 20000 >somefile.txt
HMAC计算需要密钥。那么,让我们生成它:
xxxxxxxxxx
$ openssl rand -hex 32
df036c471b612f8ad099078d8e3bd9c64339e7aeab56ec75e2222c415db113de
以下是如何使用 openssl dgst
子命令计算HMAC。为了计算HMAC而不是消息摘要,您必须将 -mac HMAC
添加到命令行开关中,并通过 -machot hexcey:
开关提供密钥:
xxxxxxxxxx
$ openssl dgst -sha-256 -mac HMAC -macopt \
hexkey:df036c471b612f8ad099078d8e3bd9c64339e7aeab56ec75e2222c415db113de \
somefile.txt
HMAC-SHA256(somefile.txt)= 55e18ba91be755133ab0f4dbca5d06f2e7df0b6bb4cd5f16f9f2d2f7cf83372c
正如您所看到的,命令行上的HMAC计算几乎和消息摘要计算一样简单。
下面显示了如何使用新的 openssl mac
子命令进行相同的HMAC计算:
xxxxxxxxxx
$ openssl mac -digest SHA-256 –macopt \ hexkey:df036c471b612f8ad099078d8e3bd9c64339e7aeab56ec75e2222c415db113de \
-in somefile.txt \
HMAC
55E18BA91BE755133AB0F4DBCA5D06F2E7DF0B6BB4CD5F16F9F2D2F7CF83372C
请注意,我们得到了相同的HMAC值,但输出格式略有不同。
现在,让我们学习如何在C程序中以编程方式计算HMAC。
我们将实现计算 HMAC-SHA-256 的 hmac
程序。
我们的 hmac
程序需要两个命令行参数:
当涉及到用于HMAC计算的应用程序编程接口(Application Programming Interface,API)时,OpenSSL 3.0提供了三个完整的API:
HMAC_
前缀的函数组成。这个API的一个有趣的事实是,尽管它不是 EVP
API,但它使用 EVP_MD
API来访问底层的消息摘要函数。EVP_DigestSign
API。此API主要用于数字签名。可以将此API用于MAC,但这样的使用并不十分直观。EVP_MAC
API。这个新的API是专门为MAC创建的,并在OpenSSL 3.0中引入。我们将在代码中使用这个API。EVP_MAC
API的官方文档可在同一名称的手册页上找到:
xxxxxxxxxx
$ man EVP_MAC
以下手册页也可能很有趣:
xxxxxxxxxx
$ man EVP_MAC-HMAC
$ man OSSL_PARAM
$ man OSSL_PARAM_int
与前几章一样,让我们制定一个高级实施计划:
EVP_MAC
对象。OSSL_PARAM
数组。EVP_MAC_CTX
。stdout
。开始吧。
以下是我们如何实施 hmac
计划:
首先,我们必须从OpenSSL的默认算法提供程序中获取HMAC算法描述:
xxxxxxxxxx
EVP_MAC* mac = EVP_MAC_fetch(NULL, OSSL_MAC_NAME_HMAC, NULL);
请注意传递给函数的 OSSL_MAC_NAME_HMAC
值。这就是我们如何指定我们想要一个特定的HMAC算法,而不是另一个MAC算法,如CMAC或GMAC。
接下来,我们必须为MAC计算上下文创建参数:
xxxxxxxxxx
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string(
OSSL_MAC_PARAM_DIGEST,
OSSL_DIGEST_NAME_SHA2_256,
0),
OSSL_PARAM_construct_end()
};
请注意 OSSL_MAC_PARAM_DIGEST
和 OSSL_DIGEST_NAME_SHA2_256
的值。这就是我们指定要使用SHA-256作为底层消息摘要函数的方式。
我们提取了算法并创建了参数;让我们使用它们来创建和初始化MAC计算上下文:
xxxxxxxxxx
EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
EVP_MAC_init(ctx, key, key_length, params);
我们还将密钥及其长度设置到上下文中。我们已将该密钥作为命令行参数获取,并对其进行了非信息化处理。
初始化已完成。现在我们需要将数据从输入文件馈送到上下文:
xxxxxxxxxx
while (!feof(in_file)) {
size_t in_nbytes = fread(in_buf, 1, BUF_SIZE, in_file);
EVP_MAC_update(ctx, in_buf, in_nbytes);
}
在所有数据都被馈送到上下文后,完成计算并获得结果HMAC:
xxxxxxxxxx
const size_t HMAC_LENGTH = 256 / 8;
unsigned char hmac[HMAC_LENGTH];
size_t out_nbytes = 0;
EVP_MAC_final(ctx, hmac, &out_nbytes, HMAC_LENGTH);
现在我们不再需要 EVP_MAC
和 EVP_MAC_CTX
对象。让我们释放它们并避免任何内存泄漏:
xxxxxxxxxx
EVP_MAC_CTX_free(ctx);
EVP_MAC_free(mac);
最后,让我们以与 openssl mac
子命令相同的格式打印计算出的HMAC:
xxxxxxxxxx
for (size_t i = 0; i < HMAC_LENGTH; ++i) {
printf("%02X", hmac[i]);
}
printf("\n");
我们的hmac程序的完整源代码可以在GitHub上以hmac.c文件的形式找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/blob/main/Chapter04/hmac.c 。
让我们运行HMAC计算程序并检查它是否有效:
xxxxxxxxxx
$ ./hmac somefile.txt df036c471b612f8ad099078d8e3bd9c
64339e7aeab56ec75e2222c415db113de
55E18BA91BE755133AB0F4DBCA5D06F2E7DF0B6BB4CD5F16F9F2D2F7CF83372C
正如您所看到的,我们的小程序计算出的HMAC与上一节中 openssl
工具计算的HMAC相同。这样的观察表明我们的程序工作正常。
在本章中,我们了解了MAC是什么,以及它们与消息摘要和数字签名有何不同。我们还了解了MAC功能的安全性和良好的MAC功能必须抵御的攻击。在此之后,我们了解了什么是HMAC函数以及它的安全性取决于什么。在理论部分,我们回顾了将MAC函数与加密相结合的几种方法,发现了最佳方法,并讨论了加密末日原理。
在实践部分,我们学习了命令行上HMAC计算的两种方法。然后,我们还学习了如何用C代码以编程方式计算HMAC。我们比较了所有使用的方法计算出的HMAC,并满意地确认所有方法都产生了相同的HMAC。
在下一章中,我们将学习密钥推导函数(Key Derivation Functions,KDF)以及如何从密码中推导加密密钥。