对接环境、验签、加解密

## 对接环境 开发环境:https://pay-dev.lx-rhino.com 生产环境:https://api.lx-rhino.com >d ◆ JAVA DEMO仓库地址: **https://code.aliyun.com/lanxi-server/open-api-tool-demo.git** ### 接口对接流程 ![api对接流程.png](https://cos.easydoc.net/74722269/files/kw0qol1h.png) ## 1. 请求参数说明 ### 1.1 参数加密 >d RSA加密说明 ◆ 加密长度为512 ◆ 需要分段加密防止数据太长导致加密失败 ◆ 获取到加密结果后通过base64转成字符串即为加密后的数据 ◆ 如参数为空字符串无需加密 #### 加密代码示例(java) ```java public static void main(String[] args) { // 明文 String paramsJson= "{\"key1\":\"value1\",\"key2\":\"value2\"}"; // publicKey:公钥,开户后将发送至邮箱 String encryptResult= encryptByPublicKey(paramsJson, publicKey); } /** * 使用公钥加密 * * @param content 待加密内容 * @param publicKeyBase64 公钥 base64 编码(发放平台给的publicKey) * @return 经过 base64 编码后的字符串 */ public static String encryptByPublicKey(String content, String publicKeyBase64) throws Exception { PublicKey publicKey = getPublicKey(publicKeyBase64); return encrypt(content, publicKey); } public static String encrypt(String ciphertext, Key key) { try { // 用公钥加密 byte[] srcBytes = ciphertext.getBytes(); // Cipher负责完成加密或解密工作,基于RSA Cipher cipher = Cipher.getInstance("RSA"); // 根据公钥,对Cipher对象进行初始化 cipher.init(Cipher.ENCRYPT_MODE, key); //分段加密 byte[] resultBytes = encryptCipherDoFinal(cipher, srcBytes); //base64编码 String base64Str = Base64Utils.encodeToString(resultBytes); return base64Str; } catch (Exception e) { e.printStackTrace(); return null; } } public static byte[] encryptCipherDoFinal(Cipher cipher, byte[] srcBytes) throws IllegalBlockSizeException, BadPaddingException, IOException { int inputLen = srcBytes.length; int offLen = 0;//偏移量 int i = 0; ByteArrayOutputStream bops = new ByteArrayOutputStream(); while (inputLen - offLen > 0) { byte[] cache; if (inputLen - offLen > 53) { cache = cipher.doFinal(srcBytes, offLen, 53); } else { cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen); } bops.write(cache); i++; offLen = 53 * i; } bops.close(); byte[] encryptedData = bops.toByteArray(); return encryptedData; } ``` ### 1.2 签名算法 #### 第一步: 将加密结果跟公共字段拼接成新的字符串 ```java String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey; ``` >d 备注:该处特别说明 &SecretKey 后面没有 ==等于==号 #### 第二步: 用MD5对新字符串加密即为签名 #### 代码示例(java) ```java public static void main(String[] args) { //约定拼接参数 //encryptResult:加密后的入参 //timestamp:时间戳,与http请求时入参保持一致 //appKey:平台分配的appKey //secretKey:平台分配的秘钥 String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey; //md5加密 String sigin = Md5Util.getMd5(localString); } private final static String[] CHARS = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; public static String getMd5(String src) { MessageDigest md5 = null; try { // 参数代表的是算法名称 md5 = MessageDigest.getInstance("md5"); byte[] result = md5.digest(src.getBytes()); StringBuilder sb = new StringBuilder(32); // 将结果转为16进制字符 0~9 A~F for (int i = 0; i < result.length; i++) { // 一个字节对应两个字符 byte x = result[i]; // 取得高位 int h = 0x0f & (x >>> 4); // 取得低位 int l = 0x0f & x; sb.append(CHARS[h]).append(CHARS[l]); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } ``` ## 2、响应报文说明 >d RSA解密说明 ◆ 需要分段解密密防止数据太长导致解密失败 ◆ 获取到加密结果后直接转成字符串即为解密结果 |参数名称|参数类型|参数含义|是否必填|参数备注| |-|-|-|-|-| |success|Boolean|请求成功与失败|必填|false表示业务异常| |status|Integer|状态码|必填|系统响应编码| |errorCode|String|错误编码|否|请求失败返回(预留字段)| |errorMessage|String|错误原因|否|请求失败返回| |data|String|业务数据(密文)|否|请求成果返回,响应对应的业务数据,此数据是密文,需要解密| 获取接口返回结果中的data字段,该值即为需要解密的数据 ``` { "success": true, "status": 0, "errorCode": "", "errorMessage": "", "data": "jaG/AODOH76rqIzaAPLLZA2Fp4k6ujXHh3jewVpCSNJFIL4JByl6byDu1oe4K6iiaym8yLNMhlisxt1IlDRu6A==" } ``` #### 解密代码示例(java) ```java public static void main(String[] args) { // data:密文 String data = "jaG/AODOH76rqIzaAPLLZA2Fp4k6ujXHh3jewVpCSNJFIL4JByl6byDu1oe4K6iiaym8yLNMhlisxt1IlDRu6A=="; // publicKey:公钥,开户后将发送至邮箱 String decryptResult= decryptByPublicKey(data, publicKey); } /** * 使用公钥解密 * * @param contentBase64 待解密内容,base64 编码 * @param publicKeyBase64 私钥 base64 编码 * @return */ public static String decryptByPublicKey(String contentBase64, String publicKeyBase64) throws Exception { PublicKey publicKey = getPublicKey(publicKeyBase64); return decrypt(contentBase64, publicKey); } public static String decrypt(String contentBase64, Key key) { try { // 用私钥解密 byte[] bytes = Base64Utils.decodeFromString(contentBase64); // Cipher负责完成加密或解密工作,基于RSA Cipher deCipher = Cipher.getInstance("RSA"); // 根据公钥,对Cipher对象进行初始化 deCipher.init(Cipher.DECRYPT_MODE, key); // 分段解密 byte[] decBytes = decryptCipherDoFinal(deCipher, bytes); // 结果转成字符串 String decrytStr = new String(decBytes); return decrytStr; } catch (Exception e) { e.printStackTrace(); return null; } } public static byte[] decryptCipherDoFinal(Cipher cipher, byte[] srcBytes) throws IllegalBlockSizeException, BadPaddingException, IOException { int inputLen = srcBytes.length; int offLen = 0; int i = 0; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); while (inputLen - offLen > 0) { byte[] cache; if (inputLen - offLen > 64) { cache = cipher.doFinal(srcBytes, offLen, 64); } else { cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen); } byteArrayOutputStream.write(cache); i++; offLen = 64 * i; } byteArrayOutputStream.close(); byte[] data = byteArrayOutputStream.toByteArray(); return data; } ``` ## 3、接口请求 >d http请求说明 ◆ 以下公共参数放在header中进行传递 #### 请求头部信息 |参数名称|参数含义|是否必填|参数备注| |-|-|-|-| |AppKey|由发放平台提供的|必填|商户签约后发放提供的唯一识别号| |Timestamp|时间戳|必填|请求时间戳精确到毫秒。需要将请求机器时间调整为北京时间,请求有效时间为两分钟| |Version|版本号|必填|如果无特别说明则填写1.0即可| |Sign|签名|必填|签名,具体来源请看==签名算法==| #### 请求代码示例(java) ```java public static void main(String[] args) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("key1", "value1"); jsonObject.put("key2", "value2"); //http请求 doPost("/open/api/xxx/xxx/xxx", jsonObject.toJSONString()); } //平台分配的appKey private static final String appKey = "xxx"; //凭条分配的秘钥 private static final String secretKey = "xxx"; //平台分配的公钥 private static final String publicKey = "xxx"; public static void doPost(String url, String paramsJson) throws Exception { System.out.println("请求参数:" + paramsJson); //加密 String encryptParam = RSAHelper.encryptByPublicKey(paramsJson, publicKey); System.out.println("-->加密:" + encryptParam); String timestamp = String.valueOf(System.currentTimeMillis()); //接口请求 BaseResponse baseResponse = HttpUtil.post(url, encryptParam, appKey, timestamp, getSign(encryptParam, timestamp)); if (baseResponse.getStatus() == 0) { if (baseResponse.getData() == null) { return; } String data = baseResponse.getData(); //解密 if (StringUtils.isNotBlank(data)) { String dec = RSAHelper.decryptByPublicKey(data, publicKey); System.out.println("-->解密:" + dec); } } } //获取签名 private static String getSign(String encryptParam, String timestamp) { return Md5Util.getMd5(encryptParam + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey); } /** * http请求 * @param url 接口地址 * @param paramsJson 请求参数json格式的字符串 * @param appKey 平台分配的appKey * @param timestamp 时间戳 * @param sign 签名 */ public static BaseResponse post(String url, String paramsJson, String appKey, String timestamp, String sign) throws Exception { RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), paramsJson); Request request = new Request.Builder() .addHeader("AppKey", appKey) .addHeader("Timestamp", timestamp) .addHeader("Version", "1.0") .addHeader("Sign", sign) .url(url) .post(body) .build(); Call call = getOkHttpClient().newCall(request); Response response = call.execute(); String res = response.body().string(); System.out.println("响应报文:" + res); return JSONObject.parseObject(res, BaseResponse.class); } ``` ## 4、回调说明 >d 1、该接口需要第三方自行编写提供接口到发放平台 接口编写示例如下。 2、回调必传callbackType用于区分回调业务。 3、贵司写完后提供到接口url给发放平台即可。回调成功需返回success,否则视为失败,每次失败会进行回调重试,重试9次后将不再回调 #### 请求方式:POST |参数名称|参数类型|参数含义|是否必填|参数备注| |-|-|-|-|-| |...|...|...|...|...| ```java @PostMapping("signCallBack") @ResponseBody public String signCallBack(HttpServletRequest request, @RequestBody String data){ String publicKey = "该公钥由发放平台提供发放到贵司邮箱"; //解密 String decode = RSAHelper.decryptByPublicKey(data, publicKey); /*贵公司处理业务逻辑*/ return "success"; } ``` #### postman中模拟接口是否正常接收数据如下操作 ![微信截图_20191111122018.png](https://cos.easydoc.net/44383407/files/k2tx5nl4.png)