☰
SM2、SM3、SM4国密算法区别与数字信封工程实践
2026/10/26 3:04:03 网站建设 项目流程

简介:面向密码学学习者与软件开发者的国密算法资料包,围绕SM2椭圆曲线公钥密码、SM3杂凑算法与SM4分组密码展开,汇集算法原理文档、多语言实现与验证工具,可用于课程实验、接口联调、算法移植及国密改造前的技术自查。压缩包共78个文件,约6.99MB,以C源码与头文件、PDF技术规范、Java和JavaScript实现为主,辅以docx需求说明、MFC验证工具及少量可执行文件与动态库,便于从规范阅读到代码对照逐步排查。已有854人学习下载。读者可依据其中的测试代码和工具验证加解密、签名验签与杂凑结果,结合文档理解推荐曲线参数、分组模式及实现注意事项,也能参考不同语言版本完成跨平台移植与联调,适合作为国密算法入门与工程实践的对照材料。

1. 一句话说清国密算法:SM2、SM3、SM4 是三种不同用途的密码工具

如果只记一句话:SM2 负责“证明这是你”,SM3 负责“给数据按个指纹”,SM4 负责“把数据锁起来”。这三个名字常被连在一起说,让人以为是同一套东西的三个版本,其实是三件完全不同的工具,好比门锁、指纹仪和封条,各干各的活,谁也不能替谁。搞懂它们的分工,比背参数有用得多:看到一份协议文档里同时出现这三个词,你能立刻判断出哪一步在验身份、哪一步在防篡改、哪一步在真正加密数据。这篇就把这套算法拆成能听懂的样子,顺便说清它和 RSA、SHA-256、AES 这些熟面孔的边界在哪。

2. 三件工具各站哪班岗:SM2 签名、SM3 摘要、SM4 加密

2.1 从一条“转账指令”看三个算法怎么接力

假设某个业务系统里,客户端要把“向账户 B 转 100 元”这条指令发给服务端,并且要满足三个诉求:服务端得确认这指令真是本人发的、得确认中途没被改过、还得保证别人截获了也看不懂内容。这三个诉求,正好对应三件工具。

流程大致是这样:客户端先用 SM3 把报文压成一个 256 位的摘要,相当于给这段话按了个指纹;再用自己的 SM2 私钥对这个摘要做签名,等于在指纹旁边签上名;接着随机生成一把临时的 SM4 密钥,用 SM4 把整条报文加密;最后用服务端的 SM2 公钥把这把临时的 SM4 密钥加密,附在密文后面一起发出去。服务端收到后反着来一遍:先用自己的 SM2 私钥解出 SM4 密钥,用 SM4 解密报文,再用客户端的 SM2 公钥验签,重新算一遍 SM3 摘要对比。

这个接力里,SM2 干的是“身份”和“钥匙传递”,SM3 干的是“完整性”,SM4 干的是“保密性”。三个角色的边界非常清晰:SM2 慢但能解决信任问题,SM4 快但解决不了“钥匙怎么给对方”的问题,SM3 不可逆所以只能校验不能还原。看懂这个接力,基本就看懂了国密算法体系的骨架。

提示:很多初学者把“加密”和“签名”当成一回事,其实两者目的相反。加密是让特定的人能看,签名是让所有人能验;加密用对方的公钥,签名用自己私钥。

2.2 SM2:非对称算法,管身份认证与密钥分发

SM2 属于公钥密码,也就是常说的非对称算法。它会生成一对钥匙:私钥自己攥着,绝不外传;公钥可以随便发给任何人。用私钥签出来的东西,只有对应公钥能验过;用公钥加密的东西,只有对应私钥能解开。这个不对称性,是所有信任关系的起点。

它底层的数学难题是椭圆曲线离散对数问题:在一条特定曲线上,已知起点和一个倍数,算出终点很容易;但只给你起点和终点,反推那个倍数,以现有算力基本做不到。SM2 的密钥长度是 256 位,这个长度在安全强度上大致能对标 3072 位的 RSA。换句话说,别人用一大串数字才能达到的强度,它用更短的钥匙就做到了,带来的好处是存储小、运算快、握手省流量。

SM2 一份算法标准里其实塞了三件事:数字签名、公钥加密、密钥交换。签名用于验身份和防抵赖,公钥加密用于传一小段关键数据(比如上一节那把 SM4 密钥),密钥交换用于双方协商出同一个会话密钥。三者数学基础相同,但流程和输出格式完全不同。工程上最容易出问题的地方就在这:把签名接口当加密接口用,或者把加密结果的字段顺序按签名的习惯去解析,都会直接失败。

2.3 SM3:单向摘要,管“这东西有没有被动过”

SM3 是哈希算法,也叫杂凑算法、摘要算法。它的工作方式是:不管输入是一字节还是一吉字节,最后都吐出一段固定 256 位(32 字节)的结果。这个过程是单向的,拿到摘要没法反推原文;同时它对抗碰撞有要求,想找到两段不同内容却算出同一个摘要,代价大到不现实。

它有三个特性值得记住。第一是雪崩效应:原文改动一个比特,输出结果面目全非,所以它能灵敏地反映任何篡改。第二是定长输出:再大的文件,摘要都是 32 字节,适合做比对。第三是确定性:同样的输入,任何时候算出来都一样,所以两边各算一遍就能核对。

实际用途主要有几类:文件完整性校验,下载完对一下摘要就知道有没有损坏;口令存储,数据库里存口令的摘要而不是明文;数字签名里的杂凑,因为 SM2 直接签长报文效率低,先算摘要再签摘要;消息认证码,配合密钥做 HMAC-SM3,防篡改的同时还能验身份。

这里有个必须提醒的点:裸哈希只能防“偶然损坏”和“无密钥的篡改”,防不了“有心的伪造”。如果攻击者能同时改内容和摘要,你对比是发现不了的。要防这个,得用带密钥的 HMAC,或者在摘要外面再套一层 SM2 签名。

2.4 SM4:对称分组密码,管真正的大批量数据

SM4 是对称算法,加密解密用同一把钥匙。它的分组长度是 128 位,密钥长度也是 128 位,内部迭代 32 轮。所谓分组密码,就是把数据切成一块一块(每块 16 字节),逐块处理。因为是对称的,它的速度比 SM2 快两个数量级以上,适合加密真正的大批量数据:文件、数据库字段、通信报文、视频流。

对称算法的核心难题从来不是算法本身,而是“钥匙怎么安全地到对方手里”。你在客户端生成一把 SM4 密钥,怎么让服务端也拿到同一把,又不被中间人看见?这就回到了 2.1 节的做法:用 SM2 把 SM4 密钥加密后传过去。这套“非对称传钥匙、对称传数据”的组合,业界叫数字信封,是几乎所有安全通信协议的通用套路。

还有一个绕不开的话题是工作模式。SM4 只定义了“一块怎么加密”,多块怎么串起来由模式决定。ECB 模式(每块独立加密)绝对不能用于真实数据,因为相同的明文块会产生相同的密文块,图案会泄漏。常用的是 CBC(前一块密文参与下一块加密,需要初始向量)、CTR(把分组密码变成流密码)、GCM(加密同时出认证标签,能防篡改)。选错模式,算法再标准也白搭。

3. 拆开看内部:曲线、压缩函数、轮迭代各解决了什么

3.1 SM2 的椭圆曲线:一对钥匙是怎么算出来的

SM2 建立在一类叫短 Weierstrass 形式的椭圆曲线上,方程形如 y² = x³ + ax + b,其中 a、b 是固定常数,所有参数都写进了公开标准。曲线上有一些点,其中一个被指定为基点 G,还有一个大素数 n 表示基点的阶。私钥 d 就是 1 到 n-1 之间随机挑的一个整数,公钥 P 则是把基点累加 d 次的结果。

正着算 P = dG 很快,反着从 P 和 G 推 d 极难,这就是全部安全性的来源。签名时,算法会引入一个一次性的随机数 k,算出一个曲线点,再结合待签摘要和私钥,最终得到两个 256 位整数,通常记作 r 和 s。验签时用公钥、摘要和 (r, s) 做一遍等式校验,成立就说明签名有效。

加密流程的输出则是一个三元组:C1 是一个曲线点(用于让接收方算出解密所需的中间量),C3 是一段摘要(用于校验密文没被篡改),C2 是真正的密文。这里有个工程上特别容易出问题的地方:不同版本的标准和不同实现,对这三个字段的排列顺序可能不一致,有的是 C1C3C2,有的是 C1C2C3。跨系统对接时如果只对算法不对格式,解密就会得到一堆乱码。

3.2 SM3 的压缩函数:任意长度怎么变成 256 位

SM3 的结构是经典的“填充 + 分组 + 迭代压缩”。第一步填充:在消息末尾补一个 1,再补若干个 0,最后留 64 位记录原始消息的比特长度,让总长度凑成 512 位的整数倍。第二步,把消息切成一个个 512 位的块。

每个块进来,先做消息扩展:把 16 个 32 位字扩展成 68 个字,再额外生成 64 个用于轮函数的字。然后进入压缩函数,迭代 64 步。每一步都用到布尔函数、循环左移、模 2³² 加法这几样基础操作,把当前状态、扩展字、轮常量揉在一起更新 8 个 32 位寄存器。所有块处理完,这 8 个寄存器拼起来就是 256 位摘要。

值得注意的设计细节是:它沿用了 Merkle–Damgård 迭代结构,这种结构有一个理论上的弱点叫长度扩展攻击——知道 H(A) 的人,可以在不知道 A 的情况下算出 H(A 拼接某段数据) 的结果。所以在需要“带身份认证的完整性”时,不能直接裸用 SM3,而应该用 HMAC-SM3,靠密钥把攻击路径堵死。

3.3 SM4 的轮函数:128 位分组是怎么搅匀的

SM4 把 128 位明文拆成 4 个 32 位字,然后做 32 轮迭代。每一轮的轮函数由三部分组成:先是非线性变换,把 4 个字节并行送进 4 个 8 进 8 出的 S 盒,这是整个算法的混淆核心,没有它算法就是纯线性的、可被轻易破解;然后是线性变换,用循环移位和异或把 32 位内部搅匀;最后与轮密钥异或。

密钥扩展用的是几乎同一套结构,只是把输入换成一整把 128 位主密钥,配合固定参数和系统参数,迭代出 32 个轮密钥。解密时不需要另写一套逆运算,只要把 32 个轮密钥倒序使用即可,加解密共用同一段代码,这是非平衡 Feistel 结构带来的便利。

它的整体结构和 AES 属于同一大类,但细节完全不同:AES-128 是 10 轮,SM4 是 32 轮;两者的 S 盒构造不同,线性层不同,密钥扩展方式也不同。32 轮在软件实现上稍慢一点,但在硬件和查表实现里效率很好,而且轮数多意味着单轮差分特征的传播更难利用。这些差异决定了它们不可能互相替代。

3.4 一张表看清三者的参数与定位

算法类型分组长度密钥/输出长度典型用途速度量级
SM2非对称(椭圆曲线)无分组概念256 位数字签名、密钥交换、短数据加密慢,适合低频操作
SM3哈希(单向摘要)512 位输入块输出 256 位完整性校验、口令派生、签名杂凑中等,随数据量线性增长
SM4对称分组密码128 位128 位大批量数据加密、隧道加密快,适合高频大流量

看这张表的正确姿势不是比数字大小,而是看“位置”。SM2 解决的是信任和钥匙分发,天然只处理很短的数据;SM3 解决的是摘要,输出不可逆;SM4 解决的是吞吐,代价是双方得先有同一把钥匙。三者拼起来才是一套完整方案,单独拿出任何一个都覆盖不了真实业务的安全需求。

4. 最容易搞混的几组区别:别把三种工具当成一个东西

4.1 误区一:国密算法一定比国际算法更安全

这是最常见的想当然。易混点在于把“标准不同”直接等同于“强度不同”。会混是因为名字里带着“国”字,容易让人产生“新一代所以更强”的联想。实际上,SM2 的 256 位对标的是 RSA 3072 位、ECDSA P-256 这一档,SM3 对标的是 SHA-256 这一档,SM4 对标的是 AES-128 这一档,安全强度基本在同一水平线上,不存在代差。

区分方法很简单:判断一个算法的安全强度,看的是它的数学难题假设、密钥长度、轮数设计,以及是否经过足够长时间的公开分析,而不是看它属于哪套标准。真正决定安全性的往往不是算法选哪个,而是实现有没有漏洞、随机数够不够随机、密钥管理到不到位。这几个环节出问题,换任何算法都救不回来。

4.2 误区二:SM3 算出来的摘要能“解回去”

易混点是把哈希和加密混为一谈。会混是因为两者看起来都是“把明文变成一串看不懂的东西”。但加密是可逆的,只要钥匙在,密文能还原成明文;哈希是不可逆的,256 位输出对应的是无穷大的输入空间,理论上就不可能唯一还原。

区分方法:问一句“我需要把原文拿回来吗”。需要拿回来,用 SM4 或 SM2 加密;只需要比对是否一致,用 SM3 摘要。还有一个更隐蔽的区别是长度:SM3 无论输入多大,输出永远是 32 字节,所以它天然不能承载原文。有人试图用“撞库”的方式反查摘要,那本质上是在比对常见明文的摘要表,对随机性强的输入毫无作用。

4.3 误区三:SM2 的签名和加密可以互换使用

易混点在于两者都叫 SM2,都用同一对密钥。会混是因为接口层面经常共享密钥对象,让人以为调用可以随便切换。但签名和加密的设计目标相反:签名是“我用私钥签,大家用公钥验”,目的是证明来源;加密是“我用你的公钥加,你用私钥解”,目的是保密。

区分方法:看数据流向。数据从发送方流向接收方,且要保密,用接收方的公钥加密;数据要证明是发送方发的,且不需要保密,用发送方的私钥签名。混用的后果很实际:拿签名结果去当密文解,必然失败;更糟的是把加密接口当签名接口暴露出去,等于让攻击者拿到了一个用私钥做运算的通道,这在密码学里是大忌。

4.4 误区四:SM4 和 AES 能互相解密

易混点在于两者都是 128 位分组、128 位密钥的分组密码,长得太像了。会混是因为很多人以为分组密码的差别只在“名字”,只要长度对上就能通。但 SM4 和 AES 的 S 盒、线性变换、轮数、密钥扩展全都不一样,用 SM4 加密的数据拿 AES 去解,得到的只会是噪声。

区分方法:记住分组密码是“整套体系”,不是“一个参数”。判断两个实现能不能互通,不看密钥长度,看算法标识。对接时必须在协议里明确写清算法名,最好在密文前面带一个算法标识字节,避免以后升级或混用时靠猜。同理,SM4 也不能拿 DES、3DES 的密钥去凑。

4.5 误区五:密钥长度数字一样,安全强度就一样

易混点在于把“位数”当成唯一标尺。会混是因为对称算法里 128 位密钥对应 128 位安全强度,这个直觉被错误地套到非对称算法上。但 RSA 的 2048 位密钥和 SM2 的 256 位密钥,安全强度是同一档;反过来,如果有人拿 256 位的 RSA 密钥来对标 256 位的 SM2,那差距是天壤之别。

区分方法:对称算法的密钥位数基本等于安全强度(因为暴力破解就是穷举);非对称算法的密钥位数只反映数学问题的规模,需要查等价比对表。工程上更实用的判断是看标准推荐值:SM2 用 256 位、SM4 用 128 位,都是标准里定好的,不要自作主张缩水。曾经见过有人为了“省资源”把 SM4 密钥截成 64 位用,这等于把安全性砍掉一大半,遇到真正的攻击者根本挡不住。

5. 实际工程里怎么配合用:数字信封、验签与随机数

5.1 为什么大文件不用 SM2 直接加密

SM2 公钥加密有一个硬性限制:它只能加密很短的数据。原因是加密过程里要把明文映射到曲线上的一个点,映射本身有长度上限,而且每次运算都涉及大数模幂和曲线点乘,开销很大。拿它去加密一个几十兆的文件,速度慢到不可接受,还可能直接报错。

正确做法是数字信封:随机生成一把 128 位 SM4 密钥,用 SM4 的某个安全模式(比如 CBC 或 GCM)加密文件;再用接收方的 SM2 公钥把这把 SM4 密钥加密成一小段数据,附在文件头后面。接收方先解出 SM4 密钥,再解文件。整套流程里,慢操作只发生在几十字节上,快操作承担全部数据量。

下面是一段示意性的调用顺序(伪代码,仅表达流程,不对应任何具体库的 API):

# 发送方 sm4_key = random_bytes(16) # 128 位会话密钥,必须来自密码学安全的随机源 iv = random_bytes(16) # CBC 模式的初始向量,同样要随机且不可复用 cipher_text = sm4_cbc_encrypt(sm4_key, iv, plain_data) # 大数据走对称加密 wrapped_key = sm2_encrypt(receiver_pub_key, sm4_key) # 只加密 16 字节密钥 digest = sm3(cipher_text) # 对密文算摘要,用于传输完整性校验 send(wrapped_key, iv, cipher_text, digest) # 接收方 sm4_key = sm2_decrypt(receiver_priv_key, wrapped_key) # 先取回会话密钥 plain_data = sm4_cbc_decrypt(sm4_key, iv, cipher_text) # 再解大数据 assert sm3(cipher_text) == digest # 先校验再解密,或解密后校验,顺序按协议约定

这段代码里最容易被忽略的是两个随机量:sm4_key和iv。它们必须每次都重新生成,不能写死、不能用时间戳代替、不能跨会话复用。IV 复用会让攻击者通过对比密文推断出明文关系,这在 CBC 模式下是有名的攻击面。会话密钥复用更严重,一旦一把密钥泄漏,历史数据全部暴露。

5.2 一次完整的签名验签,卡在哪几个细节

签名看起来就是“算摘要、签摘要、验摘要”,实际上细节能卡住一整天。第一个细节是待签数据的构造。SM2 签名不是直接对消息摘要签名,而是先算一个叫 ZA 的值——它把签名者的身份标识、曲线参数、公钥揉在一起做一次杂凑,再把 ZA 和消息拼起来算最终摘要。不同实现对身份标识的默认值处理不一致,一方用默认值、另一方传了空字符串,验签就会稳定失败。

第二个细节是签名值的编码。SM2 签名结果是两个 256 位整数 r 和 s,有的实现把它们直接拼接成 64 字节裸数据,有的包装成 ASN.1 DER 序列。这两种格式长度不同、解析方式不同,混用必挂。对接前一定要在协议文档里写清楚是裸拼还是 DER。

第三个细节是字符编码。待签的报文字符串如果一方用 UTF-8 编码成字节、另一方用别的编码,字节序列不同,摘要自然不同,验签失败还找不到原因。这类问题在跨语言对接里特别常见,血泪经验就是:签名前先把报文序列化成确定的字节序列,双方对齐同一份规范。

注意:验签失败时不要第一反应怀疑算法实现有问题。绝大多数现场故障是编码、字段顺序、ZA 处理这三类“格式问题”,先排查这三项,比读源码快得多。

5.3 随机数是唯一不能省的那一步

SM2 签名每次都要用一个一次性的随机数 k。这个 k 必须满足两个条件:密码学安全随机、绝不复用。如果两次签名用了同一个 k,攻击者只要拿到这两组签名值,就能直接算出私钥——这不是理论推演,密码学史上这类事故出现过不止一次,而且后果是毁灭性的,私钥泄漏意味着攻击者可以伪造这个身份的一切签名。

正确的做法是使用操作系统提供的密码学安全随机源,或者使用标准里推荐的确定性生成方案(把私钥和消息摘要一起喂进一个伪随机函数来派生 k,既保证唯一又避免随机源质量差的风险)。绝对不能用的做法包括:用时间戳当种子、用普通随机库、把 k 写死在代码里、为了“可复现测试”让 k 固定。

同理,SM4 的密钥和 IV 也必须是真随机。很多翻车案例的根因不是算法被攻破,而是随机数用了可预测的种子,导致密钥空间被大幅压缩。这块省下来的每一分力气,最后都会以安全事故的形式还回去。

5.4 跨系统对接最容易翻车的三处

第一处是密文字段顺序。前面提过,SM2 加密结果是 C1、C3、C2 三个分量,不同标准版本的排列不一样。对接时最稳的做法是在协议里明确写死顺序,并且在实现里加一个自检用例:用自己的公钥加密一段固定数据,再用自己的私钥解,能还原才算通过。

第二处是曲线点编码。SM2 的公钥和密文里的 C1 都是曲线上的点,点可以只存 x 坐标加一个标志位(压缩格式),也可以存 x、y 两个坐标(非压缩格式)。两种格式字节长度差一倍,解析方式不同。跨语言对接时经常出现“一方发压缩点、另一方按非压缩解析”,结果就是偏移错位、解密失败。

第三处是摘要的十六进制大小写与分隔符。摘要本身是 32 字节二进制,传输时通常转成十六进制字符串。有的实现输出小写,有的输出大写,有的每两个字符加一个冒号。比对时如果不做归一化,明明内容一致也会判成不一致。这类问题不涉及任何密码学,却实实在在地消耗了大量排查时间。

6. 什么时候该用、什么时候别硬上

6.1 该用的三类场景

第一类是协议或规范里明确指定了算法清单的对接场景。这种时候没有选择余地,必须按清单实现,重点应放在格式对齐和测试用例上,而不是讨论算法优劣。第二类是需要自己掌握完整实现链路的场景,比如内部系统之间的身份认证和报文加密,算法公开、参数公开、实现可以自己审计,不依赖外部黑匣子。第三类是对性能和资源都敏感的场景:SM2 的 256 位密钥比 RSA 短很多,证书体积小、握手快,SM4 在受限设备上也有不错的吞吐,适合嵌入式或高并发环境。

6.2 别硬上的三种情况

第一种是只想要“加密一下”但没有密钥管理方案的情况。密码系统的安全性一大半在密钥怎么产生、怎么存储、怎么轮换、怎么销毁,算法只占一小部分。没有这套机制,用再标准的算法也只是把门锁装在纸板墙上。第二种是需要和只支持国际算法的外部系统互通的情况,这时候硬上国密只会让对接无法进行,正确做法是在边界处做转换网关,而不是要求对方改造。第三种是想自己实现算法的情况,除非团队里有密码学工程背景并且愿意做侧信道防护,否则应该用经过审计的成熟实现,自己写的 S 盒查表很容易被缓存时序攻击盯上。

场景建议主要原因
规范指定的对接按清单实现,重点做格式对齐没有算法选择空间,格式错是主要故障源
内部系统身份认证与报文加密适合,配合数字信封链路可控,密钥管理可自建
资源受限设备适合,优先 SM4 + SM2 短密钥密钥短、运算量可控
无密钥管理方案先补密钥管理再谈算法算法无法弥补密钥生命周期缺失
外部系统只支持国际算法边界做转换,不强推互通性优先于算法统一
想自己手写算法实现不建议侧信道与实现漏洞风险高

6.3 一个具体判断例子

假设某团队要做一个内部文件交换系统,文件从几 KB 到几百 MB 不等,参与方是固定的十几个服务。判断路径是这样的:文件大小跨度大,说明必须用对称加密承担数据量,SM4 是唯一合理选择;参与方固定,说明可以提前分发公钥,SM2 用来做身份认证和密钥封装;需要证明文件来源,说明每次交换都要带 SM2 签名;需要防篡改,说明签名前要用 SM3 算摘要,且摘要要覆盖密文而不是明文,避免解密前的篡改。

反过来,如果这个系统要和外部合作方交换文件,而对方只支持另一套算法,那就不要试图说服对方改造,而是在边界放一个转换服务:内部用一套,出口处解密再用对方的算法重新加密,密钥在转换服务里短暂存在并及时清除。这个转换点本身会成为新的风险集中处,所以要单独做审计日志和访问控制。

我第一次接触这套算法时,觉得最难的是数学,后来发现真正让人栽跟头的是格式和随机数。有一次对接,双方算法实现都没问题,就是验签一直失败,查了两天才发现一方在算 ZA 时用了空的身份标识,另一方用了默认值,改一行配置就好了。从那以后我每次做算法对接,都强制先跑一个最小闭环用例:自己加密自己解、自己签名自己验、固定输入比对摘要,三关都过再谈联调。希望这些经验能帮你少走点弯路,这份资料里把三套算法的参数、流程和常见对接问题都整理齐了,需要的时候取来对照着看就行。

本文还有配套的精品资源,点击获取

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

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

立即咨询