☰
哈希算法双雄:SHA-256与SHA-3的对比与选型
2026/10/14 13:34:26 网站建设 项目流程

SHA-256和SHA-3,两个词在开发者社区里出镜率极高。有人把它们当成同一件事的两代版本,觉得SHA-3一定"更好";有人以为哈希算法就是加密,能把密码变成一串看不懂的字符串;还有人到现在都分不清大写的SHA-3和以太坊里那个Keccak-256到底差在哪。我参与过不少系统安全评审,几乎每次都要先花几分钟把这些概念捋直,因为很多所谓的安全隐患,根源不在代码,而在对哈希算法本质的误解。这篇文章不绕弯子,把SHA-256和SHA-3这两套当今最重要的哈希算法家族从头拆开:它们各自怎么来的、内部结构有什么根本差异、性能和安全性如何对比、项目选型到底该听谁的。适合所有写代码会碰到哈希的开发者、做架构决策的技术负责人,以及想系统理解哈希算法的安全从业者。

1. 哈希函数到底在做什么:先把这个基础说透

1.1 四个核心性质决定了哈希的"人设"

哈希函数做的事情一句话就能说清:把任意长度的输入,压缩成固定长度的输出,这个输出一般叫摘要(digest)。但"压缩"不是它的全部,真正让它变成密码学工具的,是下面四个性质。

第一是确定性。同一个输入,不管跑到哪台机器上算,永远得到同一个摘要。这是所有校验类应用的地基。第二是抗原像(单向性)。给你一个摘要,你几乎不可能反推出原始输入。第三是抗第二原像。已知某个输入和它的摘要,你找不到另一个不同输入,使得摘要相同。第四是抗碰撞。你找不到任意两个不同输入,让它们的摘要一样。

这四个性质听起来简单,实际含义很深。比如"抗碰撞"这件事,重点不在于"很难碰巧撞上",而在于"刻意去找也找不到"。哪怕设计者本人想伪造一对碰撞,在安全强度足够的前提下,也是计算上不可行的。还有个常被忽略的"雪崩效应":输入哪怕只翻转一个bit,输出的摘要都会面目全非。这个特性保证了局部的微小差异不会被局部地反映到摘要里,否则哈希函数很容易被统计分析攻破。

1.2 哈希不是加密,两者方向完全相反

很多人把哈希和加密混在一谈。加密是可逆的:你有密钥,就能把密文还原成明文。哈希是不可逆的:输入到摘要是一条单行道,没有密钥一说,也没有还原操作。你可以把哈希理解成一个粉碎机,而不是一个保险箱;文件进去出来的是固定长度的"指纹",原始内容已经无法恢复。

这个误解带来一个非常典型的错误:有些系统把用户密码直接做一次SHA-256就存进数据库,以为这就是安全存储了。问题是SHA-256太快了,普通GPU一秒钟能算几十亿次,拿到哈希值列表的攻方可以瞬间跑完整个字典。诚实的建议是:存密码要用专门的慢速KDF,比如bcrypt、scrypt、argon2,它们故意设计成计算成本高昂,让暴力破解的性价比降到极低。SHA-256这样的通用哈希,在密码存储场景里反而不合适。这是我在评审中见到最多的认知偏差,先在这里敲个警钟。

1.3 哈希真实落地的地方

在正确理解了哈希的本质之后,它的应用场景其实非常广:

  • 文件完整性与数据校验:下载镜像后比对摘要,确认文件没有被篡改或传输损坏。
  • 数字签名:先对消息做哈希摘要,再对摘要签名。签名算法处理固定长度的摘要,性能和安全边界都更好控制。
  • Merkle树与区块链:把一批数据的哈希逐层归并成一棵哈希树,根哈希可以代表整批数据。比特币、Git、分布式存储都用这个思路。
  • 消息认证码(HMAC):用密钥参与哈希计算,验证消息的来源和完整性。
  • 去重与内容寻址:内容相同即摘要相同,可以直接用摘要作为地址,节省存储。

对这些场景,SHA-256和SHA-3都是合格的选手。但"合格"背后,两者的设计哲学和工程特征差异巨大,这就是下面要展开的核心内容。

2. SHA-256的身世:从MD5到行业默认的演进路径

2.1 前辈们是怎么倒下的

SHA-256不是从石头缝里蹦出来的。它是哈希算法谱系里的后辈,这个谱系最早是MD4、MD5和SHA-1。

MD5在上世纪90年代一度是绝对主流,128位摘要,速度快,到处都是。2004年,王小云团队给出了MD5的碰撞攻击方法,此后攻击不断改进。2007年前后,选择前缀碰撞也从理论变成现实,攻击者甚至可以构造两个攻击者自选内容的文件,让它们的MD5相同。2012年的Flame恶意软件利用伪造的MD5签名证书,让整个业界彻底正视了MD5的死亡。

SHA-1是接替MD5的"正统传承",160位摘要,同样采用Merkle-Damgård迭代结构,身处同一套设计框架内。2005年出现了理论攻击,2017年Google和CWI研究所发布了SHAttered:两个内容不同的PDF文件,拥有同一个完整SHA-1哈希。这是全世界第一次看到完整的SHA-1碰撞,也宣告了SHA-1在抗碰撞层面已经失去意义。

回头看这段历史,教训很清楚:设计者的智力再高,也挡不住后来者几十年的持续分析。所以哈希算法领域形成了一条潜规则——不要只押注一种结构,要在安全边际上留足余量。

2.2 SHA-2家族和SHA-256为什么笑到了最后

SHA-2是NSA设计、NIST发布的家族,包含SHA-224、SHA-256、SHA-384、SHA-512等变体,2001年正式进入FIPS 180-2标准。它延续了Merkle-Damgård的迭代思路,但在细节上做了大量加固:更长的摘要长度、更强的消息扩展算法、更复杂的压缩函数轮函数。

SHA-256之所以成为"行业默认",有几个很现实的推力。首先是安全强度的甜点位:256位摘要意味着128位的碰撞安全强度,刚好和AES-128密钥强度对齐,被业界广泛接受。其次是硬件生态,近十年来的x86处理器普遍内置了SHA-NI指令集,SHA-256在有硬件加速的CPU上速度快得离谱,这在软件层面对其他哈希形成了碾压性优势。再其次是协议生态,TLS 1.2/1.3、X.509证书、Git、Bitcoin全都深度绑定了SHA-256,一旦成了事实标准,迁移成本就不是单个项目能承受的了。

有个细节值得展开:SHA-512在纯软件环境下的速度其实经常比SHA-256还快,因为它一次处理64位字和更大的消息块,轮数与数据块的比例更优。但SHA-512的摘要太长,不是所有场景都方便。于是标准里还有个冷门变体SHA-512/256:取SHA-512的输出截断为256位,既保留了64位路径的高性能,又规避了传统Merkle-Damgård结构在输出与内部状态一致时带来的长度扩展问题。如果你的项目想在纯软件环境里用SHA-2家族,SHA-512/256值得多看一眼。

2.3 安全强度的标尺:生日界与128位碰撞安全

为什么要强调256位摘要只提供128位碰撞安全?这里面的数学很关键。按生日悖论,给定一个n位输出的哈希函数,随机找大约2^(n/2)个输入,就有很大概率发生一次碰撞。所以对256位输出来说,找碰撞的通用攻击复杂度是2^128。这依然是天文数字,但和很多人直觉里"256位就是2^256强度"的想象差了一倍。记住这条标尺:摘要位数减半,才是实际的碰撞安全强度。

量子计算的影响也顺带提一句:Grover算法可以把抗原像搜索从2^256降低到2^128,虽然对现代密码系统还构不成现实威胁,但这也意味着密码学家在设计新算法时,普遍会刻意拉高安全边际,而不是算着刚好够用就行。

3. SHA-3诞生的真正原因:一场没有受害者的危机

3.1 2000年代中期:哈希算法的"危机前夜"

SHA-3的诞生背景可能是整个密码学史上最特别的一幕:它不是因为有谁被攻破了才启动的,而是在SHA-256依然安全的时期,学术界和标准化机构主动发起的"备胎计划"。

2004到2008年是哈希算法的多事之秋。MD5碎了,SHA-1的理论进展不断,更麻烦的是针对Merkle-Damgård这个"框架"本身的结构性攻击陆续出现:Joux的多碰撞攻击、Kelsey-Schneier的多次原像攻击、herding攻击(牧群攻击)。这些攻击当时还没有威胁到SHA-256的实际安全,但它们揭示了一个令人不安的事实:所有采用Merkle-Damgård结构的迭代哈希,理论上共享某些结构弱点。如果有一天攻击者找到一个能同时打穿Merkle-Damgård家族的通杀方法,整个基于SHA-2的生态都会瞬间暴露。这个风险,比单个算法被攻破更让密码学家睡不着。

3.2 五年的公开竞赛,Keccak如何胜出

NIST在2007年宣布举办哈希算法公开竞赛,目的非常明确:选出一个结构上与SHA-2完全不同、能长期作为平行替补的新标准。全球共收到64份候选设计,第一轮过后剩51个,第二轮收敛到14个,第三轮留下5个决赛选手:BLAKE、Grøstl、JH、Keccak和Skein。2012年10月,NIST宣布Keccak胜出,2015年8月发布正式标准FIPS 202,也就是我们今天说的SHA-3。

Keccak由Guido Bertoni、Joan Daemen、Michaël Peeters和Gilles Van Assche设计。Joan Daemen正是AES加密标准的设计者之一,这个出身让它天然带了一层光环。它在五个决赛选手里是结构差异最大的一位:不依赖Merkle-Damgård链式迭代,而是基于一种全新的海绵构造(Sponge Construction)。它的轮函数Keccak-f极度规整,分析者可以把攻击焦点集中在一个纯粹置换上,安全性论证非常清晰。再加上它可以输出任意长度摘要、支持可扩展输出函数,设计弹性远超其他决赛者。最终NIST选择它,很大程度上就是看中了这种"换个活法"的结构多样性。

3.3 备胎逻辑,不是替代逻辑

理解SHA-3的存在价值,必须放下"新版替代旧版"的思维惯性。NIST从未宣布SHA-256退休,直到今天,FIPS 180-4和FIPS 202并列有效,SHA-256和SHA-3是平等的两个标准。SHA-3的定位从一开始就是"平行备胎":万一未来真的出现了针对Merkle-Damgård家族的通用攻击,整个社会还能无缝切换到一点也不一样的SHA-3。

这也是"双雄并立"这个说法的真正来源。双雄不是新旧之争,而是两条不同的技术路线并行服役,互为备份。今天你用SHA-256,不代表你比用SHA-3的人落后;你用SHA-3,也不意味着你比前者激进多少。理解这层背景,后面的选型章节才讲得通。

4. Merkle-Damgård与海绵构造:两代设计的分水岭

4.1 SHA-256内部:一台链式压路机

SHA-256的处理过程可以拆成三步。首先做填充:在消息末尾先补一个bit的1,再补一串0,最后补64位的原始消息长度(按bit计),让总长度正好是512的整数倍。注意即便消息长度恰好命中512的倍数,也必须执行填充,这是为了消除长度边界歧义。然后按512位一块切分消息。最后逐块送入压缩函数:当前512位块和上一轮得到的256位链值一起,经过64轮轮函数运算,得到新的256位链值。

内部细节是这样的:压缩函数把512位消息块扩展成64个32位字的消息调度表,然后每一轮用Ch、Maj、Σ0、Σ1这些位运算组合,配合64个预先定义好的K常量做混合。本质上,SHA-256是把一个精心设计的密码学轮函数反复碾压64次,再与上一轮的链值相加。每一块的输出都依赖前面所有块的状态,这就是Merkle-Damgård的"链"的含义:任何一块被篡改,后续所有链值都会变,最终摘要必然不同。但也正因为如此,SHA-256没法天然并行,只能老老实实一块接一块地跑。

4.2 SHA-3内部:一块会吸水的海绵

SHA-3的路子完全不同。它维护一个1600位的宽状态,这个状态在概念上被分成两部分:收取输入的rate部分,和负责安全保障的capacity部分。rate加capacity等于1600,不同变体切分比例不同。

海绵构造分两个阶段。吸收阶段:把消息填充后按rate大小切块,每块先与rate部分做异或,再对整个1600位状态执行一次Keccak-f置换,如此循环直到消息全部吸收完毕。挤压阶段:从状态中抽取rate位作为输出,如果输出长度超出rate,就再执行一次置换,继续抽取,直到凑够想要的长度。这个设计直观理解就是海绵吸水再挤水,简单、优雅,而且天然支持任意长度输出。

具体到标准变体,参数分配如下表:

算法rate(字节)capacity(字节)目标安全强度
SHA3-22414428112位碰撞/224位原像
SHA3-25613664128位碰撞/256位原像
SHA3-38410496192位碰撞/384位原像
SHA3-51272128256位碰撞/512位原像
SHAKE12816832128位安全
SHAKE25613664256位安全

Keccak-f置换的核心是24轮完全相同的轮函数,每轮由θ、ρ、π、χ、ι五个步骤组成,基本操作就是64位字的异或、循环移位和按位逻辑。整个轮函数没有加法进位,没有条件分支,非常适合硬件并行化,也方便写成抗侧信道的恒定时间实现。SHA-3的安全论证从来没有把宝押在复杂的消息扩展上,而是押在一个纯粹置换的混淆扩散能力上——这跟SHA-256的哲学是根本性的不同。

4.3 长度扩展攻击:MD结构绕不过去的坎

Merkle-Damgård结构有个著名的结构性缺陷:长度扩展攻击。因为哈希函数的输出就是内部链值的最终态,攻击者在不知道原始消息m的情况下,只要拿到H(m),就能构造出H(m‖填充‖追加内容)。这个攻击不需要破解任何东西,纯粹是结构上的洞。

最常见的受害场景是"密钥+消息"的朴素MAC构造:MAC=H(secret‖message)。攻击者虽然不知道secret,但可以根据已获知的MAC值,在消息后面拼接内容并计算出合法的拼接后MAC,等于绕过了完整性验证。这也是为什么业界必须发明HMAC:它要做两次哈希包裹,就是为了消除长度扩展攻击的可能性。如果你坚持用SHA-256并想避免这个坑,可以选择HMAC-SHA-256,或者改用SHA-512/256这类输出不等于内部全状态的截断变体。

SHA-3天然免疫长度扩展攻击。原因是输出只来自状态里的rate部分,capacity部分从不公开。攻击者从摘要里只能反向恢复rate那部分,无法拼齐整个1600位状态,自然也就没法在海绵上"续写"。这是我个人认为SHA-3最值得称道的设计成果:它从源头消灭了一整类攻击,而不是通过外部包装来修补。

5. 工程视角下的实测对比:性能、安全性与生态位

5.1 性能:SHA-NI让SHA-256在通用CPU上"开挂"

聊性能不能脱离硬件谈。在通用x86 CPU上,SHA-256是吃了硬件加速红利的:SHA-NI指令集让它在不碰内存的情况下完成核心轮运算,吞吐量可以做到每秒数GB级别。SHA-3目前没有这么普及的原生硬件指令支持,纯软件优化下,它的吞吐量通常在每秒几百MB到1GB出头的量级,视实现质量而定。也就是说,在大多数服务器和桌面场景里,SHA-256实测更快,这个差距在大规模哈希场景下是能明显感知的。

这里有三个补充观察。

第一,SHA-512在64位平台上的纯软件速度往往超过SHA-256,因为它一轮处理64位字,轮数相对更少。如果不介意摘要太长,SHA-512是纯软件场景里SHA-2家族中性能更优的选择。

第二,SHA-3在嵌入式平台上的表现比在PC上更值得期待。Keccak置换全是位运算和小状态操作,对硬件面积和功耗非常友好,在一些MCU和FPGA上,SHA-3的性能反而不输甚至超过SHA-256。当然,32位MCU处理1600位状态里的64位lane需要额外小心,搞不好就要做位交织优化,否则吞吐量会掉得很厉害。

第三,SHA-3软件实现的上限很大程度上取决于对Keccak-f置换的优化程度。用最普通的朴素写法跑,结果一定难看;用了成熟的位切片或向量化实现,差距会缩小很多。所以比较性能时,别拿一个没优化的开源小库当结论,多测两个成熟实现再下判断。

5.2 安全边际:一个赌经验,一个赌结构

SHA-256的安全背书来自二十年以上的持续分析和实战检验。到今天,完整轮数的SHA-256没有任何可行的攻击方法,这一点基本可以放心。但它的"软肋"在于结构层面:长度扩展、多碰撞、牧群攻击等对Merkle-Damgård家族的通用分析虽然还没伤到它,却在理论上留下了阴影。SHA-512/256这类截断变体能缓解一部分,但改变不了家族基因。

SHA-3的处境刚好相反。它在结构上做到了"换赛道",1600位的宽状态提供了异常充沛的安全冗余,免疫长度扩展,设计清晰到可以针对单一置换做严格分析。但它的服役时间比SHA-256短,实战样本量也少。宽状态和大冗余虽然令人安心,但密码学的核心经验之一就是:一个算法真正的安全,最终要靠时间的检验。

一个公道话是:对绝大多数业务场景,SHA-256和SHA-3的实际安全性差距约等于零,你没有必要为了"更安全"而强行从SHA-256切到SHA-3。真正的差异体现在功能弹性和面向未来的结构多样性上,而不是"谁能防住今天的攻击"。

5.3 一个容易踩的坑:以太坊的Keccak-256不是SHA3-256

这个坑我见过太多人踩,值得单独拎出来说。NIST最终版的SHA-3在Keccak原始提交版的基础上,对填充规则做了微小调整——给消息加上了一个域分隔后缀,用来区分SHA-3、SHAKE等不同功能。调整后的标准SHA-3和竞赛时期的原始Keccak已经不兼容了。

以太坊在标准还没定稿时就采纳了原始Keccak,所以Solidity里的keccak256、web3.utils.sha3,以及大量智能合约工具链里默认的"sha3函数",使用的都是原始Keccak,而不是FIPS 202定义的SHA3-256。这两者对空字符串的摘要差别是这样的:

函数空字符串摘要
SHA3-256(FIPS 202)a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a
以太坊Keccak-256c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470

如果你在合约项目里用OpenSSL的openssl dgst -sha3-256去核对链上哈希,永远对不上。排查这类问题的时候,先搞清楚目标平台到底用的是标准SHA-3还是原始Keccak,再验证测试向量,能省掉大半天无头苍蝇式的排查。

5.4 SHA-3家族的扩展能力:SHAKE、cSHAKE、KMAC

SHA-3真正的差异化优势,在于它能输出任意长度。SHAKE128和SHAKE256是可扩展输出函数,需要32字节就拿32字节,需要64字节就拿64字节,输出的长度上限只受实际使用约束。这在设计密钥派生、挑战响应、流密码类应用时非常顺手,SHA-2家族完全没有对应能力。

更进一步,SHA-3家族还有cSHAKE和KMAC。cSHAKE允许你给哈希配置一个自定义字符串,实现域分离:同一套底层置换,不同的上下文因为自定义字符串不同而互不等效,这比"手动拼前缀"这种民间做法安全得多,也标准得多。KMAC则是基于cSHAKE构造的消息认证码,天然抗长度扩展,接口比HMAC更简单。如果你的新协议需要同时解决"摘要""可变长度输出""认证""域分离"这四件事,SHA-3家族可以一套体系全部覆盖,这是它真正的杀手锏。

6. 双雄并立的现实格局:选型与落地建议

6.1 存量系统:别为了"新"而迁移

接手现有系统时,我的第一原则是:如果系统里的SHA-256跑得好好的,没有安全告警,没有兼容性需求,就不要动它。这里说的不动,包括两层意思:不要为了追赶潮流把接口拆了换SHA-3,也不要自作主张搞什么"双哈希增强"。

迁移一个哈希算法绝不只是换个函数名。文件摘要格式、协议字段、证书链、数据库里已存的摘要值、跨系统对接方的实现版本,全部要跟着变。只要有一端没跟上,线下算的和线上验的就不会对。SHA-256今天依然是NIST在册标准、依然是TLS和证书生态的默认选择,你因为"感觉它不够新"而迁移,属于给自己找事。

真正值得做的是"密码学敏捷性"设计:把哈希调用封装在接口后面,在协议字段里带上算法标识,确保未来某个时刻万一真要切换,不需要推翻整个架构。这是花小钱买保险,比一次性大迁移划算得多。

6.2 新系统与特定场景:这些时候优先考虑SHA-3

如果你正在设计一个没有历史包袱的新系统,下面几个场景我会认真建议你用SHA-3:

第一,你需要可变长度输出。密钥派生、随机挑战、流式签名场景里,SHAKE256比SHA-256加一堆手工截断操作干净得多,也安全得多。

第二,你需要域分离。同一份数据要哈希成多种用途,或者同一个协议里要区分多个上下文,cSHAKE和KMAC提供的规范域分离,能有效防止跨上下文的字节复用攻击。

第三,你在做硬件或嵌入式设计。Keccak置换的资源占用低、并行度高,在ASIC或FPGA上实现起来非常顺手,很多低功耗场景里反而更适合。

第四,你对长期风险特别敏感。数据要保存十年以上,协议要服役很久,你希望尽量降低"Merkle-Damgård家族某天被通杀"这种黑天鹅事件的影响。SHA-3的宽状态和异质结构,给了你在SHA-2之外的第二条生路。

6.3 关于"混用"的提醒和我的经验

一个常见的误解是:把SHA-256(m)和SHA-3(m)拼接起来,安全性是不是就叠加了?答案没这么简单。Joux在2004年就证明过,迭代哈希的多碰撞攻击让简单拼接的组合强度远低于直觉预期。除非协议标准里有明确规定的组合构造,否则不要自己发明"双哈希加固",这种民间配方往往只给你心理安慰,还给实现和验证带来双倍复杂度。真想在协议里同时保留两条路线,正确做法是设计成可选算法,而不是在同一处同时上两个哈希。

最后分享几条实操层面的经验。工具上,Python的hashlib直接支持sha3_256和sha3_512,也有shake_128和shake_256;OpenSSL用openssl dgst -sha3-256 file就能算;Go在较新版本的标准库里已经直接提供crypto/sha3,更早的版本用golang.org/x/crypto/sha3。无论用哪个语言,上线前都拿NIST官方测试向量对一遍,尤其是截断变体和SHAKE这种带域分隔的,错一个padding字节都会导致全线对不上。实现层面,在嵌入式或低层环境手写Keccak时,务必保证恒定时间,避免分支依赖输入数据。一个诚恳的建议是:去理解这两个算法的结构差异,比记住十组性能数字更有价值。结构决定了你面对的安全威胁类别,也决定了你在协议里能怎么用它;性能数字过两年就会变,结构哲学不会轻易过时。这大概就是SHA-256与SHA-3"双雄并立"给我的最大启示。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询