资讯动态

商用密码改造实战:国密算法选型、密钥管理与自查清单

发布时间:2026/10/31 6:02:54 来源:尧图企业网站定制
1. 先把概念掰开商用密码到底指什么很多人第一次听到“商用密码”这个词会下意识以为是“给商家用的密码”“电商网站登录密码”之类的东西。恰恰相反它跟登录口令没有半点关系它指的是一整套用于保护不属于国家秘密的信息的密码技术、产品和服务。换句话说你手机里的支付加密、公司内网的传输加密、数据库里脱敏后的密文、甚至小区门禁卡里的一段认证码背后归口的那套技术体系就叫商用密码。它的反面是“核心密码、普通密码”那是保护更高等级信息的体系普通开发者基本接触不到也不该接触。我做过后端架构也参与过若干次业务系统的密码改造和内部技术自查最常听到的抱怨是“我 HTTPS 用得好好的AES-256 也够安全为什么还要折腾一套国密算法”这个问题问得很实在答案也得分两层。第一层是合规层面的互认需求——上下游对接时对方要求密码算法、密码模块、密钥管理都能对得上标准口径你拿一套私有实现去对接光是对齐文档就要耗掉两周。第二层是技术层面的一致性——商用密码不是“换一套算法”那么简单它是一套从算法、模块、协议、密钥管理到评估方法的完整链条。你只换算法不改密钥管理等于给防盗门换了把好锁钥匙还挂在门把手上。所以这篇东西写给谁看三类人。一是正在做系统密码改造的后端或安全工程师需要对整体盘子有个全景认识别一上来就埋头改代码二是技术负责人或架构师要判断改造的工作量、边界和风险点三是刚接触这块的运维、测试同学需要知道自查清单里那些名词到底对应什么技术动作。我不打算讲空泛的定义全部按“为什么会这样设计—具体怎么做—哪里会翻车”的顺序来展开能直接抄的部分我给到命令和配置不能抄的部分我告诉你判断逻辑。这里先立一个基本认知商用密码的技术核心可以压缩成三件事——算法可替换、密钥可管控、过程可验证。算法可替换指的是别把算法写死在业务代码里留出可插拔的接口密钥可管控指的是密钥的产生、分发、使用、轮转、销毁有明确的责任人和流程过程可验证指的是你能拿出证据说明“这一步确实做了加密而且用的是合规的实现”。后面六个章节基本都绕着这三件事转。1.1 “商用”这两个字重点在“可公开、可商用、可互操作”拆一下词。“商用”不是说只有商业公司才能用也不是说要收授权费才能用。它强调的是这套密码技术是公开的、标准化的、任何具备条件的单位都可以依法依规使用的不像某些专用密码体系那样封闭。国密算法族SM2、SM3、SM4、SM9 这些算法本身是公开的论文、标准文档、开源实现都能查到安全性建立在数学难题和密钥保密上而不是算法保密上——这一点和 AES、RSA 的设计哲学一致是 Kerckhoffs 原则的标准做法。这个特性带来两个很实际的好处。第一互操作成本低。两个不同厂商的设备要做加密通信只要都遵循同一套标准理论上把参数对齐就能通。我踩过一个坑早期某两个厂商的 SM2 实现一个默认用未压缩点格式04 前缀传公钥另一个只认压缩点格式02/03 前缀联调时签名验不过查了两天才定位到是公钥序列化格式不一致跟算法本身一点关系都没有。第二供应链风险可控。算法公开意味着你可以自己实现、可以用开源库、也可以买硬件模块不会被单一供应商锁死。我一般建议关键路径用硬件模块非关键路径用经过审查的软件库两者按同一套标准对接将来换供应商只换实现不换协议。要注意的是“可公开”不等于“随便用”。算法公开但密钥必须严格保密标准公开但具体的密码模块得经过检测认证才被认为达标。这两者的区分很多人搞混以为用了 SM4 就自动合规了其实差得远——你的密钥要是硬编码在配置文件里用的是哪个算法都没意义。1.2 它和普通加密库的本质差别在哪表面上看商用密码和普通加密库都是“调用 API 做加解密”差别好像只在算法名不同。真做起来差别在三处。第一处是算法套件的完整性。普通场景里你可能只需要一个对称加密AES加一个哈希SHA-256够用了。商用密码体系里一套完整方案通常包括非对称算法做身份认证和密钥协商SM2、对称算法做数据加密SM4、哈希算法做完整性校验SM3特殊场景还要基于标识的密码SM9。这四个不是随便挑一个用而是有固定的搭配范式比如签名用 SM2、摘要用 SM3、会话密钥用 SM4你不能拿 SM2 去做大数据量加密——它的加密速度只有对称算法的几千分之一纯属自找麻烦。第二处是密码模块的边界。普通加密库你 npm install 一下就用了出问题最多是个 CVE。商用密码场景里密码运算发生在哪里是有讲究的是在应用进程里、在独立的密码服务里、还是在硬件密码卡/密码机里直接决定了这套系统的安全等级和可评估性。密钥如果进了应用进程的内存理论上就有被 dump 出来的可能这是评估时的一个常见扣分点。第三处是证据链。普通加密库你只要功能正确就行。商用密码改造做完你得能回答“密钥是谁生成的、存在哪、谁有权限调用、多久轮转一次、调用日志在哪”这一串问题。我见过一个团队代码写得漂亮算法调用完全正确但问起密钥轮转策略回答是“没做过上线两年没换过”这一项基本就废了。所以后面我会用整整一章讲密钥管理因为它才是这套体系的重心。2. 算法选型为什么是国密算法族各自管什么用选型这件事最怕的是“听人说要用 SM4 就全上 SM4”。算法是有分工的用错了不是不安全是又慢又不对。我按实际使用频率从高到低排一下并且给出参数层面的关键信息方便你判断自己的场景该用哪个。对称加密是数据加密的主力SM4 就是这个角色。分组长度 128 位密钥长度 128 位32 轮迭代整体结构属于广义 Feistel 网络。工作模式上实际生产里我只推荐两种需要随机访问的用 CTR 模式需要认证加密的用 GCM 模式如果实现支持老系统兼容场景用 CBC 但要额外加 HMAC-SM3 做完整性保护。ECB 模式绝对不要用它会把相同明文块加密成相同密文块图片加密后还能看出轮廓这是教科书级的错误但每年都有人犯。哈希用 SM3。输出 256 位分组 512 位结构上是 Merkle-Damgård 迭代加一个专门的压缩函数抗碰撞强度对标 SHA-256。用途包括口令存储必须加盐且建议用专门的慢哈希SM3 直接存口令哈希是不够的、文件完整性校验、HMAC 的消息认证、以及签名过程中的摘要计算。有个细节值得说SM3 的抗长度扩展攻击能力跟 SHA-256 一样都受 Merkle-Damgård 结构限制所以做 MAC 时必须用 HMAC 构造别图省事直接SM3(key || message)。非对称用 SM2。基于 256 位素域上的椭圆曲线一条曲线同时支持数字签名、公钥加密、密钥交换三种用途。密钥长度 256 位安全强度大致相当于 RSA-3072。用途分工很明确做身份认证和签名验签做密钥协商SM2 密钥交换协议做小数据量加密比如加密一个会话密钥。千万别拿它加密业务数据。还有 SM9基于标识的密码公钥直接是对方的身份标识比如一个邮箱地址字符串不需要证书体系。它在某些物联网、内部系统场景里确实省事但性能比 SM2 差一个量级生态支持也少我一般只在证书管理成本特别高的场景才推荐。2.1 关键参数对照与性能量级下面这张表是我自己整理的一份速查数字来自公开标准文档和我在若干台机器上的实测量级具体到你自己的机器会有出入但相对关系是稳定的。算法类型关键参数典型用途性能量级单核软件实现SM2椭圆曲线256 位素数域推荐曲线签名、密钥交换、小数据加密签名 1500~3000 次/秒验签 500~1200 次/秒SM3哈希输出 256 位分组 512 位摘要、HMAC、完整性校验200~500 MB/sSM4分组对称分组 128 位密钥 128 位数据加密、会话加密100~300 MB/s有专用指令可到 1 GB/s 以上SM9标识密码基于双线性对曲线参数更多免证书的身份认证、密钥封装签名 100~400 次/秒看到这组数字很多人的第一反应是“SM4 好慢”。确实慢。同样一台机器上 AES-128-GCM 走 AES-NI 指令能跑到 3~6 GB/s纯软件的 SM4 只有它的十分之一都不到。所以性能优化不是可选项是必选项具体怎么优化后面第 6 章会细说。这里先给个结论能用硬件指令就用硬件指令能用批处理就用批处理实在不行就把加密运算从业务主流程里挪出去异步做。关于密钥长度还有一点必须说清楚SM2 的 256 位和 AES 的 256 位不是一回事。ECC 的 256 位安全强度大约等于对称密码的 128 位也就是说 SM2-256 大致对标 AES-128 的安全级别而不是 AES-256。做方案对比时别拿数字直接比大小容易得出错误结论。2.2 从现有算法迁移过来哪些地方必须改、哪些能偷懒老系统大概率跑的是 AES RSA SHA-256 这套组合。迁移到商用密码不是全局替换字符串那么简单我按改动量和风险排一下优先级。风险最高、必须先改的是签名与证书链。RSA 签名换成 SM2 签名涉及到证书格式、密钥用法扩展、签名算法标识全部要换。如果系统里有自己的 CA要重新签发一套 SM2 证书如果依赖外部证书得确认对方是否支持。这一块最容易卡在联调阶段因为涉及两方甚至多方。其次是传输层。TLS 有对应的国密套件规范用 SM2 做密钥交换和认证、SM4 做记录层加密、SM3 做 PRF 和 MAC。工程上用支持国密套件的 TLS 库或者在前面挂一个支持国密的网关做卸载。我倾向于后者改造面小回滚快。再次是数据存储加密。这一块可以渐进做先把最敏感的几个字段身份证号、手机号、银行卡号加密再逐步扩展。字段级加密要注意一点加密后的密文长度会变如果原字段是定长的得先扩列宽或者改用文本类型否则上线当天就会报截断错误。我就被这个坑过一次表结构没改第一批数据写进去全部截断回滚花了半小时。最不值得大动的是内部非敏感日志的哈希。有些团队把日志里的 traceId 生成逻辑也换成 SM3 了这纯属浪费算力。评估改动范围的原则很简单数据分级。哪些数据泄露会造成实际损失哪些只是内部标识分清楚之后再决定改哪里。全量替换听起来很彻底实际是把有限的工程资源浪费在低价值环节。3. 系统改造实操一个典型业务系统怎么把密码用起来讲概念容易飘我拿一个抽象的典型系统做例子一个前后端分离的业务系统前端 Web 和 App后端 Java 服务集群MySQL 存业务数据Redis 做缓存服务之间走 HTTP 调用。这是绝大多数中等规模系统的样子。改造目标是把敏感数据的传输和存储都纳入商用密码体系并且密钥管理可控。先说整体思路我不想一上来就贴代码。改造的顺序是先盘资产、再划边界、后动手。盘资产是把系统里所有涉及敏感数据的位置列出来——数据库哪些表哪些字段、缓存里存了什么、日志里打了什么、接口传了什么、文件存储里有什么。这一步最枯燥但最不能省我见过改造做完了才发现日志里还在明文打身份证号的等于白干。划边界是确定哪些运算放在应用内、哪些放在独立的密码服务、哪些放在数据库侧。动手阶段才轮到写代码。整体架构我倾向这样分三层最外层是接入网关负责传输层加密卸载把国密 TLS 终结在这一层中间是应用服务通过统一的密码 SDK 调用加解密能力SDK 内部不持有长期密钥只持有短期会话凭证底层是密钥管理服务加密码机或软件密码模块所有涉及主密钥的运算都在这里完成应用侧拿不到主密钥明文。这个分层的好处是应用被攻破攻击者拿到的是短期凭证和会话密钥长期密钥仍然安全密码机被换掉应用代码一行不用改。3.1 传输层国密 TLS 的落地方式与证书准备传输层改造有两个路子一是应用自己支持国密 TLS二是前置网关做卸载。我强烈推荐第二种原因很实际——应用侧的 TLS 库版本五花八门Java 有的版本对国密套件支持不一致升级一次 JDK 可能就把 TLS 搞挂风险不可控。网关卸载则把复杂度集中在一个点上版本统一管理。如果走 OpenSSL 或者支持国密的中间件本地验证的流程大概是这样的# 生成 SM2 私钥推荐曲线 openssl ecparam -genkey -name SM2 -out sm2.key # 查看曲线参数确认是 sm2p256v1 openssl ecparam -in sm2.key -text -noout # 生成自签证书摘要算法指定 sm3 openssl req -new -x509 -key sm2.key -sm3 -sigopt distid:1234567812345678 \ -out sm2.crt -days 365 -subj /CNinternal.example # 校验文件完整性用 SM3 openssl dgst -sm3 -hex somefile.bin # 用 SM4-CBC 做一段数据的加密示例密钥由 KDF 派生 openssl enc -sm4-cbc -K 0123456789abcdeffedcba9876543210 \ -iv 00000000000000000000000000000000 -in plain.txt -out cipher.bin这段命令里有个容易被忽略的参数-sigopt distid:...。SM2 签名在计算时会把签名者的身份标识distinguishing ID简称 distid混入摘要默认值是固定的一串字符。如果两端使用的 distid 不一致签名必然验不过而且报错信息通常很含糊只说验签失败。我在做跨系统对接时专门把 distid 写进接口文档作为必填的对接参数之一避免这种低级的联调事故。证书这一块的坑更多。SM2 证书和 RSA 证书的格式差异不只是算法标识公钥的编码、签名值的编码方式都有讲究。有些旧版本的客户端只认未压缩点格式的公钥服务端签发时用的是压缩格式握手直接失败。我的做法是统一按未压缩格式04 前缀签发兼容性最好代价是公钥长度从 33 字节变成 65 字节对握手包大小影响可以忽略。3.2 存储层字段级加密的设计与实际代码存储层加密有个经典争论整库加密还是字段加密。整库加密透明数据加密的好处是业务无感坏处是数据库进程本身能解明文防不住 SQL 注入导致的越权读取。字段加密则是在应用层加密后再入库数据库里只有密文即使被拖库也拿不到明文。我一般建议敏感字段做字段加密落地磁盘做透明加密两层叠加。字段加密的设计要点我列几条实际踩过坑总结出来的加密列必须扩长。SM4 密文比明文长含 IV、可能的填充和认证标签留够余量。Base64 编码后再存文本列的话长度大概是明文的 1.4 倍加固定开销定型字段直接改VARCHAR(512)更省心。必须带完整性保护。只加密不认证攻击者可以翻转密文位来篡改数据虽然结果不可预测但对某些业务逻辑足够造成破坏。用加密后附加 HMAC-SM3或者直接用带认证的模式。随机 IV 每次都要新生成。IV 复用是 CBC 模式最致命的问题相同明文前缀会泄露。IV 可以明文和密文一起存它不是秘密但必须随机。不要用业务主键当密钥。见过用用户 ID 派生密钥的实现听起来很优雅实际上一旦用户 ID 可枚举攻击者就能批量尝试。Java 侧用 BouncyCastle 做 SM2 签名的示意代码这段代码我在多个项目里几乎原样用过import org.bouncycastle.crypto.CryptoException; import org.bouncycastle.crypto.params.*; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.asn1.gm.GMNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; public class Sm2SignDemo { // 统一使用 sm2p256v1 推荐曲线 private static final X9ECParameters CURVE GMNamedCurves.getByName(sm2p256v1); private static final ECDomainParameters DOMAIN new ECDomainParameters(CURVE.getCurve(), CURVE.getG(), CURVE.getN(), CURVE.getH()); public static byte[] sign(byte[] data, ECPrivateKeyParameters pri) throws CryptoException { SM2Signer signer new SM2Signer(); // 关键显式指定与对端一致的 distid signer.init(true, new ParametersWithID( new ParametersWithRandom(pri, new java.security.SecureRandom()), 1234567812345678.getBytes())); signer.update(data, 0, data.length); return signer.generateSignature(); } public static boolean verify(byte[] data, byte[] sig, ECPublicKeyParameters pub) { SM2Signer signer new SM2Signer(); signer.init(false, new ParametersWithID(pub, 1234567812345678.getBytes())); signer.update(data, 0, data.length); return signer.verifySignature(sig); } }注意ParametersWithID这个包装。很多示例代码里不写它用默认值跑单测没问题一对接就失败。我现在的习惯是把 distid 写成配置项从环境变量注入接口文档里明确写“双方 distid 必须一致”一句话省掉后续两天的联调。4. 密钥管理整套体系里最容易翻车的地方前面说过算法是公开的安全性全押在密钥上。可现实中我见过的问题八成出在密钥管理而不是算法实现。有人把密钥写在application.properties里跟代码一起提交有人把主密钥和加密数据放在同一台机器上有人上线三年没做过一次轮转还有人把密钥打印在启动日志里“方便排查”。这些做法下你用的是 SM4 还是 AES、是 128 位还是 256 位其实已经不重要了。密钥管理的第一原则是分层。至少分三层根密钥也叫主密钥、密钥加密密钥KEK、数据加密密钥DEK。根密钥只在密码模块内部存在永远不出硬件用于保护 KEKKEK 用于保护 DEK可以定期更换DEK 才是真正加密业务数据的密钥可以每条数据一个、每天一个或每张表一个。这种分层名字叫信封加密好处是更换根密钥时不用重新加密所有业务数据只需要用新根密钥把 KEK 重新包一层。第二原则是最短暴露。DEK 用完立刻清出内存不要放静态变量里缓存不要塞进 ThreadLocal 忘了清理。Java 里可以用Arrays.fill(keyBytes, (byte)0)显式清零虽然 JVM 有对象复制的可能效果打折扣但聊胜于无。更彻底的做法是把加解密运算交给独立的密码服务应用侧只传数据不接触密钥这样连清零的麻烦都省了。第三原则是可审计。每一次密钥使用都该有一条记录谁、什么时候、用哪个密钥、操作类型是什么、结果如何。这条记录本身可以不含敏感信息但一定要有。评估的时候这一项是硬指标。4.1 密钥全生命周期的五个阶段每个阶段的动作清单我把密钥的生命周期拆成五个阶段每个阶段配上具体动作这份清单你可以直接拿去当内部规范的骨架。生成阶段。密钥必须由密码模块或经认证的随机源生成不能用Math.random()、不能用时间戳做种子、不能从密码字符串哈希出来。SM2 的私钥生成要用密码学安全的随机数发生器且要检查私钥值是否落在 [1, n-1] 区间内。我见过有人为了“可复现”把私钥生成算法改成用固定种子加时间戳这在测试环境无所谓上生产是重大隐患。分发阶段。密钥不能在网络上明文传输要么走加密通道要么用密钥封装的方式传送——用对方的公钥加密一个对称密钥对方私钥解开。跨机房分发大批量密钥我建议走离线的密钥灌装一次灌一批比在线分发稳妥。使用阶段。这是最容易出问题的阶段。要求是密钥不出密码模块边界能在模块内算就在模块内算、调用需鉴权谁能调、调哪个密钥要有权限控制、单次使用有次数上限防止暴力尝试、并发调用有限流防止拖垮密码机。这四条听着繁琐实际就是几行配置的事但很多团队一条都没做。轮转阶段。多长时间换一次行业惯例是一年密钥泄露风险高的场景建议缩短到三个月。轮转的技术难点在于存量数据的处理——老数据用老密钥新数据用新密钥解密时按密钥版本号选密钥。所以设计之初就要在密文头部留出版本位通常是 1 到 2 个字节别等到要轮转了才发现密文里没地方放版本号。这一点我在做第一版设计时就吃过亏后来加版本号不得不写了个双读双写迁移脚本跑了一整晚。销毁阶段。密钥不用了要真正销毁不是标记删除。软件实现的密钥内存清零加文件覆写硬件模块的密钥调用销毁指令返回成功才算完。4.2 硬件密码模块和软件实现到底怎么选这是架构评审上必吵的问题。硬件密码模块密码卡、密码机、云的密码服务贵、部署麻烦、有单点风险软件实现便宜灵活、性能看 CPU、但密钥在内存里。我的判断标准是看数据的价值和规模。维度硬件密码模块软件实现密钥保护密钥不出模块物理防拆密钥在内存/磁盘理论上可提取性能高支持高并发SM4 可达数 GB/s受 CPU 限制单机 SM4 约百 MB/s 级成本采购/租用成本高几乎为零扩展性受设备数量限制需做集群随应用实例横向扩展适用场景核心密钥、高价值数据、高频加解密一般字段加密、开发测试、低频场景我的常规做法是混合根密钥和 KEK 放硬件模块DEK 在应用侧内存中使用但由 KEK 保护传输高价值的核心表全部走硬件模块运算普通字段走软件实现。这样既保证了核心资产的安全又不至于让硬件成为性能瓶颈。还有一点值得提醒如果用的是云上的密码服务务必确认它的服务等级和可用性承诺并且做好降级预案。密码服务挂了整个业务写不进去也读不出来这比数据库挂了还致命。我一般会在设计里加一个“降级开关”紧急情况下可以临时切到软件实现应急但要记录审计日志并在恢复后立即切回。5. 内部技术自查要准备什么、怎么查、哪里容易扣分改造做完了不等于结束能不能经得起自查、经得起第三方看是另一回事。我参与过几轮内部自查也帮朋友看过他们准备的材料总结下来最常被问到的就是“四性”——真实性、机密性、完整性、不可否认性。这四个词听着抽象翻译成人话就是能证明是谁在操作、数据没被别人看到、数据没被改过、做过的事赖不掉。技术侧要做的就是为每一条拿出证据。真实性的技术落点主要是身份认证。用户登录用什么认证方式、服务间调用如何验身份、设备接入如何证明自己是合法设备。这一项的常见问题是“单向认证冒充双向认证”——客户端验证了服务端服务端没验证客户端攻击者可以伪造客户端。改造时要确保双向认证完整。机密性的技术落点是加密覆盖度。哪些数据加密了、用的什么算法、密钥怎么管的。这里最容易被揪出来的是“日志和缓存的明文泄露”——数据库字段加密了但日志里打印了明文参数Redis 里存了明文对象。自查时要顺着数据流走一遍从请求进入到落库到出库到响应每个环节都问一句“这里是不是明文”。完整性的技术落点是有没有防篡改机制。接口参数有没有签名、关键数据有没有 MAC、配置文件有没有校验。很多系统的接口签名是“可选”的客户端不签名也能通这就是漏洞。要么强制校验要么明确说明为什么不需要。不可否认性的技术落点主要看签名是否使用了带时间戳的机制、私钥是否由本人持有。内部系统里这一项通常要求不高但涉及资金、审批、合同的场景必须有。5.1 一份可执行的自查清单我按技术条线整理了一份清单按优先级从高到低排你可以照着一条条过。密码算法清单系统里用到的所有加密算法、哈希算法、签名算法是否全部列明有无残留的非合规算法比如某处还在用 MD5 做校验。算法用途正确性有没有把非对称算法当对称算法用有没有用哈希代替加密有没有用 ECB 模式。密钥管理密钥生成来源、存储位置、访问权限、轮转周期、销毁方式五项是否都有明确记录。密码模块边界哪些运算在硬件内完成哪些在应用内存完成是否与设计文档一致。传输加密所有跨网络调用是否都走了加密通道内部服务间调用是否也加密很多团队只做了外网。存储加密敏感字段清单是否完整加密后的密文格式是否统一版本号、IV、密文的排列顺序。日志脱敏日志、错误堆栈、监控指标里是否有明文敏感数据。密钥硬编码扫描全仓库搜索密钥特征串、Base64 长串、.pem/.key/.p12文件是否误提交。降级与应急密码服务不可用时的降级路径是否明确降级操作是否有审计。测试覆盖加解密、签名验签的单元测试和集成测试是否覆盖了密钥轮转、密钥版本兼容、密文损坏等异常分支。5.2 几个高频扣分点提前避开能省很多事第一个硬编码密钥。这条年年讲年年有。最隐蔽的形式是“测试密钥上了生产”——配置文件里有个默认密钥本地开发方便用的生产环境忘了覆盖。防的办法是把密钥配置项设为启动时必须存在缺了直接启动失败而不是悄悄用默认值。第二个摘要算法被当成加密用。有人觉得“我把身份证号做个 SM3 哈希存起来就安全了”结果业务需要展示脱敏后的手机号前三位后四位哈希值还原不出来功能做不了。更糟的是如果输入空间小比如手机号哈希可以被彩虹表反查。口令存储必须用加盐慢哈希普通数据要可逆就得用对称加密这两者不能混。第三个随机数质量。IV、盐值、会话密钥都依赖随机数。用java.util.Random、用当前毫秒时间戳、用自增序列都是严重问题。统一用SecureRandom并且注意在某些环境下要显式指定强随机算法。第四个签名不校验返回值。验签函数的返回值是布尔值有的代码写完不判断或者 catch 住异常当通过。这种写法在静态扫描里一搜一大把属于典型的低级错误但危害极大。第五个密文没有版本标识。前面提过轮转时才知道痛苦。密文格式建议固定为版本号(1B) || IV(16B) || 密文 || 认证标签版本号留在最前面解密时先读版本再选密钥。6. 常见问题与排查实录这一章我把过去几年遇到过、以及同行问过我的问题整理成速查表都是具体症状加定位思路不涉及理论。症状可能原因定位与解决签名验不过两端代码都“看起来对”distid 不一致、公钥点格式不一致、摘要计算范围不一致先确认 distid 配置项再抓包比对公钥前缀是 02/03 还是 04最后确认签名前是否对数据做了额外编码SM4 解密出现乱码但长度正确IV 不一致、密钥错误但碰巧长度对、模式不匹配打印 IV 的十六进制比对确认两端都用 CBC 而非 CTR检查是否一方做了填充另一方没做加密后数据入库报长度超限密文是明文的 1.4 倍以上含 Base64扩列宽或改用二进制类型存储避免 Base64 膨胀国密 TLS 握手失败套件不支持、证书链不完整、中间人代理截断用抓包工具看 ClientHello 里的套件列表确认服务端配置检查证书链是否包含中间证书加解密 CPU 占用飙到 90%纯软件实现、未走硬件指令、单条数据过大开启硬件指令、把大文件改成分块流式处理、把运算挪到密码机密钥轮转后老数据读不出来密文无版本号无法区分新旧密钥短期做双密钥试探解密长期在密文头部加版本号密码服务偶发超时连接池不足、并发限流过低、网络抖动加连接池、调限流阈值、关键路径上加本地缓存注意缓存的密钥材料有安全风险移动端加解密结果和 PC 端不一致字符编码不同、JSON 序列化顺序不同、时间戳格式不同约定统一的 UTF-8 编码和字段排序规则签名前先规范化6.1 性能问题SM4 慢得影响业务怎么办这是最实际的痛点。我按性价比排了四种办法。第一启用硬件指令。部分 CPU 支持 SM3、SM4 的专用指令或者支持在加速卡上跑。开启后性能提升通常是三到十倍这是最高性价比的优化几乎没有副作用。判断方法很简单跑一下基准测试对比开启前后的吞吐或者查 CPU 型号特性表。Java 侧可以留意 JCE 提供者是否支持硬件加速有些厂商的实现会自动检测。第二批处理和流式处理。单条小数据调一次加密的开销很大一部分在函数调用和上下文切换上。把 100 条 200 字节的数据攒成一批做一次调用吞吐能翻几倍。大文件则相反要分块流式处理避免一次性读进内存导致 GC 压力。第三运算外移。把加解密从业务线程里挪出去交给独立的密码服务或异步队列。业务线程只负责拿结果不阻塞。这个改法对架构有一定侵入但效果明显尤其是加解密耗时占接口总耗时 30% 以上的场景。第四缓存会话密钥。TLS 握手、密钥协商这些操作的代价远高于对称加解密。复用会话能省掉大量非对称运算。注意复用的有效期要设合理太长有安全隐患太短起不到效果我一般设 10 分钟左右。6.2 兼容性问题新老客户端并存期间怎么过渡任何改造都不是一刀切老客户端还在跑新协议又必须推这就得做灰度。我的做法是双轨并行 能力协商。服务端同时支持新旧两套协议通过请求头或握手阶段的扩展字段识别客户端能力。新客户端上报支持国密套件走新链路老客户端不带标记走旧链路但记录告警。灰度期间监控两条链路的成功率、耗时、错误率确认新链路稳定后再逐步把老链路的流量迁过来最后设一个下线日期。数据层的双轨更麻烦。存量数据是旧算法加密的新数据用新算法读的时候要能自动识别。这里的技巧是在密文头部写版本号写入时固定写新版本读取时按版本分支处理。存量数据不急着重加密可以在业务低峰期用离线任务批量迁移迁移过程中保持双读。还有一类兼容问题容易被忽略编码和填充约定。同一个算法A 团队用 PKCS7 填充B 团队用 ZeroPaddingA 团队用 UTF-8 编码字符串B 团队用 GBK。这些差异在单测里发现不了一联调就炸。我的经验是把这些约定写进接口文档的第一页并且提供一个“联调自检脚本”双方各跑一遍看结果是否一致能省掉大量来回扯皮的时间。7. 我个人在这些年改造里攒下的几条经验说到最后讲几条不那么技术、但我觉得更有价值的东西。第一条先分级再动手别一上来就全量改造。我见过团队一开始就把所有接口所有字段全上加密结果性能崩了、工期拖了、业务方怨声载道。正确的做法是先做数据分级把真正敏感的挑出来第一批只改这些跑顺了再扩。这跟装修一样先把水电做好别一上来就贴墙纸。第二条密钥管理是设计的起点不是收尾工作。很多团队把算法调用写完了才想起来“密钥放哪”这时候架构已经定型改起来很痛苦。我的习惯是在方案评审的第一页就画密钥流向图谁产生、谁持有、谁使用、怎么换图画不清楚说明方案没想清楚别急着写代码。第三条留一手降级方案但一定要有审计。密码服务、密码机都是会出故障的组件业务不能因为加密不了就全站瘫痪。降级不是丢人的事关键是降级要留痕事后能追查。我在设计里会明确写清楚“什么情况下允许降级、降级后多长时间必须恢复、谁来批准”把规则定在前面。第四条文档和接口约定比代码重要。跨团队对接时扯皮最多的从来不是算法实现而是 distid 不一致、编码不一致、序列化顺序不一致这些“小事”。把约定写成文档、做成自检脚本比在群里解释一百遍有用。第五条别迷信“用了国密就安全”。算法只是工具密钥管理、边界划分、日志脱敏、权限控制哪一环松了都是缺口。我在实际评审里看到算法调用写得完美但密钥写在配置文件里的方案评分不会比一个用开源库但密钥管理严谨的方案高。安全是系统工程这句话虽然老套但每次踩坑都会重新体会一遍。如果这套东西还要继续往下做我下一步会关注两件事一是把字段级加密和查询能力结合起来加密后的字段没法直接模糊查询这是很多业务落地的真实阻碍目前的解法是保留分词索引或者做确定性加密但牺牲一部分安全性二是把密钥轮转做成全自动的人工介入的环节越少出错的概率越低。这两块我先记在本子上等有实际进展再拿出来讲。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑