全部
全部
收单机构欺诈交易查询 关注
风险控制
收单机构欺诈交易查询中国银联风险控制部向收单机构提供的实时查询接口,接入后,收单机构可在本机构系统内,实时查询发卡机构通过“银联风险管理系统”报送的“发生在本机构商户的欺诈交易”。
附录

1.1     待签名串拼接规则

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

1.2      请求报文RSA签名样例代码

此方法在请求类接口交易中,报文头signMethod取值为“RSA2”时使用,对“待签字符串”进行RSA签名,为了保证双方的身份可靠性和数据完整性。

主要步骤如下:

1.代签字符串用SHA-256算法生成摘要

2.使用接入方RSA秘钥对的私钥对摘要做签名,生成签名值

3.对签名值做Base64编码,放到http消息头的sign字段中

4.签名算法,填写在http消息头的signMethod字段中

1.2.1  报文签名算法

 
/**
 * 获取签名
 * @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);
}

1.2.2  SHA256签名算法

 
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();
    }

1.2.3  RSA签名算法

//需要导入包
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验签公钥进行设置;对应生成的私钥请保存好

 

 

1.3      请求报文SM2签名样例代码

此方法在请求类接口交易中,报文头signMethod取值为“SM2”时使用,对“待签字符串”进行SM2签名,为了保证双方的身份可靠性和数据完整性。

主要步骤如下:

1.代签字符串用SM3算法生成摘要

2.使用接入方SM2秘钥对的私钥对摘要做签名,生成签名值

3.对签名值做Base64编码,放到http消息头的sign字段中

4.签名算法,填写在http消息头的signMethod字段中

 

1.3.1  实体类结构

此实体类为了后续提供公钥和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;
    }
}

1.3.2  获得公私钥对

//需要导入包
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);
        }
    }

1.3.3  SM3摘要算法

/**
     * 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;
        }
    }

1.3.4  SM2签名算法

   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));
        }
    }



1.3.5  对SM2Signature 对象进行加密,获得sign值

 String signString = Base64.encodeBase64String(JSONObject.toJSONString(sm2Signature).getBytes());


1.3.6  获得转换后的公钥加密串,为了验签用

将生成的公钥通过开放平台-个人中心-相应的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.4      通知报文RSA验签样例代码

此方法在通知类接口交易中,用“待签字符串”和验签公钥,对通知报文的签名做验签,为了保证请求来源的身份可靠性和报文完整性。

主要步骤如下:

1.从http消息头中获取signMethod;

2.对待签名串使用SHA-256算法生成摘要;

3.从http消息头中获取sign,对sign做base64解码;

4.从开放平台API认证账号处获取接收方通知验签公钥;

5.对第3步解码后的sign值,用第4步获取的公钥进行验签。

1.4.1  RSA验签样例代码:SHA-256算法生成摘要

     //构筑需摘要字符串,构筑方法请见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;
        }
    }

1.4.2  RAS验签算法

//需要导入包
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;
    }


1.4.3  SM2验签样例代码:BC工具生成待签字符串摘要

//需要导入包
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;
    }


1.4.4  SM2验签算法

 
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)



联系我们

咨询服务