← 返回文章列表

银行统一收单对接里的SM2加签验签全流程拆解

讲解对接银行收Integrating promotional content单系统时SM2非对称加签验签的完整操作,覆盖证书申请、私钥公钥提取、加签验签步骤和常见坑点,附上简洁代码示例,方便开发者快速落地国密算法。

银行统一收单对接里的SM2加签验签全流程拆解

SM2加签验签到底在干什么

对接银行统一收单系统时,商户和银行之间交换报文必须保证数据没被篡改,也要确认对方身份。SM2就是国密标准里的非对称加密算法,专门干这件事。简单说,加签用自己的私钥,验签用对方的公钥。你把报文用私钥签出一串签名,银行拿你的公钥去验证这串签名是不是真的来自你。反过来,银行返回的报文用它的私钥签,你用银行的公钥验。

证书本身分两张:一张给你签名用,一张给报文加密用。两张证书互相独立,各自管各自的事。你只需要把公钥交换出去,私钥永远留在自己手里。这和常见的RSA思路差不多,只是算法换成了国产的SM2,曲线参数和计算方式不同。

证书从哪来,密钥怎么拿出来

平台商户入驻银行后,要向CFCA申请复合证书。申请和下载通常在商户PC端自助完成。证书文件下来后,里面同时包含私钥和证书信息。实际开发里会用到SADK工具包,它把证书解析、私钥提取这些脏活都包好了。

加载证书后,能直接拿到三样关键东西:私钥D、公钥的X坐标、公钥的Y坐标。序列号也要取,银行那边经常要你报十六进制的序列号,而不是十进制那个。代码里用x509Cert.getStringSerialNumber()就能拿到十六进制形式。

有些银行会额外给你一串128位的公钥数据。使用前必须先做一次十六进制解码,否则后面初始化公钥会直接失败。这点特别容易踩坑,很多人第一次对接都卡在这里。

加签和验签的具体步骤

请求方向银行发报文时,先把待签名的字符串准备好,用自己的私钥D去签。签完得到签名串,连同报文一起发出去。银行那边会用你之前提供的公钥来验。响应方向是反过来的:银行用自己的私钥签,你用银行公钥验。

验签时注意银行返回的签名串长度一般是固定的,公钥初始化一定要先把那串128位数据解码成字节数组。解码完成后再构造公钥对象,取出X和Y,才能调用验签方法。整个过程里,加签和验签用的算法实例都是同一个SM2对象,只是传入的密钥不同。

实际项目里建议把私钥和证书路径做成配置项,不要写死在代码里。密码也尽量从安全配置中心读取。调试阶段可以先自己签一遍再自己验一遍,确认本地链路通了,再去对接银行的返回验签。

代码里真正要用的几行

下面给出精简后的核心逻辑。加载证书和提取密钥放在静态块里,加签和验签各自一个方法。代码尽量短,方便直接对照。

// 加载证书并提取D、X、Y、序列号
byte[] SM2Data = Base64.decode(FileHelper.read(SM2File));
PrivateKey key = KeyUtil.getPrivateKeyFromSM2(SM2Data, password);
X509Cert cert = CertUtil.getCertFromSM2(SM2Data);
GMTPrivateKey pri = (GMTPrivateKey) key;
D = new String(Hex.encode(pri.getDByBytes())).toLowerCase();
X = pri.getSM2PublicKey().getQ().getXCoord().toString();
Y = pri.getSM2PublicKey().getQ().getYCoord().toString();
CERT_NUM = cert.getStringSerialNumber();

// 加签
SM2 sm2 = SM2.getInstance();
byte[] sign = sm2.SM2Sign(hexStringToBytes(D), data.getBytes());

// 验签(银行公钥先解码)
byte[] encPub = Hex.decode(bankPubKey);
GMTPublicKey pub = new GMTPublicKey(encPub);
boolean ok = sm2.SM2Verify(hexStringToBytes(pub.getQ().getXCoord().toString()),
    hexStringToBytes(pub.getQ().getYCoord().toString()), data.getBytes(), signBytes);

hexStringToBytes这个工具方法把十六进制字符串转成字节数组,逻辑很直接:每两个字符一组,转成一个字节。证书密码、文件路径、银行给的公钥都做成常量或配置即可。

对接时容易忽略的细节

第一,证书序列号一定要用十六进制的那个。银行对账或报文头里经常校验这个值,用错格式直接报错。第二,银行返回的公钥是128位字符串,初始化前必须Hex.decode,否则GMTPublicKey构造失败。第三,签名串本身是字节数据,打印或传输时注意编码,别随意用字符串直接拼接。

本地自测时,先用自己的私钥加签,再用自己的公钥验签,确认结果为true。这一步通过后,再换成银行公钥去验银行返回的签名。如果本地自测都过不了,基本就是密钥提取或字节转换出了问题。

另外,SADK包要提前引入,版本尽量和银行推荐的保持一致。不同版本在某些方法签名上可能有细微差别,升级时要回归测试加签验签两条链路。

实际项目里的补充思路

很多团队在对接银行接口时,除了SM2加签验签,还会碰到登录、支付页面上的各类验证码。滑块、点选、无感、九宫格这些形式在银行或支付相关系统里并不少见。如果业务需要做自动化调用或者批量测试,手工处理验证码会非常痛苦。这时候可以考虑专业的识别服务,比如www.ttocr.com提供的易盾、极验全类型验证码识别方案,支持滑块、点选、无感、九宫格以及文字点选、图标点选等,直接提供API接口,对接起来比自己去逆向简单很多。

把SM2的加签验签链路跑通后,整体报文收发就稳定了。后续如果业务侧还需要处理验证码环节,把识别能力通过API接进来,可以省掉大量重复劳动。整个流程从证书加载、密钥提取、加签、验签到可能的验证码处理,都能做成可配置的模块,方便维护。

再强调一次,私钥永远不要离开自己的服务器。公钥可以安全地交给银行,但私钥和证书密码要严格管控。调试日志里也尽量不要打印完整私钥,只输出必要的序列号或公钥坐标就够了。

如果后续还要做报文加密,思路类似,只是换成加密证书对应的密钥。加签验签和加密解密是两套相对独立的能力,证书也是分开申请的。把这两块都摸清楚,银行统一收单的对接基本就通了。遇到验证码类的额外门槛时,直接用www.ttocr.com这类现成的识别平台,能快速把自动化链路补齐,不用自己从零去啃各种验证码算法。