
简介HTML页面国密SM4加密解密资源面向前端开发、Web安全及需要对接国密标准的开发者用于解决网页交互中SM4算法如何集成与调用的问题同时覆盖CBC与ECB两种常用模式。压缩包共2个文件、整体约5KB包含1个HTML演示页面与1个JavaScript脚本演示页面完成加解密操作的可视化调用脚本封装SM4核心算法结构紧凑适合阅读和二次修改。已有603人学习下载特别适合初中级前端开发者快速理解对称加密在浏览器端的实现方式也可作为内部工具或教学示例直接参考。通过该资源可以直接运行Demo观察相同明文在不同模式下的密文变化并学习密钥与初始化向量的构造方法同时还能注意到前端加密的密钥暴露风险为后续迁移至更安全的加密方案埋下铺垫。 做前端的人应该都接过这种需求某个接口的敏感字段甲方明确要求用国密SM4加密而且加密得在HTML页面里直接完成。我最近刚把一个纯前端的SM4加密解密功能从零落地到生产环境中间和后台联调来回折腾了好几次踩了一堆文档里不会写的坑。这篇文章就把我在HTML页面里落地SM4加密解密的完整过程和关键细节整理出来从算法参数到库选型从代码实现到联调踩坑尽量让后来的人少走弯路。如果你正准备在网页端对接SM4加解密或者只是想知道浏览器里怎么调国密算法这篇内容都适合你。我会用最简单的方式讲清楚SM4是什么、有哪些现成的前端库能用、加解密代码怎么写以及前后端最常见的“密文对不上”问题到底出在哪。全程实战视角不绕弯子。1. 需求与场景HTML里做SM4到底解决什么问题1.1 为什么是SM4而不是AES国密SM4是我国发布的商用对称分组密码算法分组长度128比特密钥长度也是128比特密文由明文分组按固定算法迭代32轮得到。很多政企项目、金融系统、信创环境下的应用都会在技术规范里明确要求用国密算法做数据加密这既是合规要求也是项目验收的硬性条件。我在实际项目里最常见的两个场景一是登录密码或订单等敏感表单字段在提交前用SM4加密避免敏感信息在网络请求里以明文形式直接暴露二是本地存储的敏感数据比如用户资料、Token先用SM4加密再写入localStorage或IndexedDB。当然真正讲究传输安全的场景通常配合HTTPS一起使用前端SM4更像一道额外的保护层或者是满足行业监管的一部分。有朋友可能会问加解密放后端不就行了为什么非得在HTML页面里做原因也很现实。有些老系统的前后端是分离的接口协议和加解密逻辑已经定死只能让浏览器把数据加密之后再提交。还有一些纯工具型页面比如数据导出、离线文件加解密工具必须在浏览器本地完成加解密根本不经过后端。1.2 前端加密不等于绝对安全这点我必须放在前面说透纯前端加解密不等于绝对安全。密钥一旦写在前端JS代码里任何会打开开发者工具的人都可以翻到源码拿到密钥和加密逻辑。所谓的“代码混淆”“压缩混淆”只能增加阅读成本挡不住真正懂行的人。那为什么还要做因为前端SM4的价值在于避免敏感字段在传输链路或日志中以明文出现提高攻击者抓包后的利用成本同时满足业务合规要求。你可以把它理解成给数据加了一道“常见的锁”但安全根基仍然是HTTPS传输、服务端鉴权和密钥管理。千万不要在前端写死一个密钥就以为万事大吉这一点后面第5节我会再展开。2. 动手前的关键认知SM4参数与前端库选型2.1 SM4不可妥协的参数细节写代码之前先把SM4的几个关键参数搞明白不然联调的时候铁定对不上。这些参数全部由算法本身和后端实现共同决定不是前端想怎么改就怎么改的。参数项取值及说明分组长度128比特即16字节明文按16字节切块处理密钥长度128比特即16字节代码里通常表示成32位hex字符串工作模式ECB或CBC最常见CBC需要额外提供16字节IV填充方式常见PKCS7明文不足16字节倍数时自动补齐输出编码密文通常输出hex字符串或Base64字符串取决于后端接口ECB模式每个明文分组独立加密相同明文会产生相同密文简单但不推荐用于长数据。CBC模式每个分组加密前会跟前一个密文分组做异或需要提供IV相同明文在不同IV下会得到完全不同的密文安全性更好。绝大多数实际项目用CBC。填充方式是另一个容易出问题的点。SM4要求明文必须是16字节的整数倍所以明文不足时要填充。PKCS7的规则是缺少几个字节就补几个值为几的字节。如果明文刚好是16字节的整数倍依然要额外补一个完整块每个字节都是0x10。解密时再根据最后一个字节的值去掉填充。2.2 前端SM4库怎么选目前前端做SM4加解密社区里用最多的是sm-crypto和gm-crypt。sm-crypto是腾讯开源的国密算法库支持SM2、SM3、SM4API设计简洁文档和示例都比较全而且在浏览器端有现成的UMD构建文件可以直接通过script标签引入非常适合纯HTML页面。我一直用它。gm-crypt功能也全配置方式更接近Java后端的风格但它更偏向Node.js环境想在浏览器纯HTML页面里直接用的话通常还需要webpack或vite打包稍微麻烦一点。我的建议很直接项目里有构建工具用npm装sm-crypto如果就是纯静态HTML页面、不想搞构建直接用CDN引入sm-crypto的dist文件。下面两节全部按这个思路来写。3. 实战纯HTML页面SM4加解密实现3.1 用CDN搭一个能跑的ECB加解密页面先来一个最小的ECB模式加解密Demo纯HTML加CDN浏览器打开就能用不依赖任何脚手架。!DOCTYPE html html langzh-cn head meta charsetutf-8 titleSM4 ECB加解密Demo/title script srchttps://cdn.jsdelivr.net/npm/sm-crypto0.3.13/dist/sm-crypto.min.js/script /head body h3SM4 ECB 加解密/h3 div label明文/label input idplainText valuehelloworld /div div label密钥/label input idsecretKey value0123456789abcdeffedcba9876543210 /div button onclickdoEncrypt()加密/button button onclickdoDecrypt()解密/button div label密文/label textarea idcipherText rows3 cols60/textarea /div div label解密结果/label textarea iddecryptResult rows2 cols60/textarea /div script function doEncrypt() { var plain document.getElementById(plainText).value; var key document.getElementById(secretKey).value; var cipher sm4.encrypt(plain, key); document.getElementById(cipherText).value cipher; } function doDecrypt() { var cipher document.getElementById(cipherText).value; var key document.getElementById(secretKey).value; var plain sm4.decrypt(cipher, key); document.getElementById(decryptResult).value plain; } /script /body /html注意几个细节sm4.encrypt的第一个参数可以是普通UTF-8字符串中文也完全没问题第二个参数key是16字节通常用32位hex字符串表示。加密结果默认输出hex字符串。如果明文只有“helloworld”这10个字符经过PKCS7填充后是16字节加密结果就是32位hex字符。sm4.decrypt默认返回UTF-8字符串所以解密结果直接在页面上显示中文也不会有乱码。3.2 升级到CBC模式CBC模式只比ECB多了一个IV参数但IV的坑是最多的。用sm-crypto时IV同样是一个32位hex字符串代表16字节。如果IV长度不对大概率会直接报错或者解出来是乱码。function doEncryptCbc() { var plain document.getElementById(plainText).value; var key document.getElementById(secretKey).value; var iv document.getElementById(ivValue).value; var cipher sm4.encrypt(plain, key, { mode: cbc, iv: iv }); document.getElementById(cipherText).value cipher; } function doDecryptCbc() { var cipher document.getElementById(cipherText).value; var key document.getElementById(secretKey).value; var iv document.getElementById(ivValue).value; var plain sm4.decrypt(cipher, key, { mode: cbc, iv: iv }); document.getElementById(decryptResult).value plain; }页面里的IV输入框可以这样加div labelIV/label input idivValue value0123456789abcdeffedcba9876543210 /divCBC模式最核心的点是前后端的IV必须完全一致。这里的“一致”指的不只是值一样而是表示方式一样。如果前端把IV当作“16个字符的字符串”传给后端后端却按“32位hex字符串”解两边实际用的IV二进制内容完全不同解密必挂。3.3 集成到真实业务表单很多读者真正关心的是我已经有一个登录页面或提交表单了怎么把SM4加密嵌进去其实逻辑非常简单就是在表单提交的瞬间把原始字段值替换成加密后的密文再提交。document.getElementById(loginBtn).addEventListener(click, function () { var rawPassword document.getElementById(password).value; // 实际项目中密钥通常从后端接口按会话获取不写死在代码里 var key sessionKey; var iv sessionIv; var encryptedPassword sm4.encrypt(rawPassword, key, { mode: cbc, iv: iv }); // 方式一写入隐藏域随表单提交 document.getElementById(encryptedPassword).value encryptedPassword; // 方式二作为JSON请求体字段提交 // fetch(/api/login, { // method: POST, // headers: { Content-Type: application/json }, // body: JSON.stringify({ password: encryptedPassword }) // }); });这里有一个非常容易被忽视的坑很多前端同学会把加密逻辑写在onsubmit里但是忘记阻止表单默认提交结果密文还没生成原始明文就已经发出去了。用addEventListener绑定点击事件或者在使用onclick时记得return false都能绕开这个问题。4. 前后端联调最容易翻车的部分4.1 联调前必须对齐的四项契约以我个人的经验前端写SM4加解密的代码基本半小时以内就能搞定真正耗时间的是前后端联调。很多“密文我这边能解开后端那边就是解不开”的问题本质上都是沟通问题不是算法问题。联调之前建议把下面四项写成文档发给后端确认。联调项必须对齐的内容密钥与IV密钥和IV都是16字节统一用32位hex字符串表示确认大小写敏感工作模式明确是ECB还是CBCCBC模式下IV由哪一方生成、如何传递填充方式前端默认PKCS7确认后端是否也是PKCS7/PKCS5密文编码密文传输时用hex还是Base64字段类型是字符串还是字节数组尤其注意“密文编码”这一项。sm-crypto默认输出hex字符串而后端如果习惯用Base64那么前端拿到hex字符串后直接提交后端拿Base64解码器一跑就抛异常。反过来也一样。这个坑我踩过不止一次。4.2 Java后端Hutool对照实现很多Java后端项目用Hutool的SmUtil做SM4我在这里给一个对照代码方便前端同学理解后端到底在干什么。import cn.hutool.crypto.symmetric.SM4; import cn.hutool.core.util.HexUtil; String key 0123456789abcdeffedcba9876543210; SM4 sm4 new SM4(HexUtil.decodeHex(key)); // 默认ECB模式默认填充对应前端 sm4.encrypt(plain, key) String ciphertext sm4.encryptHex(helloworld); System.out.println(ciphertext); String plaintext sm4.decryptStr(ciphertext); System.out.println(plaintext);如果前端用的是CBC模式后端这样写SM4 sm4 new SM4(Mode.CBC, Padding.PKCS5, HexUtil.decodeHex(key), HexUtil.decodeHex(iv)); String ciphertext sm4.encryptHex(helloworld); String plaintext sm4.decryptStr(ciphertext);注意Hutool里写的是Padding.PKCS5但在SM4这种16字节分组的算法里实际起作用的逻辑和前端PKCS7完全兼容。所以前端写PKCS7、后端写PKCS5解出来的结果是对的这点不用担心。4.3 我经历过的几个翻车现场第一个是密钥表示方式不一致。前端把32位hex字符串当成普通字符串传给后端后端却按hex解码两边看起来都是“同一个密钥”实际上二进制完全不同。第二个是输出编码不一致前端直接把hex密文塞给后端后端按Base64解码结果直接抛IllegalArgumentException。第三个是CBC模式IV不一致前端用随机IV但忘了传给后端后端自己又固定写死了一个IV两边各自都能加密解密但一交换密文就是乱码。这三个问题解决起来都很简单就是把第4.1节那张表的四项对齐即可。但如果没有提前对齐排查起来会非常痛苦因为前端和后端各自独立测试都是通过的。5. 常见报错与排查梳理5.1 报错和异常速查表把我在实际使用中遇到过的典型问题整理成了下面的表排查时可以直接对照。现象常见原因解决办法控制台报错未定义encrypt方法sm-crypto的dist文件没引入成功打开控制台输入window.sm4检查是否为undefined解密结果是乱码CBC模式IV不一致或后端返回密文被转义确认前后端IV、密文编码格式完全一致密文长度不是32的整数倍数据被截断或填充方式不是PKCS7检查传输过程是否有换行、截断后端报解码异常hex/Base64编码不一致统一密文的对外编码格式加密后中文变问号前后端字符集不一致统一使用UTF-8明文内容正确但密文每次不同CBC模式下使用了随机IV这是正常现象确认IV是否随密文一起传递给了后端5.2 关于密钥管理的一点建议最后必须再强调一遍安全。如果密钥写死在前端代码里你把JS压缩、混淆、加密到最后也只是提高了阅读门槛阻挡不了真正想逆向的人。实际项目中更稳妥的思路是密钥由后端接口在登录或会话建立时动态下发前端只保存在内存变量里不落localStorage不写死在代码中配合HTTPS一起使用。密钥本身一旦泄露再好的算法也形同虚设。我自己的体会是前端做SM4加解密真正花时间的不是加密那几行代码而是前后端参数对齐这件事。密钥表示方式、工作模式、IV、密文编码任何一个环节不一致都白搭。所以动手之前一定要把联调契约写明白、对齐到位别上来就写代码。最后再分享一个我自己的小习惯每次写完加解密先用固定的测试向量在本端验证一遍再拿去对接口排错效率会高很多。本文还有配套的精品资源点击获取