在本章中,我们将学习消息摘要(message digests),也称为加密哈希(cryptographic hashes)。由加密哈希函数生成的消息摘要具有许多重要应用,如数据完整性检查、密码验证、数字签名、TLS等安全网络协议,甚至区块链。我们将概述OpenSSL支持的加密哈希函数,并建议使用哪些哈希函数和避免哪些哈希函数。然后,在本章的实践部分,我们将学习如何使用命令行和C代码计算消息摘要。在阅读了本章并尝试了实践部分后,您将知道为什么需要消息摘要以及如何计算它们。
第三章 消息摘要技术要求什么是消息摘要和加密哈希函数?为什么需要消息摘要?数据完整性验证HMAC的基础数字签名网络协议密码验证内容标识符区块链和加密货币工作量证明评估加密哈希函数的安全性OpenSSL支持的加密哈希函数概述回顾SHA-2哈希函数家族回顾SHA-3哈希函数家族回顾SHA-1和SHA-0哈希函数回顾MD哈希函数家族回顾BLAKE2哈希函数家族回顾OpenSSL支持的不太流行的哈希函数国家加密哈希函数其他加密哈希函数你该选用哪种加密哈希函数如何在命令行计算消息摘要如何以编程方式计算消息摘要实施摘要程序运行摘要程序总结
本章将包含可以在命令行上运行的命令以及可以编译和运行的C代码。对于命令行命令,您将需要 openssl
命令行工具以及相关的openssl动态库。要构建C代码,您需要OpenSSL动态或静态库、库头、C编译器和链接器。
我们将在本章中实施一个示例程序,以练习我们正在学习的内容。该程序的完整源代码可以在这里找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/tree/main/Chapter03 。
密码学中的消息(message)是由密码算法(cryptographic algorithm)处理的任何数据,无论大小。
密码散列函数(cryptographic hash function)是一种将任意大小的消息映射到相对较短(例如256位)固定大小的比特数组的算法。这个固定大小的位数组称为消息摘要(message digest)或加密哈希(cryptographic hash)。
换句话说,消息摘要是加密哈希函数的输出。正如我们在上一章中提到的,我们可以说消息摘要是一个加密强度很强的校验和。
一个好的加密哈希函数具有以下属性:
理解什么是消息摘要并不难,但为什么需要它们,为什么它们很重要?
消息摘要有很多应用。最明显的一个是数据完整性验证(data integrity verification)。
当您从互联网下载软件时,您通常会在下载链接附近找到分发包的消息摘要。例如,请查看OpenSSL下载页面 https://www.openssl.org/source/:
在下载链接旁边,您将看到名为SHA256和SHA1的链接。这些包含相应 .tar.gz 文件的消息摘要。下载所需的 .tar.gz 文件后,可以计算下载文件的摘要并将其与预期摘要进行比较。如果摘要匹配,则文件在传输和保存时可能没有损坏或以其他方式更改。如果文件被更改,由于 雪崩效应(avalanche effect),计算出的摘要看起来与预期的摘要非常不同。在我们的例子中,OpenSSL分发包是一个相当小的下载,而OpenSSL官方网站是一个值得信赖的来源,因此下载被破坏的可能性很小。但是想象一下,如果你正在下载一些大的东西,比如Linux安装映像,或者你从非官方来源收到了一个发行版,并想检查它是否被篡改。在这里,您可以从官方网站获得正确的摘要,并检查不受信任的下载摘要是否匹配。
消息摘要也适用于存储的数据。如果某些数据存储时间过长,则可能会因存储介质错误或其他意外更改而损坏。通过消息摘要进行验证是检查数据是否已损坏的可靠方法。
消息摘要构成了基于哈希的消息身份验证码(Hash-based Message Authentication Codes,HMAC)的基础。HMAC是一种消息认证码(Message Authentication Code,MAC),它结合了密钥和加密哈希函数进行数据认证。HMAC将在【第4章MAC和HMAC】中更详细地介绍。
当消息被数字签名时,该消息的摘要也会被签名。值得一提的是,数字签名不仅用于文件。它们通常用于对驱动程序、应用程序甚至加密对象进行签名,例如TLS协议中使用的X.509证书(X.509 certificates)。【第7章“数字签名及其验证”】将更详细地介绍数字签名。
支持安全的网络协议,如TLS和SSH,在不同的协议阶段使用HMAC和传输数据的数字签名。正如我们之前提到的,HMAC和数字签名需要加密哈希函数,因此安全网络协议也需要它们。
大多数现代操作系统和网站都不以明文形式存储密码。通常,会存储一个盐密码哈希。Salted是指在哈希之前在密码中添加一些盐。Salt是少量的随机数据,可以小至2个字节,它被添加到密码之前或附加到密码之后,这样相同的密码将具有不同的哈希值,并且在已知哈希值的帮助下更难破解。盐本身不是秘密,通常与哈希一起存储。验证密码后,将其与存储的盐连接并散列。如果计算的摘要与存储的摘要相同,则验证成功。
由于良好的加密哈希函数的一个特征是哈希冲突的概率极低,因此消息摘要可用于识别对象。让我们来看一些如何使用加密哈希进行身份识别的示例。
在验证X.509证书时,通过其名称及其公钥的加密哈希查找颁发者证书。
现代源代码管理(Source Code Management,SCM)系统,如Git和Mercurial,使用加密哈希来唯一标识存储的对象,如文件、提交、分支和标签。这种识别方案还确保了代码库的完整性。
一些对等文件共享网络,如eDonkey,使用消息摘要来标识共享文件。
区块链(blockchain)块使用加密哈希链接在一起。较新的块通过哈希引用旧块。这种引用确保了链的完整性,并且不可能更改旧块,因为任何块更改都会更改其哈希值。因此,它的身份也会改变,改变后的区块将不再是区块链的一部分。
区块链技术最著名的应用是加密货币(cryptocurrencies)。但这不是唯一的一个。上述Git和Mercurial SCM在其存储对象之间使用相同类型的链接,因此它们也基于区块链技术。区块链还有许多其他用途:在贸易、医药和其他领域。没有加密哈希函数和消息摘要,区块链是不可能的。
工作量证明(proof-of-work)系统为其客户端提供了一个加密挑战,只有在客户端执行一定量的计算工作时才能克服。其中一个挑战是 部分哈希反转(partial hash inversion),这意味着客户端必须找到一些部分消息摘要与挑战中请求的部分消息摘要相同的数据。如果使用了一个好的加密哈希函数,客户端必须执行一些暴力工作。找到的数据将证明客户端执行了这项工作。
工作量证明系统可用于加密货币挖矿、处理加密货币交易以及防止拒绝服务攻击(denial-of-service)或垃圾邮件(spam)。
让我们用垃圾邮件预防的例子来解释这个概念。想象一下,一个邮件客户端连接到邮件服务器,想要向一个或多个地址发送电子邮件。服务器向客户端发出工作量证明挑战——每个目标地址都有一个。如果客户端想将消息发送到一个或几个地址,它只需要花费少量的计算资源,这不会是一个很大的负担。然而,如果垃圾邮件发送者想将同一封邮件发送到一百万个地址,那么计算工作量将是难以承受的。
工作量证明系统的重要特性是,检查已完成的工作比执行工作容易得多。
为什么在工作量证明挑战中只需要找到部分哈希?因为恢复一个好的加密哈希函数的完整哈希值太昂贵,可能需要数十亿年的时间。工作量证明挑战的目的是防止服务滥用,而不是完全阻止服务。
正如我们所看到的,加密哈希函数和消息摘要非常重要,有很多很好的应用。但加密哈希函数只有在安全的情况下才有用。让我们了解一下对消息摘要函数执行的流行攻击,我们如何衡量它们的安全性,以及哈希函数的安全程度。
可以对加密哈希函数执行的两种最重要的攻击类型是:
对同一哈希函数执行碰撞攻击比执行预映像攻击更容易,因为消息搜索不限于单个目标消息摘要。
加密哈希函数的安全级别是碰撞攻击的计算复杂度。安全级别以安全位来衡量,类似于对称加密算法的安全级别。例如,如果碰撞攻击的复杂性是2128个哈希计算,那么安全级别是128位。
加密哈希函数的安全级别取决于其输出消息摘要的大小。如果消息摘要的大小为n比特,则碰撞攻击的最大攻击复杂度为2n/2,预映像攻击的最大复杂度为2n。碰撞攻击不可能有比2n/2更高的复杂度,因为基于生日悖论的生日攻击总是可以在2n/2时间内找到碰撞。
例如,SHA-256函数具有256位哈希大小、2128 冲突攻击复杂度、2256 预映像攻击复杂度和128位安全级别。还有人说,该函数的抗冲突级别为128位,其预映像级别为256位。
多少个安全位就足够了?与对称加密算法相同:
到目前为止,我们知道哈希函数的安全性是如何衡量的,以及多少安全性是足够的。接下来,我们将了解OpenSSL支持哪些加密哈希函数以及它们的安全性。
在本节中,我们将回顾OpenSSL支持的加密哈希函数及其属性,如安全性、速度和消息摘要大小。我们还将了解一些关于这些哈希函数的历史。
SHA-2家族包含最流行的现代加密哈希函数SHA-256。SHA-256输出256位摘要,抗碰撞级别为128位。
SHA-256被广泛使用。它目前是TLS协议中使用的默认哈希函数,也是X.509证书和SSH密钥的默认签名函数。包括比特币在内的几种加密货币使用SHA-256来验证交易和工作量证明。流行的Git SCM正在迁移到SHA-256哈希,用于区块链实现和对象识别过程。SHA-256用于许多安全协议和软件,如SSH、IPsec、DNSSEC、PGP等。SHA-256也是Unix系统中密码散列的流行选择。
除了SHA-256,SHA-2家族还包括以下哈希函数:
SHA-2函数由美国 国家安全局(National Security Agency,NSA)开发,并于2001年由美国国家标准与技术研究所(National Institute of Standards and Technology,NIST)首次作为联邦标准发布。SHA-2算法已获得专利,但该专利可在免版税许可下获得。
SHA代表安全哈希算法(Secure Hash Algorithm)。SHA-2函数目前被认为是安全的,但密码分析师正在缓慢而稳定地推进对SHA-2的攻击。虽然攻击正在推进,但目前还没有对SHA-2完整版本的成功攻击,只有对轮数减少的版本。因此,目前,所有SHA-2函数的抗冲突级别等于哈希比特数除以2,这是可能的最大值。
SHA-256及其修改对32位字(32-bit words)运行,而SHA-512及其修改对64位字(64-bit words)运行。因此,在32位CPU上SHA-256的计算速度比SHA-512快2-4倍,但在64位CPU上,SHA-512的计算速度大约是SHA-256的1.5倍。
SHA-2函数在现代硬件中具有可接受的速度,但比其前身SHA-1函数慢。例如,SHA-256函数比SHA-1慢两倍。
Intel和AMD的现代x86_64 CPU支持Intel SHA扩展(Intel SHA extensions),这加快了计算SHA-256和SHA-1的过程。一些ARMv8 CPU,如Cortex A53,支持ARMv8加密扩展(ARMv8 Cryptography Extensions),这也加快了计算SHA-256和SHA-1以及SHA-224的过程。
在撰写本文时,SHA-2函数是最流行的加密哈希函数,但 SHA-2家族 已经有了继任者:SHA-3家族。
与SHA-2的内部开发不同,SHA-3算法是通过算法竞赛选出的。这类似于通过另一场比赛选择高级加密标准(Advanced Encryption Standard,AES)算法的方式。NIST组织了这样一场比赛,因为SHA-2的前身,即SHA-1、SHA-0和MD5,都受到了成功的攻击,这些攻击都是基于与SHA-2相同的原理构建的;需要基于不同原理的哈希函数。SHA-3的选择是一个漫长的过程。NIST于2006年开始组织比赛,并于2007年正式宣布。申请人提交了他们的算法,直到2009年。然后,在2009年和2010年进行了两轮比赛。之后,2012年选出了获奖者,该标准于2014年发布,最终于2015年获得批准。
请注意,SHA-2算法并没有因为SHA-3的标准化而被弃用或放弃。SHA-3的目的是扩展良好消息摘要算法的多样性,并在需要时为SHA-2提供现成且简单的替代方案。
SHA-3竞赛的获胜者是比利时密码学家团队的Keccak算法。其中一位密码学家是Joan Daemen,她也是Rijndael算法(AES)的合著者。
SHA3算法家族由以下功能组成:
前四个函数是主要的SHA-3函数,与SHA-2家族的相应函数相似。它们分别输出224、256、384和512位的消息摘要。与SHA-2家族类似,这四个函数的抗冲突级别是消息摘要大小的一半,分别表示112、128、192和256位,预映像级别与消息摘要的大小相同,分别表示224、256、384和512位。
正如我们所看到的,这四个SHA-3函数目前提供了与SHA-2函数相同的安全级别。但是对SHA-2的攻击正在推进,这种平等可能有一天会结束。目前,对SHA-3的攻击进展远低于对SHA-2的攻击。
SHA-3 Keccak算法比SHA-2慢,因为它包含比SHA-2更多的顺序操作。SHA3-256函数比SHA-256慢约1.5倍,而SHA3-512比SHA-512慢2-4倍,具体取决于CPU。CPU扩展,如Intel SHA扩展和ARMv8加密扩展,支持SHA-2,但不支持SHA-3。然而,一些CPU具有指令集扩展,可以加快计算SHA-3的过程。例如包括x86/x86_64 CPU上的MMX、SSE、BMI2和AVX2/AVX-512/AVX-512VL,ARM CPU上的SVE/SVE2和ARMv8.2-SHA加密扩展集,以及POWER8 CPU上的Power ISA。值得一提的是,ARMv8.4-A版本的ARMv8 CPU指令集也支持SHA-3加速。
为了应对SHA-3的缓慢,SHA-3的作者开发了SHAKE128和SHAKE256函数,这两个函数明显比其他SHA-3函数快。严格来说,它们不是哈希函数,而是可扩展输出函数(eXtendable Output Functions,XOF)。可扩展的输出函数可以生成任意大小的摘要,并作为哈希函数的基础。通过选择特定的摘要大小,您可以从XOF中创建哈希函数。SHAKE128和SHAKE256名称中的数字表示函数的最大抗冲突攻击级别,分别为128和256位。请注意,由于生日悖论,可变大小摘要必须足够长才能达到最大安全级别,SHAKE128至少为256位,SHAKE256至少为512位。SHAKE128大约和SHA-256一样快,而在现代x86_64 CPU上,SHAKE256比SHA-512慢1.5倍。
后来,这些作者提出了KangarooTwelve(K12)可扩展输出函数,该函数基于Keccak,但减少了轮数(从24个减少到12个),可以利用多核CPU上并行执行的可用性。作者声称,KangarooTwelve的速度大约是SHA3-256的13倍(每字节0.51个周期,而Intel Skylake-X CPU上的每字节6.4个周期),并且仍然保持128位的安全级别。然而,实际实现的独立基准测试表明,KangarooTwelve的速度仅为SHA3-256的2-3倍。到目前为止,这种安全声明是正确的,因为对SHA-3算法最著名的攻击(在撰写本文时)只能攻击Keccak的简化6轮版本。然而,KangarooTwelve的安全裕度小于完整的24轮SHA-3。作为后来的开发,KangarooTwelve不被认为是SHA-3家族的一部分,也不受OpenSSL的支持。
尽管SHA-3在软件实现中比SHA-2慢,但SHA-3的硬件实现比SHA-2甚至SHA-1的硬件实现更快。对于现代CPU,SHA-3的速度是可以接受的。
SHA-3目前还没有SHA-2那么流行。加密库中的SHA-3支持已经很好了,但并没有多少应用程序开始使用该家族的哈希函数。SHA-3算法尚未在TLS或X.509中标准化,也未在OpenSSH和GnuPG等流行的加密软件中使用。SHA-3的一个显著用途是以太坊(Ethereum)加密货币,它用于工作量证明检查。值得一提的是,SHA-3现在正在进行NIST后量子密码学标准化过程,用于签名和密钥交换。从SHA-2迁移到SHA-3的过程非常缓慢,类似于从其前身SHA-1迁移SHA-2的过程。
SHA-1是美国国家安全局在20世纪90年代开发的一种加密哈希函数。SHA-1规范于1993年首次发布。然而,由于算法中发现了安全漏洞,该规范很快被撤回。SHA-1的最新修订规范于1995年发布,1993年的算法有一个非官方名称SHA-0。
SHA-1生成一个160位的消息摘要。该算法速度很快,大约是SHA-256的两倍。SHA-1的速度也与它的前身MD5算法差不多,在现代CPU上,由于硬件加速,它甚至更快。
不幸的是,SHA-1不再被认为是安全的。SHA-1的安全级别最初声称为80位,但按照现代标准,这并不安全。最佳攻击将安全级别进一步降低到61位。2017年,首次公开的SHA-1碰撞被发现。荷兰和谷歌的Centrum Wiskunde & Informatica研究中心发布了两个内容不同但SHA-1哈希相同的PDF文档。如果您可以获得一个文档的基于SHA-1的数字签名,则该签名对第二个文档也有效。这只是为什么加密哈希函数中的冲突是危险的一个例子。2020年,发现SHA-1碰撞的成本降至5万美元以下,这对大型企业、情报机构甚至富人来说都很容易负担得起。
SHA-1是SHA-256接管之前最流行的加密哈希算法。它被用于SSL、TLS、SSH和IPsec协议、X.509证书、Pretty Good Privacy(PGP)和 安全多用途邮件扩展(Secure Multipurpose Mail Extension,S/MIME)实现、数字签名算法(Digital Signature Algorithm,DSA)和其他领域。Git和Mercurial等源代码管理系统仍然使用SHA-1进行对象完整性和标识,尽管它们仍在继续努力迁移到其他哈希算法。
从SHA-1到SHA-256的迁移对IT行业来说是一个漫长的过程。著名密码学家 Bruce Schneier 在2005年建议从SHA-1迁移。NIST于2011年正式弃用SHA-1,主要网络浏览器于2017年停止接受基于SHA-1的证书。微软在2020年停止了对Windows更新基于SHA-1的签名的支持。许多鲜为人知的应用程序尚未迁移。
SHA-1的前身是MD哈希函数家族的MD5哈希函数。
MD系列哈希函数由四个函数组成:MD2、MD4、MD5和MD6。没有MD1和MD3函数。也没有官方信息说明为什么它们不存在,但有传言说MD1是一种从未向公众开放的专有算法,而MD3是一种被其设计者抛弃的实验算法。
MD代表消息摘要(Message Digest)。一个有趣的事实是,MD5(以及SHA-1和SHA-2)基于Merkle-Damgård构造(construction),也可以缩写为MD。
MD函数由著名密码学家Ronald Rivest设计,他发明了RC对称密码,如RC2、RC4和RC5,我们在【第2章“对称加密和解密”】中对此进行了回顾。
OpenSSL支持MD2、MD4和MD5哈希函数。它们都具有128位的消息摘要大小和64位的初始安全级别。不幸的是,这些功能不再安全,不建议您使用它们。最好的攻击将MD2、MD4和MD5的安全级别分别降低到63、1(是的,只有一个比特!)和18比特。在典型的PC上,对MD4或MD5的碰撞攻击可以在几分之一秒(in a fraction of a second)内完成。
MD5是MD家族中最著名的功能。MD5是SHA-1的前身,与SHA-1和SHA-2共享许多数学知识。MD5在被SHA-1取代之前非常普遍。它被用于所有流行的加密协议、标准和软件中。它在Unix和web系统中的密码散列(password hashing)也很受欢迎。关于MD5和SHA-1的一个有趣的事实是,SSL 3.0、TLS 1.0和TLS 1.1协议使用了MD5和SHA-1的组合,因为很难找到同时产生MD5和SHA-1冲突的数据块。
MD4函数以在Windows NT、2000和XP中用于密码散列(password hashing)而闻名。自Windows Vista以来,默认情况下MD4已禁用密码哈希,但即使在Windows 10下也可以启用。
BLAKE2散列函数家族由几个散列函数组成,其中两个是OpenSSL支持的,即BLAKE2s和BLAKE2b。BLAKE2s产生256位消息摘要,而BLAKE2b产生512位消息摘要。这些功能仍然分别保持128和256位的最大抗冲突级别。BLAKE2函数具有比SHA-2函数更大的安全裕度,并且与SHA-3函数相似。
BLAKE2函数以其速度而闻名。在没有硬件加速的旧CPU上,BLAKE2s和BLAKE2b比MD5、SHA-1、SHA-2和SHA-3更快。在具有硬件加速的较新CPU上,如Intel Skylake或更高版本,SHA-1是最快的,但BLAKE2和BLAKE2b仍然比SHA-2对应物SHA-256和SHA-512快。BLAKE2s和BLAKE2b也明显快于SHA3-256和SHA3-512,同时具有相似的安全裕度。
BLAKE2加密散列函数家族是由一个国际密码学家小组于2012年开发的。BLAKE2家族基于2008年发布的BLAKE函数家族,参加了SHA-3哈希函数竞赛,甚至进入了决赛,但最终输给了Keccak算法。BLAKE函数族基于ChaCha流密码,我们在上一章中对此进行了回顾。
BLAKE2函数家族已经有了一个继任者:2020年发布的BLAKE3函数。BLAKE3函数支持等于或长于256位的任意大小的消息摘要。BLAKE3比BLAKE2快几倍,并且可以高度并行化,因为它基于Merkle树结构(Merkle tree structure)。关于BLAKE3的安全性,作者声称其具有128位的抗碰撞和预映像攻击能力。但是,与其他哈希函数相比,BLAKE3上的安全分析要少得多。因此,目前很难说BLAKE3有多安全。BLAKE3没有包含在OpenSSL 3.0中。
尽管BLAKE2没有标准化用于 TLS、*SSH *或 PGP,但一些软件开发人员注意到了哈希函数家族,并决定在他们的软件中使用它。某些BLAKE2算法用于流行的软件,如WhatsApp、7-zip、WinRAR、Rsync、Chef、WireGuard,以及Zcash和NANO等加密货币。
OpenSSL还支持其他消息摘要算法,但它们不如我们已经讨论过的算法那么受欢迎。一些不太受欢迎的算法是特定国家的国家标准。
那么,在新项目中应该使用哪个消息摘要函数呢?如果您的项目没有特殊要求,请选择 SHA3-256。此函数提供了良好的安全性,得到了加密库的良好支持,具有可接受的性能,并且经得起未来考验。一些密码学家已经建议人们从SHA-2迁移到SHA-3。预计SHA-3函数将在未来版本的安全标准和协议中得到更好的支持,如TLS、SSH、PGP 和 X.509,以及在使用密码学的流行软件中,如web浏览器、PGP、GnuPG、各种数字签名软件以及操作系统和网站的密码散列子系统。预计较新的CPU和专用加密芯片将为SHA-3算法获得更好的硬件加速。
如果您现在需要与现有软件的更多兼容性和互操作性,请从SHA-2系列中选择SHA-256。但是,如果您的SHA-2安全性被破坏,请准备好迁移到SHA-3函数。
如果您想要更高的速度和安全性,并且互操作性不是问题,那么请选择具有512位消息摘要的BLAKE2b函数。它具有非常好的安全性,并且明显快于64位CPU上的BLAKE2s、SHA-2 和 SHA-3函数。
至此,我们了解了OpenSSL支持的不同加密哈希函数的属性,以及在哪种情况下使用什么哈希函数的建议。但理论够了!让我们做一些实际的消息摘要计算。
在命令行上使用OpenSSL计算消息摘要很容易。您需要使用openssl工具的 openssl dgst
子命令。此子命令的文档可在 openssl-dgst
手册页上找到:
xxxxxxxxxx
$ man openssl-dgst
让我们生成一个示例文件以进行进一步的消息摘要计算:
xxxxxxxxxx
$ seq 20000 >somefile.txt
要检查您的OpenSSL版本支持哪些消息摘要算法,可以使用 -list
开关:
xxxxxxxxxx
$ openssl dgst –list
Supported digests:
-blake2b512 -blake2s256 -md4
-md5 -md5-sha1 -ripemd
-ripemd160 -rmd160 -sha1
-sha224 -sha256 -sha3-224
-sha3-256 -sha3-384 -sha3-512
-sha384 -sha512 -sha512-224
-sha512-256 -shake128 -shake256
-sm3 -ssl3-md5 -ssl3-sha1
-whirlpool
【FreeBSD的openssl少了sm3】
让我们计算SHA3-256摘要:
xxxxxxxxxx
$ openssl dgst -sha3-256 somefile.txt
SHA3-256(somefile.txt)= 658656e129914052546af527ba8cf573ab27fb47551a0682ffcf00eeaf56d32b
您可能还记得,消息摘要计算是一个确定性操作。对于相同数据,无论我们计算多少次摘要,摘要总是相同的。
这就是它有多容易。现在,让我们学习如何用 C代码 计算相同的摘要。
我们将实现 digest
程序,该程序将计算文件的SHA3-256消息摘要。
我们的 digest
程序将只接收一个命令行参数:要散列的输入文件的名称。
与上一章中的对称加密一样,让我们制定一个计算消息摘要的高级计划:
与对称加密类似,OpenSSL包含用于消息摘要计算的新 EVP API 和旧的、不推荐使用的低级API。EVP API的函数以 EVP_
前缀开头,而低级API的函数以特定于哈希函数的前缀开头,如 MD5_
或 SHA256_
。我们在前一章中讨论了旧的、不推荐使用的API的缺点,因此我们在此不再讨论。与对称密码一样,我们将使用EVP API进行消息摘要计算。即使我们想使用低级API函数,我们也无法将它们用于SHA3-256,因为SHA-3算法相对较新,旧的API不支持它们。
让我们实现我们的 digest
程序:
首先,我们必须创建并初始化消息摘要计算上下文:
xxxxxxxxxx
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
EVP_DigestInit(ctx, EVP_sha3_256());
然后,我们必须将输入文件中的数据馈送到上下文中:
xxxxxxxxxx
while (!feof(in_file)) {
size_t in_nbytes = fread(in_buf, 1, BUF_SIZE, in_file);
EVP_DigestUpdate(ctx, in_buf, in_nbytes);
}
一旦所有数据都被馈送到上下文中,我们必须完成计算,并将消息摘要放入unsigned char数组中:
xxxxxxxxxx
const size_t DIGEST_LENGTH = 256 / 8;
unsigned char md[DIGEST_LENGTH];
EVP_DigestFinal(ctx, md, NULL);
一旦我们获得了消息摘要,我们就不再需要 EVP_MD_CTX
对象了。我们必须释放它以避免内存泄漏:
xxxxxxxxxx
EVP_MD_CTX_free(ctx);
唯一要做的就是打印计算出的消息摘要。让我们以与openssl-dgst子命令相同的格式打印它:
xxxxxxxxxx
printf("SHA3-256(%s)= ", in_fname);
for (size_t i = 0; i < DIGEST_LENGTH; ++i) {
printf("%02x", md[i]);
}
printf("\n");
与对称加密相比,消息摘要计算不需要很多参数,如密钥、初始化向量或填充类型。加密散列只需要散列函数类型和输入数据。对输入数据格式没有要求,任何比特流都可以被散列。因此,计算消息摘要很难失败。如果您的程序在哈希方面遇到故障,最可能的原因是输入/输出错误或初始化错误,例如特定的哈希函数未被编译到您的OpenSSL版本中,这意味着消息摘要的计算无法初始化。
digest
程序的完整源代码可以在GitHub的 digest.c 文件中找到:https://github.com/PacktPublishing/Demystifying-Cryptography-with-OpenSSL-3/blob/main/Chapter03/digest.c.
让我们在上一节中使用的 somefile.txt 文件上运行摘要计算程序:
xxxxxxxxxx
$ ./digest somefile.txt
SHA3-256(somefile.txt)= 658656e129914052546af527ba8cf573ab27fb47551a0682ffcf00eeaf56d32b
正如我们所看到的,计算的摘要与 openssl dgst
子命令计算的摘要相同。这证实了我们的程序按预期运行。
在本章中,我们学习了什么是加密哈希函数和消息摘要。我们还了解了对哈希函数执行的常见攻击以及如何评估哈希函数的安全性。然后,我们回顾了OpenSSL支持的加密哈希函数及其提供的安全性。我们通过提供一条关于在哪些情况下使用哪些哈希函数的实用建议来完成我们的审查。
在实践部分,我们学习了如何使用 openssl dgst
子命令计算消息摘要。最后,我们学习了如何开发一个使用OpenSSL库进行消息摘要计算的C程序。我们将我们的程序生成的消息摘要与openssl dgst
生成的消息文摘进行了比较,并确认我们的摘要生成正确。
在下一章中,我们将了解消息身份验证码(Message Authentication Codes,MACs)和基于哈希的消息身份验证代码(Hash-based Message Authentication Codes,HMACs)。HMAC基于加密哈希函数,这也是为什么在本章中学习哈希函数和消息摘要很重要的原因之一。