SM2数字签名:传输完整性
国资监管的数据传输里,SM2数字签名是标准配置。但很多对接工程师对它的理解停留在"按文档调通了"——为什么用签名、签名验证的是什么、和加密有什么区别,说不清楚。这篇把SM2数字签名在传输完整性场景的原理和实操讲透,包括完整的签名生成和验证流程。
一、签名解决什么问题
先分清三个概念:加密、签名、摘要。
加密解决保密性(数据被截获也看不懂——防偷看)。签名解决完整性和不可抵赖(数据没被篡改、确实是对端发的——防篡改防冒充)。摘要是签名的基础(任意长度数据压缩成固定长度的指纹)。
传输场景的威胁模型:报送数据在网络传输中被中间人篡改(金额被改、记录被删)——加密防不了这个(加密保证传输中看不懂,但不保证解密后的数据没被动过,如果攻击者伪造了整份密文呢?)。签名补上这个缺口:发送方对数据签名,接收方验证签名——签名验证失败说明数据被动过,直接拒收。
国资监管的报送链路里,签名是 mandatory 的环节:报文要签名、监管端验签,这是制度性的安全要求,不是可选项。
二、SM2签名的算法流程
SM2签名的完整流程(原理层讲清,实操看下节)。
签名方(发送端)的步骤。步骤一:准备签名私钥。SM2密钥对(私钥自己保管、公钥给验证方),私钥的格式(256位整数,通常以hex或base64存储)。步骤二:计算摘要。对报文数据计算SM3摘要(国标摘要算法,输出256位,注意:SM2签名标准里,摘要计算前有个ZA预处理(用户ID加公钥参与的杂凑值),这是SM2区别于普通"先摘要再签名"的细节,库封装了这步但要知道它的存在)。步骤三:生成签名。用私钥对摘要做签名运算,输出签名值(r,s两个256位整数,通常拼接后hex/base64传输)。
验签方(接收端)的步骤。步骤一:拿到报文加签名值加签名方的公钥(公钥的交换提前完成,证书或线下交换)。步骤二:同样方式计算报文的SM3摘要(含ZA预处理)。步骤三:用公钥和签名值做验证运算,输出真或假。
验证为真的含义:报文和签名时完全一致(一个字节没动)、签名确实由对应私钥生成(对端身份确认,私钥只有对端有)。两个保证合起来:完整性和不可抵赖。
三、Java实操:签名和验签代码
实操环境:BouncyCastle的SM2实现(Java生态最常用的国密库)。
签名端代码:
// 1. 加载私钥(hex字符串)
BigInteger privateKey = new BigInteger(privateKeyHex, 16);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(
privateKey, GMNamedCurves.getByName("sm2p256v1"));
// 2. SM2签名(withSm3表示使用SM3摘要+ZA预处理)
SM2Signer signer = new SM2Signer();
signer.init(true, new ParametersWithID(privateKeyParameters, "1234567812345678".getBytes()));
signer.update(messageBytes, 0, messageBytes.length);
byte[] signature = signer.generateSignature();
// signature即签名值,转hex或base64随报文发送
String signatureHex = Hex.toHexString(signature);
验签端代码:
// 1. 加载公钥(hex字符串,04开头非压缩格式)
byte[] publicKeyBytes = Hex.decode(publicKeyHex);
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(
curve.decodePoint(publicKeyBytes), GMNamedCurves.getByName("sm2p256v1"));
// 2. SM2验签
SM2Signer verifier = new SM2Signer();
verifier.init(false, new ParametersWithID(publicKeyParameters, "1234567812345678".getBytes()));
verifier.update(messageBytes, 0, messageBytes.length);
boolean isValid = verifier.verifySignature(Hex.decode(signatureHex));
// isValid为true:数据完整且来源可信;false:拒收并告警
三个实操要点。要点一:UserID(默认值"1234567812345678"),签名和验签两端的UserID必须一致(这是ZA预处理的输入,不一致则验签必失败,跨系统对接的高频坑)。要点二:签名的对象是报文的原始字节(报文体按约定的编码,UTF-8的JSON串,两边字节级一致才能验过;一边经过转码或格式化,字节不一致验签失败)。要点三:密钥格式(04开头的非压缩公钥、各自系统的私钥保管,测试环境先用固定测试密钥跑通,生产换正式密钥对)。
四、报文签名的工程集成
单次签名验证的代码通了,工程化集成要做三件事。
- 事一:签名的报文结构约定。报送报文的规范结构:
- 签名覆盖的范围:header加body的序列化字节(header里的timestamp和nonce参与签名,防重放攻击:同一签名在时间窗外或nonce重复的拒收)。
- 事二:验签失败的统一处理。验证失败的处理链:拒收(验签失败的数据不进业务处理)、告警(连续验签失败触发安全告警,可能是攻击也可能是配置错误,两种都要查)、记录(失败的报文和原因入日志,事后排查的依据)。常见的验签失败原因排序:两端报文序列化不一致(字段顺序、空值处理)>UserID不一致>密钥配错(用了测试公钥验生产签名)>真篡改(最少见但最严重)。
- 事三:密钥的轮换管理。密钥的定期轮换(建议一年,或证书到期时同步轮换):新密钥的预分发(新公钥提前给对端、双密钥并行期,新旧签名都能验,过渡平稳)、切换完成旧密钥废止。密钥轮换的台账(用搭贝这类平台配置密钥管理台账:密钥对的生命周期状态、轮换的时间计划、到期前的自动提醒,手工管理的密钥,过期断送的是整条报送链路)。
五、签名和加密的组合使用
传输安全的完整方案是签名加密的组合(不是二选一)。
组合的标准流程:发送端先签名后加密(对报文签名,签名值附在报文里,然后把"报文+签名"整体用SM4加密,SM4密钥用SM2加密分发)、接收端先解密后验签(SM2解密拿到SM4密钥、SM4解密拿到报文和签名、验签确认完整性)。
这个组合(SM2管密钥分发和签名、SM4管批量数据加密)是国密传输的标准架构,保密性(SM4加密)、完整性(SM2签名)、密钥安全(SM2的密钥交换)三位一体。国资监管的传输规范用的就是这个架构。
六、性能的考量
签名验签的性能特征:SM2签名比SM4加密慢两个量级(非对称运算的天然代价)。
性能的优化策略:策略一:签名对象最小化(大报文只签摘要,报文本身用SM4加密传输,签名只需要对摘要签,运算量固定不随报文大小涨)。策略二:批量验签的并行(多报文的验签用线程池并行,验签是CPU密集型,多核并行的收益直接)。策略三:硬件加速(高吞吐场景上密码卡或密码机,SM2运算的硬件加速十倍级,监管报送这种频率的场景软件实现足够,秒级高频的场景才需要硬件)。
- 联调阶段的第一步做签名比对:双方各自打印待签名串(stringToSign),邮件或工单交换比对。串一致而验签失败,查摘要实现;串不一致,查拼接和编码。这一步省下八成的联调时间。
- 密钥的配置管理用环境隔离:开发、测试、生产三套密钥对,配置中心分环境存储。上线检查清单加一条:验签配置指向生产密钥的确认。
- 签名的兼容性测试覆盖老报文:新版本签名逻辑上线前,用历史报文样本回归验证。签名逻辑的隐形变更(库版本升级引入的行为差异)是生产事故的隐蔽来源。
- 监控大盘的签名指标两条:验签成功率(正常应接近百分之百,骤降说明配置或攻击)、平均验签耗时(性能基线的监控)。两条异常都设告警。
- 应急演练做过一次验签全挂的场景:原因定位到证书到期,恢复用了四十分钟。之后的改进是证书到期前六十天开始三级提醒。演练暴露的坑,比事故暴露的便宜。
常见问题
Q:签名验证失败最常见的原因是什么?
两端对"被签名数据"的理解不一致排第一:发送方签的是原始JSON串,接收方拿到后做了反序列化再序列化(字段顺序变了、空格变了),字节不一致验签必然失败。解法:约定签名覆盖的确切范围(发送方签名时的报文字节,base64编码后随签名一起传,接收方对这份base64解码后的字节验签,不要自己重新序列化)。
Q:SM2和RSA签名可以互相验吗?
不能。算法体系完全不同(椭圆曲线对大整数分解),SM2签名只有SM2能验。和国外系统对接(对方只有RSA)的场景:两套签名并行(报文同时带SM2签名和RSA签名,各自生态验各自的,合规和互通的两全)或网关转换(外联网关做签名的转换,内部的国密体系不变,对外适配对方的算法)。
Q:私钥泄露了怎么办?
立即响应的动线:停用(泄露密钥的签名立即失效声明——通知验证方废止该公钥)、换钥(新密钥对生成、公钥重新分发)、排查(泄露原因:私钥文件权限、传输过程、人员问题——不查清原因,新密钥同样会泄)、影响评估(泄露期间被伪造签名的报文范围——和验证方核对可疑签名记录)。私钥保管的事前规范:加密存储(明文私钥文件是重大隐患)、专人保管(私钥的接触面最小化)、离线备份(密钥的灾难恢复备份,加密介质保险柜)。
Q:测试环境和生产环境的密钥怎么管?
严格分离:测试密钥对(公开的测试密钥——内网测试随便用,文档里甚至可以写死)和生产密钥对(正式交换、加密保管)是两套,配置里明确区分(环境变量或配置中心的分环境配置——生产代码里硬编码测试密钥的事故,比想象中常见)。上线前的检查清单加一项:签名验签配置的生产密钥确认。
浙公网安备 33010602011771号