From c3c0c4709519937c84fc5a568ccc0c18c7278d9b Mon Sep 17 00:00:00 2001 From: zhoubin <2023106681@qq.com> Date: Fri, 3 Dec 2021 16:06:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BD=91=E8=81=94=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E9=AA=8C=E7=AD=BE=E6=8E=A5=E5=8F=A3=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=93=B6=E8=81=94=E7=AD=BE=E5=90=8D=E9=AA=8C=E7=AD=BE?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/sunyard/SydApi.java | 6 +- .../java/com/sunyard/inf/SydCryptionApi.java | 9 + .../sunyard/inf/customize/SydUnionPayApi.java | 33 ++ .../java/racal/sunyard/main/SydApi4j.java | 303 +++++++++++++++++- .../sunyard/sydapi/test/TestAllMethods.java | 13 + 5 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/sunyard/inf/customize/SydUnionPayApi.java diff --git a/src/main/java/com/sunyard/SydApi.java b/src/main/java/com/sunyard/SydApi.java index 6f315f3..758df96 100644 --- a/src/main/java/com/sunyard/SydApi.java +++ b/src/main/java/com/sunyard/SydApi.java @@ -5,6 +5,7 @@ import com.sunyard.inf.*; import com.sunyard.inf.customize.SydHAApi; import com.sunyard.inf.customize.SydQNUCApi; import com.sunyard.inf.customize.SydSDNXApi; +import com.sunyard.inf.customize.SydUnionPayApi; import racal.sunyard.main.SydApiBuilder; import java.io.InputStream; @@ -13,7 +14,10 @@ import java.io.InputStream; * Created by Cheney on 2017/11/11. * 程序调用接口 */ -public interface SydApi extends SydRawApi, SydCertApi, SydCryptionApi, SydGenApi, SydSVApi, SydHAApi, SydQNUCApi, SydPledge, SydPINOpt, SydManufactureApi, SydZYOption { +public interface SydApi extends + SydRawApi, SydCertApi, SydCryptionApi, SydGenApi, + SydSVApi, SydHAApi, SydQNUCApi, SydPledge, SydPINOpt, + SydManufactureApi, SydZYOption, SydUnionPayApi { byte ALG_AES = 0x30; byte ALG_SM4 = 0x31; diff --git a/src/main/java/com/sunyard/inf/SydCryptionApi.java b/src/main/java/com/sunyard/inf/SydCryptionApi.java index 78f142f..9bcce0c 100644 --- a/src/main/java/com/sunyard/inf/SydCryptionApi.java +++ b/src/main/java/com/sunyard/inf/SydCryptionApi.java @@ -19,6 +19,15 @@ public interface SydCryptionApi extends SydSYMApi, SydASYMApi, SydHashApi { */ interface SydHashApi { + /** + * 计算SM3散列值 + * @param sn 证书序号 + * @param userId 用户ID + * @param msg 原始数据,二进制的消息块 + * @return + */ + byte[] SM3HashSNaId(String sn, String userId, byte[] msg); + /** * 输入对指定的原始数据和公钥,通过 SM3 算法计算数据的散列值。 * @param publicKey 公钥的 DER 编码(可以为空) diff --git a/src/main/java/com/sunyard/inf/customize/SydUnionPayApi.java b/src/main/java/com/sunyard/inf/customize/SydUnionPayApi.java new file mode 100644 index 0000000..347de71 --- /dev/null +++ b/src/main/java/com/sunyard/inf/customize/SydUnionPayApi.java @@ -0,0 +1,33 @@ +package com.sunyard.inf.customize; + +/** + * 银联接口 + */ +public interface SydUnionPayApi { + /** + * 用指定的证书私钥对指定的数据进行数字签名。 + * 银联 + * @param pCertSerial 输入。签名 X509 证书的序列号。 + * @param userId 输入。用户id。 + * @param pOrgData 输入。 待签名的原始数据。 + * @return 签名数据(BASE64 编码)。 + */ + String netSign(String pCertSerial, String userId, byte[] pOrgData); + + + /** + * 用指定证书对指定的原始数据进行数字签名验证。 + * 银联 + * @param pCertSerial 输入。 签名 X509 证书的序列号。 + * @param userId 输入。用户id。 + * @param pOrgData 输入。 待签名的原始数据。 + * @param pSignData 输入。 签名数据(BASE64 编码)。 + * @return true 表示验证正确;false 表示失败; + */ + boolean netVerify( + String pCertSerial, + String userId, + byte[] pOrgData, + String pSignData + ); +} diff --git a/src/main/java/racal/sunyard/main/SydApi4j.java b/src/main/java/racal/sunyard/main/SydApi4j.java index e76a3be..b0a0056 100644 --- a/src/main/java/racal/sunyard/main/SydApi4j.java +++ b/src/main/java/racal/sunyard/main/SydApi4j.java @@ -2232,9 +2232,139 @@ public class SydApi4j implements SydApi { return ((PacketSection) ret.get("random")).getString(); } - // 测试通过 + + /** + * 用指定的证书私钥对指定的数据进行数字签名。 + * 银联 + * @param pCertSerial 输入。签名 X509 证书的序列号。 + * @param userId 输入。用户id。 + * @param pOrgData 输入。 待签名的原始数据。 + * @return 签名数据(BASE64 编码)。 + */ + @Override + public String netSign(String pCertSerial, String userId, byte[] pOrgData) { + + // 针对传化需求定制 + // 获取协议 + Proto6C proto = new Proto6C(); + + // 先进行 HASH + pOrgData = SM3HashSNaId(pCertSerial, userId, pOrgData ); + + // 填充数据 + PacketSN sn = PacketSN.gen(); + HashMap packet = new HashMap(); + packet.put("header", sn.getSn().array()); + HashMap data = new HashMap(); + data.put("certificateModel$option", 0); + data.put("certificateSn", pCertSerial); + data.put("inData", pOrgData); + data.put("dataFlag", 'P'); + packet.put("data", data); + + // 渲染发送 + ByteBuffer bb = proto.rend(packet); + synchronized (this) { + // 响应解析 + bb = syncRead(syncSend(bb)); + } + + //ProtocolParser parser = pset.getProtocolResParse(); + Packet p = proto.parse(bb); + Map ret = p.toMap(); + if ( + !Arrays.equals(sn.getSn().array(), ((PacketSection) ret.get("header")).getBytes()) + ) { + throw new SydApiException("包序号不匹配", -4); + } + ret = (Map) ret.get("data"); + PacketSection error = (PacketSection) ret.get("error"); + if (!"00".equals(error.getString())) { + throw new SydApiException(error.getInfo(), error.autoInt()); + } + + return ((PacketSection) ret.get("sign")).getString(); + } + + /** + * 用指定证书对指定的原始数据进行数字签名验证。 + * 银联 + * @param pCertSerial 输入。 签名 X509 证书的序列号。 + * @param userId 输入。用户id。 + * @param pOrgData 输入。 待签名的原始数据。 + * @param pSignData 输入。 签名数据(BASE64 编码)。 + * @return true 表示验证正确;false 表示失败; + */ + @Override + public boolean netVerify(String pCertSerial, String userId, byte[] pOrgData, String pSignData) { + // 获取协议 + Proto6D proto=new Proto6D(); + + pOrgData = SM3HashSNaId(pCertSerial, userId, pOrgData ); + + // 填充数据 + PacketSN sn = PacketSN.gen(); + HashMap packet = new HashMap(); + packet.put("header", sn.getSn().array()); + HashMap data = new HashMap(); + data.put("certificateModel$option", 0); + data.put("certificateSn", pCertSerial); + data.put("inData", pOrgData); + data.put("sign", pSignData); + data.put("dataFlag", 'P'); + packet.put("data", data); + + // 渲染发送 + ByteBuffer bb = proto.rend(packet); + synchronized (this) { + // 响应解析 + bb = syncRead( syncSend(bb) ); + } + //ProtocolParser parser = pset.getProtocolResParse(); + Packet p = proto.parse(bb); + Map ret = p.toMap(); + if ( + !Arrays.equals(sn.getSn().array(), ((PacketSection) ret.get("header")).getBytes()) + ) { + throw new SydApiException("包序号不匹配", -4); + } + ret = (Map) ret.get("data"); + PacketSection error = (PacketSection) ret.get("error"); + if (!"00".equals(error.getString())) { + throw new SydApiException(error.getInfo(), error.autoInt()); + } + + return true; + } + + /** + * 用指定的证书私钥对指定的数据进行数字签名。 + * 网联 + * @param pCertSerial 输入。签名 X509 证书的序列号。 + * @param pOrgData 输入。 待签名的原始数据。 + * @return 签名数据(BASE64 编码)。 + */ @Override public String netSign(String pCertSerial, byte[] pOrgData) { + return netSign(pCertSerial, null, pOrgData); + } + + /** + * 用指定证书对指定的原始数据进行数字签名验证。 + * 网联 + * @param pCertSerial 输入。 签名 X509 证书的序列号。 + * @param pOrgData 输入。 待签名的原始数据。 + * @param pSignData 输入。 签名数据(BASE64 编码)。 + * @return true 表示验证正确;false 表示失败; + */ + @Override + public boolean netVerify(String pCertSerial, byte[] pOrgData, String pSignData) { + return netVerify(pCertSerial, null, pOrgData, pSignData); + } + + + // 测试通过 + private String _netSign(String pCertSerial, byte[] pOrgData) { // 获取协议 @@ -2276,8 +2406,7 @@ public class SydApi4j implements SydApi { } // 测试通过 - @Override - public boolean netVerify(String pCertSerial, byte[] pOrgData, String pSignData) { + private boolean _netVerify(String pCertSerial, byte[] pOrgData, String pSignData) { // 获取协议 //ProtocolRender render = pset.getProtocolReqRender("6D"); Proto6D proto = new Proto6D(); @@ -3811,6 +3940,173 @@ public class SydApi4j implements SydApi { return ((PacketSection) ret.get("data")).getBytes(); } + public byte[] SM3HashSNaId(String sn, String userId, byte[] msg) { + return SM3Hash(null, sn, null, userId, msg); + } + + /** + * 计算SM3散列值 + * @param publicKey 公钥的 DER 编码(可以为空) + * @param csn 证书序号 + * @param cdn 证书DN + * @param userId 用户ID + * @param msg 原始数据,二进制的消息块 + * @return + */ + private byte[] SM3Hash(String publicKey, String csn, String cdn, String userId, byte[] msg) { + + // 填充数据 + PacketSN sn = PacketSN.gen(); + final int packLen = 2048; + int loop = msg.length / packLen; + if (msg.length % packLen > 0) { + loop++; + } + + // 保存上一轮的 hash + byte[] ctx = new byte[0]; + byte packtype; + int i = 0; + while (i < loop) { + if (0 == i) { + if (1 == loop) { + packtype = '0'; + } else { + packtype = '1'; + } + } else { + if (i == (loop - 1)) { + packtype = '3'; + } else { + packtype = '2'; + } + } + ByteBuffer bb = ByteBuffer.allocate(3000); + + byte[] ret = new byte[2]; + bb.put(ret); + bb.put(sn.getSn().array()); + bb.put("76".getBytes()); + bb.put(packtype); + + + int tmplen = (i < (loop - 1) || !(msg.length % packLen > 0)) ? packLen : (msg.length % packLen); + + // 填充长度 + bb.put(String.format("%04d", tmplen).getBytes()); + + // 填充数据 + bb.put(msg, i * packLen, tmplen); + + + if ('0' == packtype || '1' == packtype) { + + + if ( null != csn || null != cdn ){ + if (userId != null) { + bb.put(String.format("%02d", userId.length()).getBytes()); + bb.put(userId.getBytes()); + } else { + bb.put(String.format("%02d", 16).getBytes()); + bb.put("1234567812345678".getBytes()); + } + + // 通过证书查找公钥 + bb.put("P".getBytes()); + byte[] content; + if ( null != csn ){ + content = csn.getBytes(); + bb.put( "0".getBytes() ); + } else { + content = cdn.getBytes(); + bb.put( "1".getBytes() ); + } + + bb.put( String.format("%04d", content.length ).getBytes() ); + bb.put( content ); + } + else if (null == publicKey) { + + // 无公钥 + + bb.put(String.format("%02d", 0).getBytes()); + bb.put("".getBytes()); + bb.put(String.format("%04d", 0).getBytes()); + bb.put("".getBytes()); + } + else { + // 传入公钥 + bb.put(String.format("%02d", 16).getBytes()); + bb.put("1234567812345678".getBytes()); + bb.put(String.format("%04d", publicKey.length() / 2).getBytes()); + bb.put(Util.hexString2Bytes(publicKey)); + } + + + } else if ('2' == packtype || '3' == packtype) { + + bb.put(String.format("%04d", ctx.length).getBytes()); + bb.put(ctx); + + } + + + int len = bb.position() - 2; + + bb.put(0, (byte) (0xFF & (len / 256))); + bb.put(1, (byte) (0xFF & (len % 256))); + + + synchronized (this) { + // 响应解析 + bb = syncRead(syncSend(bb)); + } + + + bb.flip(); + + // 忽略头部 + bb.get(new byte[12], 0, 12); + + + // 检测错误 + byte[] error = new byte[2]; + + bb.get(error, 0, 2); + + int e = Util.a2i(error, 16); + + if (0 != e) { + throw new SydApiException(0x80 == e ? "数据长度错" : "输入长度错", e); + } + + + if ('0' == packtype || '3' == packtype) { + byte[] hash = new byte[32]; + bb.get(hash, 0, 32); + return hash; + } else if ('1' == packtype || '2' == packtype) { + + byte[] hashLen = new byte[4]; + + bb.get(hashLen, 0, 4); + + + byte[] hash = new byte[Util.a2i(hashLen, 10)]; + + bb.get(hash, 0, hash.length); + + ctx = hash; + + + } + + i++; + } + + return new byte[0]; + } + @Override public byte[] SM3Hash(byte[] msg) { return SM3Hash(null, msg); @@ -3823,6 +4119,7 @@ public class SydApi4j implements SydApi { } + @Override public byte[] SM3Hash(String publicKey, byte[] msg, int block, byte[] mac) { diff --git a/src/test/java/com/sunyard/sydapi/test/TestAllMethods.java b/src/test/java/com/sunyard/sydapi/test/TestAllMethods.java index fbe78cf..c8ad01c 100644 --- a/src/test/java/com/sunyard/sydapi/test/TestAllMethods.java +++ b/src/test/java/com/sunyard/sydapi/test/TestAllMethods.java @@ -1254,6 +1254,19 @@ public class TestAllMethods { System.out.println(b); } + // 测试通过 + // 6D + @Test + public void testNetVerify2() { + String sn = "1004282451"; + String userId = "1234567812345678"; + String data = "111"; + String s = api.netSign(sn, userId, data.getBytes()); + + boolean b = api.netVerify(sn, userId, data.getBytes(), s); + System.out.println(b); + } + // 测试通过 // 6E @Test