附录请求/通知报文中的数据元按照如下顺序排列,并按照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)