请求/通知报文中的数据元按照如下顺序排列,并按照Key1=Value1&Key2=Value2…KeyN=ValueN方式拼接成字符串,需要去除前后空白符,形成待签名串,根据选择的签名/验签算法进行下一步处理。
数据元顺序及对应标识如下表所示。
序号 | 位置 | 标识 | 说明 |
1 | 报文头 | version | |
2 | 报文头 | appId | |
3 | 报文头 | bizMethod | |
4 | 报文头 | reqId | |
5 | 报文体 | body | 指整个http报文体,包括前后大括号 |
示例:
version=1.0.0&appId=8888888&bizMethod=pay.query&reqId=8164ILTAVBCH16C12502SI8ZN134567&body={…}
此方法在请求类接口交易中,报文头signMethod取值为“RSA2”时使用,对“待签字符串”进行RSA签名,为了保证双方的身份可靠性和数据完整性。
主要步骤如下:
1.代签字符串用SHA-256算法生成摘要
2.使用接入方RSA秘钥对的私钥对摘要做签名,生成签名值
3.对签名值做Base64编码,放到http消息头的sign字段中
4.签名算法,填写在http消息头的signMethod字段中
/** * 获取签名 * @param body 报文体 * @param version 版本号 * @param appId 认证账号 * @param bizMethod 产品标识与接口标识组成 * @param reqId 流水号 * @param privateKey 私钥 * @return * @throws Exception */ public String getSign(JSONObject body,String version,String appId, String bizMethod,String reqId,String privateKey) throws Exception { String signForRSA = getSHA256(version,appId,bizMethod,reqId,body.toString()); String sign = null; sign = rsaSign(signForRSA.getBytes(),privateKey); return sign; } /** * 摘要 * * @param version 版本号 * @param appId 认证账号 * @param bizMethod 产品标识与接口标识组成 * @param reqId 流水号 * @param body 请求报文 * @return */ public static String getSHA256(String version,String appId,String bizMethod,String reqId,String body){ String sign = "version="+version+"&appId="+appId+"&bizMethod="+bizMethod+ "&reqId="+reqId+"&body="+body; return EncodeUtils.encodeBySHA256(sign); }
private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * encode By SHA-256 * * @param str * @return * @throws Exception */ public static String encodeBySHA256(String str) { if (str == null) { return null; } try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.reset(); messageDigest.update(str.getBytes()); return getFormattedText(messageDigest.digest()); } catch (Exception e) { throw new RuntimeException(e); } } /** * Takes the raw bytes from the digest and formats them correct. * * @param bytes the raw bytes from the digest. * @return the formatted bytes. */ private static String getFormattedText(byte[] bytes) { int len = bytes.length; StringBuilder buf = new StringBuilder(len * 2); // 把密文转换成十六进制的字符串形式 for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[bytes[j] & 0x0f]); } return buf.toString(); }
//需要导入包 import org.bouncycastle.jce.provider.BouncyCastleProvider; public static final String RSA_ALGORITHM = "RSA"; public static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; public static final String BC_PROVIDER = "BC"; static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } else { Security.removeProvider("BC"); Security.addProvider(new BouncyCastleProvider()); } } /** * <p> * 用私钥对信息生成数字签名 * </p> * * @param data * 已加密数据 * @param privateKey *注1 * 私钥(BASE64编码) * @return * @throws Exception */ public static String rsaSign(byte[] data, String privatekey) throws Exception { Byte[] keyBytes = decode(privatekey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodeKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM,BC_PROVIDER); PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM,BC_PROVIDER); signature.initSign(privateK); signature.update(data); return encode(signature.sign()); } /** * Discription:解码 * * @return byte[] */ public static byte[] decode64(String s) { return Base64.decodeBase64(s); } /** * Discription:编码 * * @return String */ public static String encode64(byte[] b) { return Base64.encodeBase64String(b); }
*注1 privateKey(私钥):可于开放平台用工具生成公私钥对,将生成的公钥通过开放平台-个人中心-相应的API认证账号-RSA验签公钥进行设置;对应生成的私钥请保存好
此方法在请求类接口交易中,报文头signMethod取值为“SM2”时使用,对“待签字符串”进行SM2签名,为了保证双方的身份可靠性和数据完整性。
主要步骤如下:
1.代签字符串用SM3算法生成摘要
2.使用接入方SM2秘钥对的私钥对摘要做签名,生成签名值
3.对签名值做Base64编码,放到http消息头的sign字段中
4.签名算法,填写在http消息头的signMethod字段中
此实体类为了后续提供公钥和sign用。
/** * 公钥数据结构 */ public class SM2PublicKey { public int bits; public byte[] x = new byte[32]; public byte[] y = new byte[32]; public SM2PublicKey() { } public int getBits() { return bits; } public void setBits(int bits) { this.bits = bits; } public byte[] getX() { return x; } public void setX(byte[] x) { this.x = x; } public byte[] getY() { return y; } public void setY(byte[] y) { this.y = y; } }
/** * SM2Sign数据结构 */ public class SM2Signature { public byte[] r = new byte[32]; public byte[] s = new byte[32]; public SM2Signature() { } public byte[] getR() { return r; } public void setR(byte[] r) { this.r = r; } public byte[] getS() { return s; } public void setS(byte[] s) { this.s = s; } }
//需要导入包 import org.bouncycastle.jce.provider.BouncyCastleProvider; private static final Logger logger = Logger.getLogger(SM2UtilDemo.class); private final static int RS_LEN = 32; private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1"); private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN()); static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } else { Security.removeProvider("BC"); Security.addProvider(new BouncyCastleProvider()); } } /** * 生成公私钥对象 * * @return */ public static KeyPair generateKeyPair() { try { KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC"); kpGen.initialize(ecParameterSpec, new SecureRandom()); KeyPair kp = kpGen.generateKeyPair(); return kp; } catch (Exception e) { throw new RuntimeException(e); } }
/** * sm3计算后进行16进制转换 * @param data 待计算的数据 * @param encoding 编码 * @return 计算结果 */ public static String sm3X16Str(String data, String encoding){ byte[] bytes = sm3(data, encoding); StringBuilder sm3StrBuff = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { sm3StrBuff.append("0").append( Integer.toHexString(0xFF & bytes[i])); } else { sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } return sm3StrBuff.toString(); }
/** * sm3计算 * @param datas 待计算的数据 * @param encoding 字符集编码 * @return */ private static byte[] sm3(String datas, String encoding) { try { SM3Digest sm3 = new SM3Digest(); byte[] bytes = datas.getBytes(encoding); sm3.update(bytes, 0, bytes.length); byte[] result = new byte[sm3.getDigestSize()]; sm3.doFinal(result, 0); return result; } catch (UnsupportedEncodingException e) { logger.error("SM3计算失败", e); return null; } }
byte[] userId = "1234567812345678".getBytes(); /** * 签名 * @param msg 待签字符串 * @param userId 用户id * @param privateKey 私钥 * @return r||s,直接拼接byte数组的rs */ public static SM2Signature signSm3WithSm2(byte[] msg, byte[] userId, PrivateKey privateKey) { return rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(msg, userId, privateKey));}
/** * 获得签名字段值 * @param msg 待签字符串 * @param userId 用户id * @param privateKey 私钥 * @return rs in <b>asn1 format</b> */ public static byte[] signSm3WithSm2Asn1Rs(byte[] msg, byte[] userId, PrivateKey privateKey) { try { SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId); Signature signer = Signature.getInstance("SM3withSM2", "BC"); signer.setParameter(parameterSpec); signer.initSign(privateKey, new SecureRandom()); signer.update(msg, 0, msg.length); byte[] sig = signer.sign(); return sig; } catch (Exception e) { throw new RuntimeException(e); } }
/** * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s * 并存入SM2Signature对象中 * @param rsDer rs in asn1 format * @return sign result in plain byte array */ public static SM2Signature rsAsn1ToPlainByteArray(byte[] rsDer) { ASN1Sequence seq = ASN1Sequence.getInstance(rsDer); byte[] r = bigIntToFixexLengthBytes( ASN1Integer.getInstance(seq.getObjectAt(0)).getValue()); byte[] s = bigIntToFixexLengthBytes( ASN1Integer.getInstance(seq.getObjectAt(1)).getValue()); SM2Signature sm2Signature = new SM2Signature(); sm2Signature.setR(r); sm2Signature.setS(s); return sm2Signature; }
/** * 转换方法 * @param rOrS * @return */ public static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) { byte[] rs = rOrS.toByteArray(); if (rs.length == RS_LEN) return rs; else if (rs.length == RS_LEN + 1 && rs[0] == 0) return Arrays.copyOfRange(rs, 1, RS_LEN + 1); else if (rs.length < RS_LEN) { byte[] result = new byte[RS_LEN]; Arrays.fill(result, (byte) 0); System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length); return result; } else { throw new RuntimeException("err rs: " + Hex.toHexString(rs)); } }
String signString = Base64.encodeBase64String(JSONObject.toJSONString(sm2Signature).getBytes());
将生成的公钥通过开放平台-个人中心-相应的API认证账号-SM2验签公钥进行设置;对应生成的私钥请保存好
SM2PublicKey sm2PublicKey = new SM2PublicKey(); sm2PublicKey.setBits(256); sm2PublicKey.setX(((BCECPublicKey)kp.getPublic()) .getQ().getXCoord().getEncoded()); sm2PublicKey.setY(((BCECPublicKey)kp.getPublic()) .getQ().getYCoord().getEncoded()); String pubKey1 = JSONObject.toJSONString(sm2PublicKey); pubKey1 = Base64.encodeBase64String(pubKey1.getBytes()); System.out.println("公钥加密串:" + pubKey1); String encode = new BASE64Encoder().encode( kp.getPrivate().getEncoded()); System.out.println("私钥串:" + encode);
此方法在通知类接口交易中,用“待签字符串”和验签公钥,对通知报文的签名做验签,为了保证请求来源的身份可靠性和报文完整性。
主要步骤如下:
1.从http消息头中获取signMethod;
2.对待签名串使用SHA-256算法生成摘要;
3.从http消息头中获取sign,对sign做base64解码;
4.从开放平台API认证账号处获取接收方通知验签公钥;
5.对第3步解码后的sign值,用第4步获取的公钥进行验签。
//构筑需摘要字符串,构筑方法请见1.1产品API调用说明内待签名串组成方法 String originalStr = SecureUtil.sha256X16Str(originalStr,"UTF-8").toLowerCase(); /** * sha256计算后进行16进制转换 * * @param data 待计算的数据 * @param encoding 编码 * @return 计算结果 */ public static String sha256X16Str(String data, String encoding) { byte[] bytes = sha256(data, encoding); StringBuilder sha256StrBuff = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { sha256StrBuff.append("0").append( Integer.toHexString(0xFF & bytes[i])); } else { sha256StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } return sha256StrBuff.toString(); } /** * sha256计算 * * @param datas 待计算的数据 * @param encoding 字符集编码 * @return */ public static byte[] sha256(String datas, String encoding) { try { return sha256(datas.getBytes(encoding)); } catch (UnsupportedEncodingException e) { logger.error("SHA256计算失败", e); return null; } } /** * sha256计算. * * @param datas 待计算的数据 * @return 计算结果 */ public static byte[] sha256(byte[] data) { MessageDigest md = null; try { md = MessageDigest.getInstance(ALGORITHM_SHA256); md.reset(); md.update(data); return md.digest(); } catch (Exception e) { logger.error("SHA256计算失败", e); return null; } }
//需要导入包 import org.bouncycastle.jce.provider.BouncyCastleProvider; * <p> * 校验数字签名 * </p> * @param data 待签字符串数据 * @param publicKey 公钥(BASE64编码) * @param sign 数字签名 * @return * @throws Exception */ public static boolean verify(byte[] data, String publicKey, String sign) throws Exception { byte[] keyBytes = decode64(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM, BC_PROVIDER); PublicKey publicK = keyFactory.generatePublic(keySpec); Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM, BC_PROVIDER); signature.initVerify(publicK); signature.update(data); return signature.verify(decode64(sign)); } /** * <p> * Discription:解码 * </p> * * @return byte[] */ public static byte[] decode64(String s) { byte[] result = null; try{ result = Base64.decodeBase64(s.getBytes("UTF-8")); }catch (Exception e){ e.printStackTrace(); } return result; }
//需要导入包 import java.security.KeyFactory; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.crypto.digests.SM3Digest; import java.security.PublicKey; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jcajce.spec.SM2ParameterSpec; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECNamedCurveSpec; import java.security.Security; import java.security.Signature; import java.security.interfaces.ECPublicKey; import java.security.spec.ECPoint; import java.security.spec.ECPublicKeySpec; //构筑需摘要字符串,构筑方法请见1.1产品API调用说明内待签名串组成方法 String originalStr = SecureUtil.sm3X16Str(originalStr,"UTF-8").toLowerCase(); …… /** * sha256计算后进行16进制转换 * * @param data 待计算的数据 * @param encoding 编码 * @return 计算结果 */ public static String sm3X16Str(String data, String encoding) { byte[] bytes = sm3(data, encoding); StringBuilder sm3StrBuff = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { if (Integer.toHexString(0xFF & bytes[i]).length() == 1) { sm3StrBuff.append("0").append( Integer.toHexString(0xFF & bytes[i])); } else { sm3StrBuff.append(Integer.toHexString(0xFF & bytes[i])); } } return sm3StrBuff.toString(); } /** * sm3计算 * @param datas 待计算的数据 * @param encoding 字符集编码 * @return */ private static byte[] sm3(String datas, String encoding) { try { SM3Digest sm3 = new SM3Digest(); byte[] bytes = datas.getBytes(encoding); sm3.update(bytes, 0, bytes.length); byte[] result = new byte[sm3.getDigestSize()]; sm3.doFinal(result, 0); return result; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; }
public final static int RS_LEN = 32; public final static String ids = "1234567812345678"; /**大整数N*/ static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } else { Security.removeProvider("BC"); Security.addProvider(new BouncyCastleProvider()); } } /** <p> * 校验数字签名 * </p> * @param originalStr 待签字符串数据 * @param publicKey 公钥(BASE64编码) * @param sign 数字签名 * @return * @throws Exception */ public static boolean verify(String sign,String publicKey,String originalStr)throws Exception { byte[] signdeco = Base64.decodeBase64(sign); JSONObject jsonObject = JSONObject.parseObject(new String(signdeco)); byte[] r = Base64.decodeBase64(jsonObject.getString("r")); byte[] s = Base64.decodeBase64(jsonObject.getString("s")); signdeco = new byte[RS_LEN * 2]; byte[] pubdeco = Base64.decodeBase64(publicKey.getBytes()); JSONObject object = JSONObject.parseObject(new String(pubdeco)); String x = Hex.toHexString(Base64.decodeBase64(object.getString("x"))); String y = Hex.toHexString(Base64.decodeBase64(object.getString("y"))); PublicKey pubKey = hexToSM2PublicKey(x, y); System.arraycopy(r, 0, signdeco, 0, r.length); System.arraycopy(s, 0, signdeco, 32, s.length); boolean verifySm3WithSm2 = verifySm3WithSm2(originalStr.getBytes(), ids.getBytes(), signdeco, pubKey); if (!verifySm3WithSm2) { return false; } else { return true; } } /** * SM2公钥字符串转PublicKey对象的方法 * @param x * @param y * @return */ public static ECPublicKey hexToSM2PublicKey(String x, String y) { ECPublicKey pubkey = null; try { KeyFactory keyFactory = KeyFactory.getInstance("EC"); ECPoint ecPoint = new ECPoint(new BigInteger(x, 16), new BigInteger(y, 16)); ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); ECNamedCurveSpec spec = new ECNamedCurveSpec("sm2p256v1", parameterSpec.getCurve(), parameterSpec.getG(), parameterSpec.getN(), parameterSpec.getH(), parameterSpec.getSeed()); ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, spec); pubkey = new BCECPublicKey("BC", keySpec, BouncyCastleProvider.CONFIGURATION); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return pubkey; } /** 验签 * @param msg * @param userId * @param rs r||s,直接拼接byte数组的rs * @param publicKey * @return */ public static boolean verifySm3WithSm2(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) { if (rs == null || msg == null || userId == null) return false; if (rs.length != RS_LEN * 2) return false; return verifySm3WithSm2Asn1Rs(msg, userId, rsPlainByteArrayToAsn1(rs), publicKey); } /** * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式 * @param sign in plain byte array * @return rs result in asn1 format */ private static byte[] rsPlainByteArrayToAsn1(byte[] sign) { if (sign.length != RS_LEN * 2) throw new RuntimeException("err rs. "); BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN)); BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2)); ASN1EncodableVector v = new ASN1EncodableVector(); v.add(new ASN1Integer(r)); v.add(new ASN1Integer(s)); try { return new DERSequence(v).getEncoded("DER"); } catch (IOException e) { throw new RuntimeException(e); } } /** * @param msg * @param userId * @param rs in <b>asn1 format</b> * @param publicKey * @return */ public static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] userId, byte[] rs, PublicKey publicKey) { try { SM2ParameterSpec parameterSpec = new SM2ParameterSpec(userId); Signature verifier = Signature.getInstance("SM3withSM2", "BC"); verifier.setParameter(parameterSpec); verifier.initVerify(publicKey); verifier.update(msg, 0, msg.length); return verifier.verify(rs); } catch (Exception e) { // throw new RuntimeException(e); return false; } }
1.5 加解密算法
请参考银联开放平台官网
OpenAPI网关规则说明文档- 中国银联开放平台 (unionpay.com)