导读:本期聚焦于追梦人创作的《Node.js如何利用Crypto模块实现国密算法SM2、SM3、SM4加密解密?》,敬请观看详情。SM2、SM3、SM4是我国自主设计的密码算法标准,在金融、政务等对数据安全要求较高的领域应用广泛。Node.js自带的Crypto模块从新版本开始陆续支持了SM3哈希和SM4对称加密,但SM2椭圆曲线公钥算法仍需要借助第三方库才能实现。本文将介绍国密算法的基本概念与适用场景,详细讲解如何在Node.js环境中生成SM2密钥对、完成签名验签、实现SM3摘要计算以及SM4的ECB和CBC模式加解密,同时分析Node原生能力与node-sm-crypto等第三方库的差异,并给出可直接运行的完整代码示例,帮助开发者快速在项目中落地国密改造。

国密算法是中国国家密码管理局制定的一系列密码标准,其中SM2负责椭圆曲线公钥加密与数字签名,SM3负责哈希摘要,SM4负责分组对称加密。在涉及金融支付、政务系统和等级保护测评的项目中,使用国密算法往往是硬性要求。Node.js开发者面对这类需求时,通常会发现原生Crypto模块的支持并不完整,需要结合第三方库来覆盖全部能力。本文将从原生支持情况讲起,逐步给出SM2、SM3、SM4的完整实现方案。

Node.js如何利用Crypto模块实现国密算法SM2、SM3、SM4加密解密?

一、Node.js原生Crypto模块对国密算法的支持现状

首先需要明确一个前提:不同版本的Node.js对国密算法的支持差异很大。只有当Node.js编译时链接了OpenSSL 1.1.1及以上版本,SM3和SM4才会出现在可用算法列表中。Node.js 12之后的版本基本都满足这个条件,但更老的Node.js 10及以下版本则完全不支持。

可以通过一段简单代码检查当前环境是否支持国密算法。使用crypto.getHashes()查看是否包含sm3,使用crypto.getCiphers()查看是否包含sm4-ecbsm4-cbc等算法。注意SM3在算法列表中显示为小写的sm3,而SM4相关算法在cipher列表中以sm4-开头。

const crypto = require('crypto');

// 检查SM3摘要算法是否可用
console.log(crypto.getHashes().includes('sm3')); // true 表示支持

// 检查SM4加密算法是否可用
const sm4Ciphers = crypto.getCiphers().filter(c => c.startsWith('sm4'));
console.log(sm4Ciphers);
// 可能输出: [ 'sm4-ecb', 'sm4-cbc', 'sm4-ctr', 'sm4-ofb' ]

需要特别指出的是,原生Crypto模块并不支持SM2。SM2是基于特定椭圆曲线(国密曲线sm2p256v1)的公钥算法,而OpenSSL对SM2的支持在命令行工具层面比较完善,但Node.js的Crypto接口没有暴露对应的密钥生成入口。因此SM2的加解密和签名验签,必须依赖第三方库,比如node-sm-crypto或gm-crypt。这是很多开发者踩过的坑:以为升级Node版本就能用上全部国密算法,结果发现SM2根本无从下手。

二、SM3摘要计算与HMAC的实现

SM3是国产哈希算法,输出256位摘要,安全性对标SHA-256。它的使用方式和其他哈希算法完全一致,掌握了createHash的用法就能直接迁移。

基本用法如下,先创建一个sm3类型的hash实例,再更新数据并输出摘要。摘要默认输出Buffer,一般会转成十六进制字符串便于传输和比对。除了直接计算摘要,SM3还可以配合createHmac实现带密钥的消息认证码,用于接口签名验证等场景。

const crypto = require('crypto');

// 计算SM3摘要
const hash = crypto.createHash('sm3');
hash.update('你好,国密算法');
const digest = hash.digest('hex');
console.log('SM3摘要:', digest);

// 使用SM3实现HMAC
const hmac = crypto.createHmac('sm3', 'secret-key');
hmac.update('需要认证的消息');
console.log('HMAC-SM3:', hmac.digest('hex'));

有一个细节值得注意:SM3摘要在不同实现之间可能存在编码差异。有些Java或Go的服务端在计算摘要前会先把字符串按UTF-8编码,再对字节数组做哈希,Node.js的update方法默认也是按UTF-8处理字符串,所以一般能够对齐。但如果对方系统使用GBK编码(国内老系统并不少见),就需要手动指定编码:hash.update(str, 'gbk'),否则双方算出的摘要会完全对不上,排查起来非常耗时。

另外,SM3常用于密码加盐存储。虽然专门的密码哈希算法(如scrypt、argon2)更适合这个场景,但在等保合规要求统一的国密体系下,SM3加随机盐迭代多次也是一种可接受的折中方案。

三、SM4对称加密:ECB与CBC模式的实践

SM4是分组长度和密钥长度均为128位的对称加密算法,可以理解为国密版AES。原生Crypto模块支持sm4-ecb、sm4-c4-cbc等多种模式,其中ECB模式最简单但安全性最弱,CBC模式配合随机IV是更推荐的用法。

先看ECB模式的写法。ECB不需要初始化向量,相同的明文块会加密成相同的密文块,容易被分析出数据模式,因此只建议用于加密很短的、随机的数据(比如已经过随机化的密钥)。代码中密钥必须是16字节,这一点和AES-128一致。

const crypto = require('crypto');

function sm4EcbEncrypt(plainText, key) {
  const cipher = crypto.createCipheriv('sm4-ecb', Buffer.from(key, 'utf8'), null);
  return Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]).toString('base64');
}

function sm4EcbDecrypt(cipherText, key) {
  const decipher = crypto.createDecipheriv('sm4-ecb', Buffer.from(key, 'utf8'), null);
  return Buffer.concat([
    decipher.update(Buffer.from(cipherText, 'base64')),
    decipher.final()
  ]).toString('utf8');
}

const key = '1234567890abcdef'; // 必须16字节
const enc = sm4EcbEncrypt('这是一段机密数据', key);
console.log('ECB加密结果:', enc);
console.log('ECB解密结果:', sm4EcbDecrypt(enc, key));

CBC模式的写法稍微复杂一些,需要额外提供16字节的初始化向量IV。安全性上CBC优于ECB,因为相同的明文块配合不同IV会产生不同密文。实践中常见的做法是每次加密时随机生成IV,把IV拼在密文前面一起传输,解密方从密文头部取出IV再解密。

const crypto = require('crypto');

function sm4CbcEncrypt(plainText, key) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('sm4-cbc', Buffer.from(key, 'utf8'), iv);
  const encrypted = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
  // 将IV拼接在密文前面,格式:IV + 密文
  return Buffer.concat([iv, encrypted]).toString('base64');
}

function sm4CbcDecrypt(cipherText, key) {
  const data = Buffer.from(cipherText, 'base64');
  const iv = data.subarray(0, 16);
  const encrypted = data.subarray(16);
  const decipher = crypto.createDecipheriv('sm4-cbc', Buffer.from(key, 'utf8'), iv);
  return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}

const result = sm4CbcEncrypt('CBC模式更安全', '1234567890abcdef');
console.log('CBC结果:', result);
console.log('还原:', sm4CbcDecrypt(result, '1234567890abcdef'));

与Java端互通时要注意填充方式的问题。OpenSSL默认使用PKCS7填充,Java的Cipher.getInstance("SM4/CBC/PKCS5Padding")实际上也是PKCS7(在16字节分组下两者等价),所以直接互通没有问题。但如果对方采用的是无填充模式,就必须在Node侧显式禁用自动填充,调用cipher.setAutoPadding(false),并且保证明文长度恰好是16字节的整数倍。

四、SM2公钥算法:借助node-sm-crypto实现加解密与签名

SM2的实现是国密改造中最麻烦的一环,因为原生Crypto无能为力。社区里最常用的库是node-sm-crypto,安装命令为npm install node-sm-crypto --save。它基于纯JavaScript实现,不依赖OpenSSL,在Windows、Linux各个平台都能稳定运行。

SM2密钥对的生成非常直接,公钥是130位十六进制字符串(04前缀加两个坐标点),私钥是64位十六进制字符串。下面演示密钥生成、加密解密的完整流程。SM2加密时会内置一个随机数,因此同一段明文每次加密的结果都不同,这是正常现象,不要误以为加密出了问题。

const sm2 = require('node-sm-crypto');

// 生成SM2密钥对
const keypair = sm2.generateKeyPairHex();
const publicKey = keypair.publicKey; // 04开头的十六进制字符串
const privateKey = keypair.privateKey;

console.log('公钥:', publicKey);
console.log('私钥:', privateKey);

// 加密:cipherMode为1表示C1C3C2模式(新国标推荐)
const cipherText = sm2.doEncrypt('敏感信息', publicKey, 1);
console.log('SM2加密:', cipherText);

// 解密
const plainText = sm2.doDecrypt(cipherText, privateKey, 1);
console.log('SM2解密:', plainText);

这里有个极易出错的点:密文排列顺序。国密标准最初定义的密文格式是C1C2C3,后来更新为C1C3C2,Java的hutool、BC库以及各类硬件加密机默认格式不一。如果Node端加密后Java端解密报错,优先排查两边的cipherMode参数是否一致,sm2.doEncrypt的第三个参数传0表示C1C2C3,传1表示C1C3C2。此外,有些系统传输密文时会带上04前缀,有些不带,node-sm-crypto输出的密文不带04前缀,对接时可能需要手动补上或去掉。

SM2签名验签同样简单,签名算法使用SM3作为摘要。签名结果默认输出为DER编码的hex字符串,如果对接方要求裸的r+s拼接格式,可以把hashData与输出格式参数调整一下,具体以库的文档为准。

const sm2 = require('node-sm-crypto');

const msg = '需要签名的数据';
const sig = sm2.doSignature(msg, privateKey);
console.log('签名:', sig);

// 验签:返回true表示验证通过
const valid = sm2.doVerifySignature(msg, sig, publicKey);
console.log('验签结果:', valid);

// 注意:如果数据已经做过SM3摘要,可传第三个参数指定{ hash: false, der: true }

五、工程实践建议与常见问题排查

在真实项目中落地国密算法,光有代码还不够,有几个工程层面的建议值得参考。第一,密钥管理不要硬编码在代码里,应该放入环境变量、配置中心或KMS服务,尤其是SM4密钥和SM2私钥,一旦泄露整套加密体系就形同虚设。第二,建议把国密操作封装成独立的工具模块,统一处理hex与base64的编码转换、密文格式拼接,避免各个业务模块各自实现导致格式不一致。

第三,跨语言互通是国密项目最大的坑源。排查问题时建议用固定的测试向量对拍:让Java端和Node端加密同一段明文、同一个密钥、同一个IV(CBC模式下IV固定为全零进行测试),比对密文是否一致。一致说明算法层面没问题,不一致再逐步检查密钥编码、填充方式、密文格式这三个高频出错点。测试完成后再在生产代码中恢复随机IV。

最后补充性能方面的考量。node-sm-crypto是纯JS实现,SM2签名运算大约需要几毫秒,对于常规的接口签名验签完全够用,但如果要在网关层对海量请求做验签,建议通过N-API编写C++扩展,或者将国密运算下沉到独立的加密服务。SM3和SM4由于走的是OpenSSL原生实现,性能表现与SHA-256、AES处于同一量级,无需过度担心。

总结一下:SM3和SM4直接用Node.js原生Crypto模块即可,成本低、性能好;SM2则交给node-sm-crypto这类成熟库,重点盯住密文格式与签名编码的兼容性。三者组合起来,一套完整的国密安全体系在Node.js中就搭建完成了。

国密算法Node.js加密SM2修改时间:2026-09-11 13:48:55

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260911/54695.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。