(1)接入方生成SM2公私钥对(公私钥对生成方法见下文),签名公钥录入至开放平台“个人中心-API认证账号-SM2验签公钥(SM2-VerifyPublicKey) ”(需要用户先登录开放平台),签名私钥SignPrivateKey用来计算签名。调用接口时,请提前将服务器IP地址添加到用户IP白名单中,否则将无法调用,返回应答码为9901(详见:全局返回码说明)。
(2)使用请求头部信息和、SM2签名私钥SignPrivateKey计算得出签名信息sign。
签名规则:
a. 将请求头部信息的version、appid、bizMethod、reqId和请求报文body以&作为连接符拼接成待签名串, 型如:
"version=[version]&appId=[appId]&bizMethod= [bizMethod]&reqId=[reqId]&body=[body]";
b. 对待签名串使用SM3做摘要(SM3摘要算法见下文);
c. 用SignPrivateKey对摘要做SM2签名(SM2签名算法见下文),得到签名信息sign。
SignPrivateKey: SM2签名私钥;
body: 请求JSON报文;
(3)使用API认证账号AppId、签名信息sign、请求头部信息header、请求报文body,发出API调用请求。
HTTP 请求方式:
POST
HTTP URL:
https://openapi.unionpay.com/upapi/cardbin/cardinfo
HTTP 报文头(header):
Content-Type: application/json
其余参数请参考下表:
中文名称 | 英文名称 | 域类型 | 域长度 | 默认值 | 请求要求 | 备注 |
版本号 | version | string | 8 | M-必填 | 格式:xx.yy.zz 取值举例:1.0.0 | |
发送方索引类型 | appType | string | 2 | M-必填 | 取值: 00:银联索引 01:机构索引 02:商户索引 | |
发送方索引标识码 | appId | string | 32 | M-必填 | 由银联分配,请求或应答的发送方填写,标识签名验签对应的发送方 | |
接口类型 | bizMethod | string | 64 | M-必填 | 取值:宜全英文,不应出现中文 取值举例:cardbin.cardinfo | |
签名 | sign | string | 344 | M-必填 | 根据报文签名方法生成对报文摘要的签名 | |
签名或摘要方式 | signMethod | string | 20 | M-必填 | 取值: 1、RSA2 2、SM2 | |
发送方流水号 | reqId | string | 64 | M-必填 | 请求方自行生成,保证每笔交易不重复,但非交易主键 |
HTTP 报文体(body):
JSON格式接口请求报文,如{"cardNo":""}
(4)API接口的请求参数、应答参数依据业务需求会配置校验规则或加解密规则,相关内容请参考《参数规则说明》
引用jar包:
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.60</version>
<type>jar</type>
</dependency>
生成公私钥对象:
/** 需要导入包 */
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**转化长度*/
private final static int RS_LEN = 32;
/**获取SM2椭圆曲线推荐参数*/
private static X9ECParameters x9ECParameters =GMNamedCurves.getByName("sm2p256v1");
/**构建EC算法参数*/
private static ECParameterSpec ecParameterSpec = new ECParameterSpec(
x9ECParameters.getCurve(),/**设置曲线方程*/
x9ECParameters.getG(), /**椭圆曲线G点*/
x9ECParameters.getN());/**大整数N*/
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);
}
}
公私钥对象转换并获得私钥和公钥字符串,转换方法如下:
/**私钥字符串,为了签名用*/
String priKey = new BASE64Encoder().encode(kp.getPrivate().getEncoded());
/** 公钥字符串pubKey,为了后续验签用 */
SM2PublicKey sm2PublicKey = new SM2PublicKey();
sm2PublicKey.setBits(256);
sm2PublicKey.setX(((BCECPublicKey)kp.getPublic()).getQ().getXCoord().getEncoded());
sm2PublicKey.setY(((BCECPublicKey)kp.getPublic()).getQ().getYCoord().getEncoded());
String pubKey = Base64.encodeBase64String(JSONObject.toJSONString(sm2PublicKey).getBytes());
/**
* 公钥数据结构
*/
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;
}
}
SM2非对称认证方式报文签名规则-Java语言
/**
* SM2非对称认证方式签名算法
* @param body 请求报文体
* @param SignPrivateKey SM2签名私钥
*/
public static String getSM2Sign(String body,String SignPrivateKey,String version,String appId,String bizMethod,String reqId){
//获得私钥对象
byte[] prvBytes22 = new BASE64Decoder().decodeBuffer(SignPrivateKey);
PKCS8EncodedKeySpec eks2 = new PKCS8EncodedKeySpec(prvBytes22);
KeyFactory kf22 = KeyFactory.getInstance("EC", "BC");
PrivateKey pvk = kf22.generatePrivate(eks2);
// 拼接待签名字符串,计算SM3摘要
String originalStr = "version="+version+"&appId="+appId+"&bizMethod="+bizMethod+"&reqId="+reqId+"&body="+body;
String signForVerify = sm3X16Str(originalStr, "UTF-8");
String realSign = null;
try {
// 对摘要做sm2签名
SM2Signature sm2Signature= signSm3WithSm2(signForVerify.getBytes(),userId, pvk);
realSign= Base64.encodeBase64String(JSONObject.toJSONString(sm2Signature).getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return realSign;
}
SM3摘要算法实现样例-Java语言
/**
* 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) {
return null;
}
}
SM2签名算法实现样例-Java语言
/**固定值 */
byte[] userId = "1234567812345678".getBytes();
/**
* SM2签名
* @param msg 待签字符串(SM3计算后)
* @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));
}
}
/**
* 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;
}
}