增加银行sdk代码, 更改pom, 添加依赖
This commit is contained in:
parent
6056e5282e
commit
e910e65cd8
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>org.mdd</groupId>
|
||||
<artifactId>likeadmin-java</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>dfp-open-sdk</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>dfp-open-sdk</name>
|
||||
<description>兴业数金开放银行平台 Java SDK(源码由银行 demo 迁入,版本参见 demo Readme)</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- Readme:国密需 bcprov + bcpkix,jdk15on 1.60+ -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<!-- AbstractTransService 使用 com.alibaba.fastjson -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.0</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package com.cib.fintech.dfp.open.sdk;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.config.Configure;
|
||||
import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 示例类
|
||||
* <p>
|
||||
* 注意:该类仅供参考学习,请不要将该类直接包含在开发者代码中
|
||||
* 重要:各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,务必保证传入参数的安全性,否则会导致安全问题
|
||||
*/
|
||||
public class Example {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Example example = new Example();
|
||||
|
||||
// 【1.设置SDK中应用密钥信息,需在项目启动时完成赋值】
|
||||
example.keyConfigureInit();
|
||||
|
||||
// 设置是否测试环境,true连接测试环境,false连接生产环境
|
||||
Configure.setDevEnv(true);
|
||||
|
||||
// 按需求调用对应方法,传入keyId区分应用配置
|
||||
String response = example.execSdk("KYONLYFORTEST012345678");
|
||||
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SDK中应用密钥信息
|
||||
* <p>
|
||||
* 注意:需在项目启动时完成赋值
|
||||
* 本代码值仅为示例,实际使用请替换为开发者申请的keyId及密钥
|
||||
*/
|
||||
public void keyConfigureInit() {
|
||||
|
||||
Map<String, KeyConfigure> keyConfigures = Configure.getKeyConfigures();
|
||||
|
||||
// 应用ID,为KY字母开头的字符串,区分大小写
|
||||
String keyId = "KYONLYFORTEST012345678";
|
||||
|
||||
// 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
|
||||
String priKey = "AK7JUorRStFAwM1+BXiHefwmCOZXjVU42b6Oa5JFf5Y7";
|
||||
|
||||
// 响应报文验签公钥
|
||||
String respPubKey = "BAQl3uq+FxAGi1/vAZpWyBhdTqBUcenUm8jdPl59XHOeyFkSU3obseTJOTms7lfl0rFALHVTrOmV39N97Z2ZOVI=";
|
||||
|
||||
// 字段加密密钥
|
||||
String reqParamEncryptKey = "9A9A4QyNz/pc8kZZXzhXOQ==";
|
||||
|
||||
// keySignTypeEnum 请求签名算法
|
||||
// respSignSwitch 响应报文验签开关,true将进行验签,false不进行验签
|
||||
// respSignAlgorithmEnum 响应报文验签算法
|
||||
KeyConfigure keyConfigure = new KeyConfigure();
|
||||
keyConfigure.setKeyId(keyId);
|
||||
keyConfigure.setPriKey(priKey);
|
||||
keyConfigure.setRespPubKey(respPubKey);
|
||||
keyConfigure.setReqParamEncryptKey(reqParamEncryptKey);
|
||||
keyConfigure.setKeySignType(KeySignTypeEnum.SM3WITHSM2);
|
||||
keyConfigure.setRespSignSwitch(true);
|
||||
keyConfigure.setRespSignAlgorithm(RespSignAlgorithmEnum.SM3WITHSM2);
|
||||
|
||||
// 灰度发布配置
|
||||
// GrayConfigure grayConfigure = new GrayConfigure();
|
||||
// grayConfigure.setKey("wkroute");
|
||||
// grayConfigure.setValue("graytest");
|
||||
// keyConfigure.setGrayConfigure(grayConfigure);
|
||||
|
||||
// CFCA证书配置(设置CFCA私钥证书全路径文件名、私钥保护密码以及签名算法)
|
||||
// keyConfigure
|
||||
// .setCfcaProperties("C:/Users/cibfintech/Desktop/test.pfx", "WjXcgOQr", KeySignTypeEnum
|
||||
// .SHA256WITHRSA);
|
||||
|
||||
// 用户token认证源为“企业客户授权认证”时配置
|
||||
// keyConfigure.setEnterpriseBearer("1117", "PD4gW2jjoeDzeEdwLbPMFGTN", "123456789");
|
||||
|
||||
keyConfigures.put(keyId, keyConfigure);
|
||||
|
||||
Configure.setKeyConfigures(keyConfigures);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求接口(非文件)
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @return 响应报文
|
||||
*/
|
||||
public String execSdk(String keyId) {
|
||||
|
||||
// 【2.准备请求参数】
|
||||
// 开发者根据API文档,准备不同的参数集合。headParams-头部信息请求参数、urlParams-URL请求参数、bodyParams-BODY请求参数
|
||||
Map<String, String> bodyParams = new HashMap<String, String>(8);
|
||||
bodyParams.put("stringParam", "Hello World");
|
||||
bodyParams.put("intParam", "111");
|
||||
bodyParams.put("byteParam", "1");
|
||||
bodyParams.put("shortParam", "1111");
|
||||
bodyParams.put("longParam", "111111111");
|
||||
bodyParams.put("floatParam", "111.111");
|
||||
bodyParams.put("doubleParam", "111.111111");
|
||||
bodyParams.put("boolParam", "false");
|
||||
bodyParams.put("charParam", "A");
|
||||
|
||||
bodyParams.put("mockBean[0].intParam", "111");
|
||||
bodyParams.put("mockBean[0].stringParam", "ascb");
|
||||
|
||||
bodyParams.put("stringList[0]", "stringList0");
|
||||
bodyParams.put("stringList[1]", "stringList1");
|
||||
|
||||
bodyParams.put("mockBeanMap[0].intParam", "111");
|
||||
bodyParams.put("mockBeanMap[0].doubleParam", "111.111");
|
||||
bodyParams.put("mockBeanMap[0].stringParam", "AAAAA");
|
||||
bodyParams.put("mockBeanMap[1].intParam", "222");
|
||||
bodyParams.put("mockBeanMap[1].doubleParam", "222.222");
|
||||
bodyParams.put("mockBeanMap[1].stringParam", "BBBBB");
|
||||
|
||||
bodyParams.put("mockBeanList[0].intParam", "111");
|
||||
bodyParams.put("mockBeanList[0].doubleParam", "111.111");
|
||||
bodyParams.put("mockBeanList[0].stringParam", "group1/M00/97/68/CmAFGVpd3CGAREPDAAAECO1Nr3I123.txt");
|
||||
bodyParams.put("mockBeanList[1].intParam", "222");
|
||||
bodyParams.put("mockBeanList[1].doubleParam", "222.222");
|
||||
bodyParams.put("mockBeanList[1].stringParam", "group2/M00/97/68/CmAFGVpd3CGAREPDAAAECO1Nr3I123.txt");
|
||||
|
||||
// 需要加密的字段值处理示例(请参阅对应接口文档,并按实际情况使用)
|
||||
// try {
|
||||
// bodyParams.put("key", SymmetricEncrypt.aesEncrypt("value","9A9A4QyNz/pc8kZZXzhXOQ=="));
|
||||
// } catch (Exception e) {
|
||||
// // 如果字段加密密钥或字段值设置错误,需要处理情况
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
// 【3.调用SDK方法】
|
||||
// 传入请求资源URI、请求方法,以及各位置的参数
|
||||
String response = OpenSdk.gatewayWithKeyId("/api/testApi", ReqMethodEnum.POST, null, null, bodyParams, keyId);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求接口(非文件),json
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @return 响应报文
|
||||
*/
|
||||
public String execJsonSdk(String keyId) {
|
||||
|
||||
// 【2.准备请求参数】
|
||||
// 开发者根据API文档,准备不同的参数集合。headParams-头部信息请求参数、urlParams-URL请求参数、bodyJson-BODY请求参数JSON字符串
|
||||
String bodyJson = "{\"key\":\"value\"}";
|
||||
|
||||
// 需要加密的字段值处理示例(请参阅对应接口文档,并按实际情况使用)
|
||||
// try {
|
||||
// JSONObject bodyJson = new JSONObject();
|
||||
// bodyJson.put("key", SymmetricEncrypt.aesEncrypt("value","9A9A4QyNz/pc8kZZXzhXOQ=="));
|
||||
// } catch (Exception e) {
|
||||
// // 如果字段加密密钥或字段值设置错误,需要处理情况
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
// 【3.调用SDK方法】
|
||||
// 传入请求资源URI、请求方法,以及各位置的参数
|
||||
String response = OpenSdk.gatewayJsonWithKeyId("/api/testApi", ReqMethodEnum.POST, null, null, bodyJson, keyId);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件下载接口
|
||||
* <p>
|
||||
* 该请求用于下载文件,filePath 文件存储路径
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @return 响应报文
|
||||
*/
|
||||
public String downloadFile(String keyId) {
|
||||
|
||||
// 【2.准备请求参数】
|
||||
// 文件ID
|
||||
String fileId = "group1/M00/A2/19/CmAFGVppoA2AII5mAAAECO1Nr3I.a914b6";
|
||||
|
||||
// 文件存储路径
|
||||
String filePath = "C:\\Users\\cibfintech\\Desktop\\test.txt";
|
||||
|
||||
// 【3.调用SDK方法】
|
||||
String response = OpenSdk.downloadFileWithKeyId(fileId, filePath, keyId);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传接口
|
||||
* <p>
|
||||
* 该请求用于上传文件,filePath 文件路径
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @return 响应报文
|
||||
*/
|
||||
public String uploadFile(String keyId) {
|
||||
|
||||
// 【2.准备请求参数】
|
||||
// 待上传文件路径
|
||||
String filePath = "C:\\Users\\cibfintech\\Desktop\\test.txt";
|
||||
|
||||
// 【3.调用SDK方法】
|
||||
String response = OpenSdk.uploadFileWithKeyId(filePath, keyId);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥查询接口
|
||||
* <p>
|
||||
* 该请求用于查询公钥(如遇验签失败问题,可尝试调用该接口用于排除密钥、算法不一致问题)
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @return 响应报文
|
||||
*/
|
||||
public String queryPubKey(String keyId) {
|
||||
|
||||
// 【调用SDK方法】
|
||||
String response = OpenSdk.gatewayWithKeyId("/api/open/queryPubKey", ReqMethodEnum.GET, null, null, null, keyId);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
package com.cib.fintech.dfp.open.sdk;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.config.Configure;
|
||||
import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
import com.cib.fintech.dfp.open.sdk.service.AbstractTransService;
|
||||
import com.cib.fintech.dfp.open.sdk.util.Const;
|
||||
import com.cib.fintech.dfp.open.sdk.util.FileUtil;
|
||||
import com.cib.fintech.dfp.open.sdk.util.SdkUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 开放银行平台统一接口类
|
||||
* <p>
|
||||
* 注意:需要先配置Configure类中的值,示例参见Example.java文件
|
||||
* 该SDK中代码仅供学习和参考,开发者可以自己根据接口文档进行编码实现,而不使用该SDK中的实现
|
||||
* 各字段长度、类型等请参考具体接口文档中的定义,SDK不检查这些长度,而直接发送给服务端
|
||||
* 重要:各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,否则可能会导致安全问题
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class OpenSdk {
|
||||
|
||||
/**
|
||||
* 文件ID
|
||||
*/
|
||||
private static final String FILE_ID = "fileId";
|
||||
|
||||
/**
|
||||
* 文件特征值算法
|
||||
*/
|
||||
private static final String ALGORITHM = "SHA-1";
|
||||
|
||||
/**
|
||||
* 文件哈希值key
|
||||
*/
|
||||
private static final String FILE_HASH_VALUE = "fileHashValue";
|
||||
|
||||
/**
|
||||
* SDK请求入口
|
||||
* <p>
|
||||
* [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param headParams 头部信息请求参数
|
||||
* @param urlParams URL请求参数
|
||||
* @param bodyParams BODY请求参数
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String gateway(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams,
|
||||
Map<String, String> urlParams, Map<String, String> bodyParams) {
|
||||
try {
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(reqUri, reqMethod, headParams, urlParams, bodyParams, null, Configure.getKeyConfigure(),
|
||||
null);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,form格式
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param headParams 头部信息请求参数
|
||||
* @param urlParams URL请求参数
|
||||
* @param bodyParams BODY请求参数
|
||||
* @param keyId 应用ID
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String gatewayWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams,
|
||||
Map<String, String> urlParams, Map<String, String> bodyParams, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(reqUri, reqMethod, headParams, urlParams, bodyParams, null, keyConfigure, null);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,json格式
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param headParams 头部信息请求参数
|
||||
* @param urlParams URL请求参数
|
||||
* @param bodyJson BODY请求参数JSON字符串
|
||||
* @param keyId 应用ID
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String gatewayJsonWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams,
|
||||
Map<String, String> urlParams, String bodyJson, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
if (!SdkUtil.isJSONString(bodyJson)) {
|
||||
return new SdkException(SdkExType.PARAM_ERR).toString();
|
||||
}
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(reqUri, reqMethod, headParams, urlParams, null, null, keyConfigure, bodyJson);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,form格式
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param headParams 头部信息请求参数
|
||||
* @param urlParams URL请求参数
|
||||
* @param bodyParams BODY请求参数
|
||||
* @param keyId 应用ID
|
||||
* @return 请求结果
|
||||
*/
|
||||
public static String gatewayNetSignWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams,
|
||||
Map<String, String> urlParams, Map<String, String> bodyParams, String bodyJson, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.execNetSign(reqUri, reqMethod, null, null, bodyParams, null, keyConfigure, bodyJson);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件下载
|
||||
* <p>
|
||||
* [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @return 若下载成功,返回下载的文件的字节数组(byte[]类型)
|
||||
*/
|
||||
public static Object downloadFile(String fileId) {
|
||||
try {
|
||||
Map<String, String> urlParams = new HashMap<String, String>(8);
|
||||
urlParams.put(FILE_ID, fileId);
|
||||
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null,
|
||||
Configure.getKeyConfigure(), null);
|
||||
/**
|
||||
* 将下载文件byte[]类型转为String并返回
|
||||
*/
|
||||
/*String fileString = new String((byte[]) result);
|
||||
return fileString;*/
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件下载
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @param keyId 应用ID
|
||||
* @return 若下载成功,返回下载的文件的字节数组(byte[]类型)
|
||||
*/
|
||||
public static Object downloadFileWithKeyId(String fileId, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
Map<String, String> urlParams = new HashMap<String, String>(8);
|
||||
urlParams.put(FILE_ID, fileId);
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null, keyConfigure, null);
|
||||
/**
|
||||
* 将下载文件byte[]类型转为String并返回
|
||||
*/
|
||||
/*String fileString = new String((byte[]) result);
|
||||
return fileString;*/
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件下载
|
||||
* <p>
|
||||
* [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @param filePath 文件存储路径
|
||||
* @return 若下载成功,返回下载成功json提示字符串(String类型)
|
||||
*/
|
||||
public static String downloadFile(String fileId, String filePath) {
|
||||
try {
|
||||
Map<String, String> urlParams = new HashMap<String, String>(8);
|
||||
urlParams.put(FILE_ID, fileId);
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null,
|
||||
Configure.getKeyConfigure(), null);
|
||||
if (result instanceof byte[]) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
fos = new FileOutputStream(file);
|
||||
fos.write((byte[]) result);
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return Const.FILE_SUCCESS_RESULT;
|
||||
} else {
|
||||
return (String) result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件下载
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
*
|
||||
* @param fileId 文件ID
|
||||
* @param filePath 文件存储路径
|
||||
* @param keyId 应用ID
|
||||
* @return 若下载成功,返回下载成功json提示字符串(String类型)
|
||||
*/
|
||||
public static String downloadFileWithKeyId(String fileId, String filePath, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
Map<String, String> urlParams = new HashMap<String, String>(8);
|
||||
urlParams.put(FILE_ID, fileId);
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null, keyConfigure, null);
|
||||
if (result instanceof byte[]) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
fos = new FileOutputStream(file);
|
||||
fos.write((byte[]) result);
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return Const.FILE_SUCCESS_RESULT;
|
||||
} else {
|
||||
return (String) result;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件上传
|
||||
* <p>
|
||||
* [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
* [重要]SDK不校验文件大小,开发者请根据实际情况自行限制
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @return 文件ID
|
||||
*/
|
||||
public static String uploadFile(String filePath) {
|
||||
try {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
}
|
||||
// 计算文件的哈希值
|
||||
String fileHashValue = FileUtil.fileHash(filePath, ALGORITHM);
|
||||
Map<String, String> bodyParams = new HashMap<String, String>(8);
|
||||
bodyParams.put(FILE_HASH_VALUE, fileHashValue);
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_UPLOAD_URI, ReqMethodEnum.POST, null, null, bodyParams, filePath,
|
||||
Configure.getKeyConfigure(), null);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SDK请求入口,文件上传
|
||||
* <p>
|
||||
* [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
|
||||
* [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
|
||||
* [重要]SDK不校验文件大小,开发者请根据实际情况自行限制
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @param keyId 应用ID
|
||||
* @return 文件ID
|
||||
*/
|
||||
public static String uploadFileWithKeyId(String filePath, String keyId) {
|
||||
try {
|
||||
KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
|
||||
if (null == keyConfigure) {
|
||||
return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
|
||||
}
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
}
|
||||
// 计算文件的哈希值
|
||||
String fileHashValue = FileUtil.fileHash(filePath, ALGORITHM);
|
||||
Map<String, String> bodyParams = new HashMap<String, String>(8);
|
||||
bodyParams.put(FILE_HASH_VALUE, fileHashValue);
|
||||
Object result = AbstractTransService.getInstance()
|
||||
.exec(Const.FILE_UPLOAD_URI, ReqMethodEnum.POST, null, null, bodyParams, filePath, keyConfigure,
|
||||
null);
|
||||
return (String) result;
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.UNKNOWN);
|
||||
return ex.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
package com.cib.fintech.dfp.open.sdk.common;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.config.Configure;
|
||||
import com.cib.fintech.dfp.open.sdk.config.GrayConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
import com.cib.fintech.dfp.open.sdk.service.AbstractTransService;
|
||||
import com.cib.fintech.dfp.open.sdk.util.Const;
|
||||
import com.cib.fintech.dfp.open.sdk.util.SdkUtil;
|
||||
import com.cib.fintech.dfp.open.sdk.util.VerifyRespSignature;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 使用URLConnection的通讯器
|
||||
*
|
||||
* @author zengminghua, tangchongcan
|
||||
*/
|
||||
public class HttpsPostRequest extends AbstractTransService {
|
||||
|
||||
private static final int BUFFER_SIZE = 2048;
|
||||
|
||||
private static final String HEADER_TIMESTAMP = "Timestamp";
|
||||
|
||||
private static final String HEADER_SIGNATURE = "Signature";
|
||||
|
||||
private static final String HEADER_NONCE = "Nonce";
|
||||
|
||||
private static final String HTTPS = "https";
|
||||
|
||||
/**
|
||||
* request头和上传文件内容的分隔符
|
||||
*/
|
||||
private static final String BOUNDARY = "---------------------------543279124964751";
|
||||
|
||||
private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
|
||||
|
||||
private static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
|
||||
@Override
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
|
||||
}
|
||||
}};
|
||||
|
||||
private static HostnameVerifier hv = new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private static SSLContext sc;
|
||||
private static SSLSocketFactory sf;
|
||||
|
||||
static {
|
||||
try {
|
||||
sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
sf = sc.getSocketFactory();
|
||||
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, SSLSocketFactory> SFS = new HashMap<>();
|
||||
private static Object LOCK = new Object();
|
||||
private static boolean SSL_INIT_STATUS = false;
|
||||
|
||||
private void initSSLContents() {
|
||||
if (!SSL_INIT_STATUS) {
|
||||
synchronized (LOCK) {
|
||||
if (!SSL_INIT_STATUS) {
|
||||
Map<String, KeyConfigure> keyConfigures = Configure.getKeyConfigures();
|
||||
for (Map.Entry<String, KeyConfigure> entry : keyConfigures.entrySet()) {
|
||||
String keyId = entry.getKey();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
||||
fis = new FileInputStream(new File(Configure.getCertPath(), keyId + ".p12"));
|
||||
char[] password = entry.getValue().getCertProtectionPwd() == null ?
|
||||
null :
|
||||
entry.getValue().getCertProtectionPwd().toCharArray();
|
||||
keyStore.load(fis, password);
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
kmf.init(keyStore, password);
|
||||
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
|
||||
SFS.put(keyId, sslContext.getSocketFactory());
|
||||
} catch (Exception e) {
|
||||
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SSL_INIT_STATUS = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object send(String url, ReqMethodEnum method, String authInfo, Map<String, String> headParams,
|
||||
String bodyParamString, String filePath, Map<String, String> bodyParamMap, KeyConfigure keyConfigure,
|
||||
String bodyJson) throws Exception {
|
||||
|
||||
// 通讯公共部分
|
||||
URL connUrl = null;
|
||||
HttpURLConnection conn = null;
|
||||
Object retn = null;
|
||||
|
||||
// 打开URL连接
|
||||
connUrl = new URL(url);
|
||||
conn = (HttpURLConnection) connUrl.openConnection();
|
||||
|
||||
// 测试环境忽略SSL证书验证
|
||||
if (Configure.isDevEnv() && HTTPS.equals(connUrl.getProtocol())) {
|
||||
ignoreSSLVerify((HttpsURLConnection) conn);
|
||||
}
|
||||
if (Configure.getCertPath() != null) {
|
||||
initSSLContents();
|
||||
SSLSocketFactory sslSocketFactory = SFS.get(keyConfigure.getKeyId());
|
||||
if (sslSocketFactory != null) {
|
||||
((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置HEAD信息
|
||||
if (keyConfigure.getEnterpriseBearer() != null) {
|
||||
authInfo = authInfo + "," + keyConfigure.getEnterpriseBearer();
|
||||
}
|
||||
conn.setRequestProperty(Const.POST_HEAD_KEY, authInfo);
|
||||
if (keyConfigure.getXCfcaBasic() != null) {
|
||||
conn.setRequestProperty("X-Cfca-Basic", keyConfigure.getXCfcaBasic());
|
||||
}
|
||||
if (headParams != null) {
|
||||
for (String key : headParams.keySet()) {
|
||||
conn.setRequestProperty(key, headParams.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
// 灰度发布配置添加到请求头中
|
||||
GrayConfigure grayConfigure = keyConfigure.getGrayConfigure();
|
||||
if (null != grayConfigure && grayConfigure.notNull()) {
|
||||
conn.setRequestProperty(grayConfigure.getKey(), grayConfigure.getValue());
|
||||
}
|
||||
|
||||
// 响应验签相关数据
|
||||
String timestamp = "";
|
||||
String responseSignValue = "";
|
||||
String responseNonce = "";
|
||||
|
||||
// 非文件上传
|
||||
if (null == filePath) {
|
||||
|
||||
OutputStream outStream = null;
|
||||
InputStream inStream = null;
|
||||
|
||||
if (SdkUtil.isJSONString(bodyJson)) {
|
||||
bodyParamString = bodyJson;
|
||||
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
}
|
||||
|
||||
try {
|
||||
switch (method) {
|
||||
case POST:
|
||||
conn.setRequestMethod(ReqMethodEnum.POST.toString());
|
||||
conn.setDoOutput(true);
|
||||
outStream = conn.getOutputStream();
|
||||
outStream.write(bodyParamString.getBytes(Const.CHARSET));
|
||||
outStream.flush();
|
||||
break;
|
||||
case PUT:
|
||||
conn.setRequestMethod(ReqMethodEnum.PUT.toString());
|
||||
conn.setDoOutput(true);
|
||||
outStream = conn.getOutputStream();
|
||||
outStream.write(bodyParamString.getBytes(Const.CHARSET));
|
||||
outStream.flush();
|
||||
break;
|
||||
case DELETE:
|
||||
conn.setRequestMethod(ReqMethodEnum.DELETE.toString());
|
||||
conn.setDoOutput(true);
|
||||
outStream = conn.getOutputStream();
|
||||
outStream.write(bodyParamString.getBytes(Const.CHARSET));
|
||||
outStream.flush();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 获取返回结果
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= HttpURLConnection.HTTP_OK && code < HttpURLConnection.HTTP_MULT_CHOICE && code != 0) {
|
||||
inStream = conn.getInputStream();
|
||||
} else {
|
||||
inStream = conn.getErrorStream();
|
||||
}
|
||||
|
||||
byte[] bin = readInputStream(inStream);
|
||||
if (APPLICATION_OCTET_STREAM.equals(conn.getContentType())) {
|
||||
retn = bin;
|
||||
} else {
|
||||
retn = new String(bin, Const.CHARSET);
|
||||
}
|
||||
|
||||
timestamp = conn.getHeaderField(HEADER_TIMESTAMP);
|
||||
responseSignValue = conn.getHeaderField(HEADER_SIGNATURE);
|
||||
responseNonce = conn.getHeaderField(HEADER_NONCE);
|
||||
} finally {
|
||||
if (inStream != null) {
|
||||
inStream.close();
|
||||
}
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 文件上传
|
||||
try {
|
||||
conn.setRequestMethod(ReqMethodEnum.POST.toString());
|
||||
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setDoOutput(true);
|
||||
conn.setDoInput(true);
|
||||
conn.setUseCaches(false);
|
||||
|
||||
conn.setRequestProperty("Connection", "Keep-Alive");
|
||||
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
|
||||
|
||||
OutputStream out = new DataOutputStream(conn.getOutputStream());
|
||||
|
||||
// text
|
||||
if (null != bodyParamMap) {
|
||||
StringBuffer strBuf = new StringBuffer();
|
||||
Iterator<Map.Entry<String, String>> iter = bodyParamMap.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, String> entry = iter.next();
|
||||
String inputName = entry.getKey();
|
||||
String inputValue = entry.getValue();
|
||||
if (inputValue == null) {
|
||||
continue;
|
||||
}
|
||||
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
|
||||
strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
|
||||
strBuf.append(inputValue);
|
||||
}
|
||||
out.write(strBuf.toString().getBytes());
|
||||
}
|
||||
|
||||
// file
|
||||
File file = new File(filePath);
|
||||
String filename = file.getName();
|
||||
String inputName = "file";
|
||||
|
||||
StringBuffer strBuf = new StringBuffer();
|
||||
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
|
||||
strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename
|
||||
+ "\"\r\n\r\n");
|
||||
|
||||
out.write(strBuf.toString().getBytes());
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
DataInputStream in = new DataInputStream(fileInputStream);
|
||||
int bytes = 0;
|
||||
byte[] bufferOut = new byte[1024];
|
||||
while ((bytes = in.read(bufferOut)) != -1) {
|
||||
out.write(bufferOut, 0, bytes);
|
||||
}
|
||||
in.close();
|
||||
fileInputStream.close();
|
||||
|
||||
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
|
||||
out.write(endData);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
// 获取返回结果
|
||||
InputStream inStream = null;
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= HttpURLConnection.HTTP_OK && code < HttpURLConnection.HTTP_MULT_CHOICE && code != 0) {
|
||||
inStream = conn.getInputStream();
|
||||
} else {
|
||||
inStream = conn.getErrorStream();
|
||||
}
|
||||
|
||||
// 读取返回数据
|
||||
strBuf = new StringBuffer();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(inStream);
|
||||
BufferedReader reader = new BufferedReader(inputStreamReader);
|
||||
String line = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
strBuf.append(line);
|
||||
}
|
||||
retn = strBuf.toString();
|
||||
reader.close();
|
||||
reader = null;
|
||||
inputStreamReader.close();
|
||||
inputStreamReader = null;
|
||||
|
||||
timestamp = conn.getHeaderField(HEADER_TIMESTAMP);
|
||||
responseSignValue = conn.getHeaderField(HEADER_SIGNATURE);
|
||||
responseNonce = conn.getHeaderField(HEADER_NONCE);
|
||||
} catch (FileNotFoundException e) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
} catch (IOException e) {
|
||||
SdkException ex = new SdkException(SdkExType.CONN_ERR);
|
||||
return ex.toString();
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应报文验签开关非关闭,且响应报文不包含OPEN异常时,执行验签
|
||||
if (keyConfigure.isRespSignSwitch() && !retn.toString().contains(Const.RESP_EXCEPTION_CODE)) {
|
||||
VerifyRespSignature.verify(timestamp, responseNonce, responseSignValue, retn, keyConfigure);
|
||||
}
|
||||
|
||||
return retn;
|
||||
}
|
||||
|
||||
private static byte[] readInputStream(InputStream inStream) throws IOException {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
byte[] data = null;
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
int len = 0;
|
||||
try {
|
||||
while ((len = inStream.read(buffer)) != -1) {
|
||||
outStream.write(buffer, 0, len);
|
||||
}
|
||||
data = outStream.toByteArray();
|
||||
} finally {
|
||||
if (outStream != null) {
|
||||
outStream.close();
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void ignoreSSLVerify(HttpsURLConnection conn) {
|
||||
conn.setSSLSocketFactory(sf);
|
||||
conn.setHostnameVerifier(hv);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
package com.cib.fintech.dfp.open.sdk.config;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 开放银行平台配置类
|
||||
* <p>
|
||||
* 这里存放所有配置,开发者可以扩展该类
|
||||
* 注意:请根据采用的配置方式调用OpenSdk类中对应的方法
|
||||
* 1.采用单应用配置方式时,请务必配置该类中的变量,如keyId等
|
||||
* 2.采用多应用配置方式时,请务必配置该类中的keyConfigures
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class Configure {
|
||||
|
||||
/********************************************** ↓ 开发者不可修改 ↓ *************************************/
|
||||
|
||||
/**
|
||||
* 开放银行平台生产环境地址,无需修改
|
||||
*/
|
||||
public static final String PROD_URL = "https://open.cibfintech.com";
|
||||
|
||||
/*********************************************** ↓ 开发者可修改 ↓ **************************************/
|
||||
|
||||
/**
|
||||
* 是否测试环境开关,true连接测试环境,false连接生产环境
|
||||
*/
|
||||
private static boolean devEnv = true;
|
||||
|
||||
/**
|
||||
* 开放银行平台测试环境地址,可根据需要修改
|
||||
*/
|
||||
private static String devUrl = "https://open.test.cibfintech.com";
|
||||
|
||||
/**
|
||||
* 应用ID,为KY字母开头的字符串,区分大小写
|
||||
*/
|
||||
private static String keyId = "KYONLYFORTEST012345678";
|
||||
|
||||
/**
|
||||
* 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
|
||||
*/
|
||||
private static String priKey = "AK7JUorRStFAwM1+BXiHefwmCOZXjVU42b6Oa5JFf5Y7";
|
||||
|
||||
/**
|
||||
* 响应报文验签公钥
|
||||
*/
|
||||
private static String respPubKey = "BAQl3uq+FxAGi1/vAZpWyBhdTqBUcenUm8jdPl59XHOeyFkSU3obseTJOTms7lfl0rFALHVTrOmV39N97Z2ZOVI=";
|
||||
|
||||
/**
|
||||
* 字段加密密钥
|
||||
*/
|
||||
private static String reqParamEncryptKey = "9A9A4QyNz/pc8kZZXzhXOQ==";
|
||||
|
||||
/**
|
||||
* 请求签名算法
|
||||
*/
|
||||
private static KeySignTypeEnum keySignType = KeySignTypeEnum.SM3WITHSM2;
|
||||
|
||||
/**
|
||||
* 响应报文验签开关,true将进行验签,false不进行验签
|
||||
*/
|
||||
private static boolean respSignSwitch = false;
|
||||
|
||||
/**
|
||||
* 响应报文验签算法
|
||||
*/
|
||||
private static RespSignAlgorithmEnum respSignAlgorithm = RespSignAlgorithmEnum.SM3WITHSM2;
|
||||
|
||||
/**
|
||||
* HTTP通讯类,如需使用自己的类库,请修改该值
|
||||
*/
|
||||
private static String httpsRequestClassName = "com.cib.fintech.dfp.open.sdk.common.HttpsPostRequest";
|
||||
|
||||
/**
|
||||
* 多应用配置方式时,创建KeyConfigure对象存入map中。示例:map{key=keyId,value=keyConfigure}
|
||||
*/
|
||||
private static Map<String, KeyConfigure> keyConfigures = new HashMap<>(8);
|
||||
|
||||
/**
|
||||
* 双向认证证书文件存放路径,不为空将开启双向认证
|
||||
* 该路径下证书文件名格式要求:{keyId}.p12,如KYONLYFORTEST012345678.p12
|
||||
*/
|
||||
private static String certPath = null;
|
||||
|
||||
/**
|
||||
* 双向认证证书私钥的保护密码,用于提取证书私钥。双向认证开启时必须设置该项
|
||||
*/
|
||||
private static String certProtectionPwd = null;
|
||||
|
||||
/**
|
||||
* 灰度发布标识参数
|
||||
*/
|
||||
private static GrayConfigure grayConfigure = null;
|
||||
|
||||
/*****************************************************************************************/
|
||||
|
||||
private static final Object GET_KEYCONFIGURE_LOCK = new Object();
|
||||
|
||||
public static String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public static void setKeyId(String keyId) {
|
||||
Configure.keyId = keyId;
|
||||
}
|
||||
|
||||
public static String getPriKey() {
|
||||
if (priKey.startsWith("-----BEGIN PRIVATE KEY-----")) {
|
||||
priKey = priKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
|
||||
}
|
||||
return priKey;
|
||||
}
|
||||
|
||||
public static void setPriKey(String priKey) {
|
||||
Configure.priKey = priKey;
|
||||
}
|
||||
|
||||
public static KeySignTypeEnum getKeySignType() {
|
||||
return keySignType;
|
||||
}
|
||||
|
||||
public static void setKeySignType(KeySignTypeEnum keySignType) {
|
||||
Configure.keySignType = keySignType;
|
||||
}
|
||||
|
||||
public static boolean isRespSignSwitch() {
|
||||
return respSignSwitch;
|
||||
}
|
||||
|
||||
public static void setRespSignSwitch(boolean respSignSwitch) {
|
||||
Configure.respSignSwitch = respSignSwitch;
|
||||
}
|
||||
|
||||
public static RespSignAlgorithmEnum getRespSignAlgorithm() {
|
||||
return respSignAlgorithm;
|
||||
}
|
||||
|
||||
public static void setRespSignAlgorithm(RespSignAlgorithmEnum respSignAlgorithm) {
|
||||
Configure.respSignAlgorithm = respSignAlgorithm;
|
||||
}
|
||||
|
||||
public static String getRespPubKey() {
|
||||
return respPubKey;
|
||||
}
|
||||
|
||||
public static void setRespPubKey(String respPubKey) {
|
||||
Configure.respPubKey = respPubKey;
|
||||
}
|
||||
|
||||
public static String getReqParamEncryptKey() {
|
||||
return reqParamEncryptKey;
|
||||
}
|
||||
|
||||
public static void setReqParamEncryptKey(String reqParamEncryptKey) {
|
||||
Configure.reqParamEncryptKey = reqParamEncryptKey;
|
||||
}
|
||||
|
||||
public static String getHttpsRequestClassName() {
|
||||
return httpsRequestClassName;
|
||||
}
|
||||
|
||||
public static void setHttpsRequestClassName(String httpsRequestClassName) {
|
||||
Configure.httpsRequestClassName = httpsRequestClassName;
|
||||
}
|
||||
|
||||
public static boolean isDevEnv() {
|
||||
return devEnv;
|
||||
}
|
||||
|
||||
public static void setDevEnv(boolean devEnv) {
|
||||
Configure.devEnv = devEnv;
|
||||
}
|
||||
|
||||
public static String getDevUrl() {
|
||||
return devUrl;
|
||||
}
|
||||
|
||||
public static void setDevUrl(String devUrl) {
|
||||
Configure.devUrl = devUrl;
|
||||
}
|
||||
|
||||
public static Map<String, KeyConfigure> getKeyConfigures() {
|
||||
return keyConfigures;
|
||||
}
|
||||
|
||||
public static void setKeyConfigures(Map<String, KeyConfigure> keyConfigures) {
|
||||
Configure.keyConfigures = keyConfigures;
|
||||
}
|
||||
|
||||
public static String getCertPath() {
|
||||
return certPath;
|
||||
}
|
||||
|
||||
public static void setCertPath(String certPath) {
|
||||
Configure.certPath = certPath;
|
||||
}
|
||||
|
||||
public static String getCertProtectionPwd() {
|
||||
return certProtectionPwd;
|
||||
}
|
||||
|
||||
public static void setCertProtectionPwd(String certProtectionPwd) {
|
||||
Configure.certProtectionPwd = certProtectionPwd;
|
||||
}
|
||||
|
||||
public static GrayConfigure getGrayConfigure() {
|
||||
return grayConfigure;
|
||||
}
|
||||
|
||||
public static void setGrayConfigure(GrayConfigure grayConfigure) {
|
||||
Configure.grayConfigure = grayConfigure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 采用单应用配置方式时,获取配置对象方法
|
||||
*
|
||||
* @return keyConfigure
|
||||
*/
|
||||
public static KeyConfigure getKeyConfigure() {
|
||||
KeyConfigure keyConfigure = keyConfigures.get(keyId);
|
||||
if (null != keyConfigure) {
|
||||
return keyConfigure;
|
||||
}
|
||||
synchronized (GET_KEYCONFIGURE_LOCK) {
|
||||
keyConfigure = keyConfigures.get(keyId);
|
||||
if (null != keyConfigure) {
|
||||
return keyConfigure;
|
||||
}
|
||||
keyConfigure = new KeyConfigure();
|
||||
keyConfigure.setKeyId(keyId);
|
||||
keyConfigure.setPriKey(priKey);
|
||||
keyConfigure.setKeySignType(keySignType);
|
||||
keyConfigure.setRespSignSwitch(respSignSwitch);
|
||||
keyConfigure.setRespSignAlgorithm(respSignAlgorithm);
|
||||
keyConfigure.setRespPubKey(respPubKey);
|
||||
keyConfigure.setReqParamEncryptKey(reqParamEncryptKey);
|
||||
keyConfigure.setCertProtectionPwd(certProtectionPwd);
|
||||
keyConfigure.setGrayConfigure(grayConfigure);
|
||||
keyConfigures.put(keyId, keyConfigure);
|
||||
}
|
||||
return keyConfigure;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.cib.fintech.dfp.open.sdk.config;
|
||||
|
||||
/**
|
||||
* 灰度发布配置对象
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class GrayConfigure {
|
||||
|
||||
/**
|
||||
* 请求头中的灰度参数键
|
||||
*/
|
||||
private String key;
|
||||
|
||||
/**
|
||||
* 请求头中的灰度参数值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
public GrayConfigure() {
|
||||
}
|
||||
|
||||
public GrayConfigure(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public boolean notNull() {
|
||||
if (null != this.key && null != this.value) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
package com.cib.fintech.dfp.open.sdk.config;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.util.Base64;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* 应用配置对象
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class KeyConfigure {
|
||||
|
||||
/**
|
||||
* 应用ID,为KY字母开头的字符串,区分大小写
|
||||
*/
|
||||
private String keyId;
|
||||
|
||||
/**
|
||||
* 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
|
||||
*/
|
||||
private String priKey;
|
||||
|
||||
/**
|
||||
* 响应报文验签公钥
|
||||
*/
|
||||
private String respPubKey;
|
||||
|
||||
/**
|
||||
* 字段加密密钥
|
||||
*/
|
||||
private String reqParamEncryptKey;
|
||||
|
||||
/**
|
||||
* 请求签名算法
|
||||
*/
|
||||
private KeySignTypeEnum keySignType = KeySignTypeEnum.SM3WITHSM2;
|
||||
|
||||
/**
|
||||
* 响应报文验签开关,true将进行验签,false不进行验签
|
||||
*/
|
||||
private boolean respSignSwitch = false;
|
||||
|
||||
/**
|
||||
* 响应报文验签算法
|
||||
*/
|
||||
private RespSignAlgorithmEnum respSignAlgorithm = RespSignAlgorithmEnum.SM3WITHSM2;
|
||||
|
||||
/**
|
||||
* 双向认证证书私钥的保护密码,用于提取证书私钥。双向认证开启时必须设置该项
|
||||
*/
|
||||
private String certProtectionPwd = null;
|
||||
|
||||
/**
|
||||
* 灰度发布标识参数
|
||||
*/
|
||||
private GrayConfigure grayConfigure = null;
|
||||
|
||||
/**
|
||||
* CFCA证书基本信息头
|
||||
*/
|
||||
private String xCfcaBasic = null;
|
||||
|
||||
/**
|
||||
* 企业客户授权信息
|
||||
*/
|
||||
private String enterpriseBearer = null;
|
||||
|
||||
public String getKeyId() {
|
||||
return keyId;
|
||||
}
|
||||
|
||||
public void setKeyId(String keyId) {
|
||||
this.keyId = keyId;
|
||||
}
|
||||
|
||||
public String getPriKey() {
|
||||
return priKey;
|
||||
}
|
||||
|
||||
public void setPriKey(String priKey) {
|
||||
this.priKey = priKey;
|
||||
}
|
||||
|
||||
public KeySignTypeEnum getKeySignType() {
|
||||
return keySignType;
|
||||
}
|
||||
|
||||
public void setKeySignType(KeySignTypeEnum keySignType) {
|
||||
this.keySignType = keySignType;
|
||||
}
|
||||
|
||||
public boolean isRespSignSwitch() {
|
||||
return respSignSwitch;
|
||||
}
|
||||
|
||||
public void setRespSignSwitch(boolean respSignSwitch) {
|
||||
this.respSignSwitch = respSignSwitch;
|
||||
}
|
||||
|
||||
public RespSignAlgorithmEnum getRespSignAlgorithm() {
|
||||
return respSignAlgorithm;
|
||||
}
|
||||
|
||||
public void setRespSignAlgorithm(RespSignAlgorithmEnum respSignAlgorithm) {
|
||||
this.respSignAlgorithm = respSignAlgorithm;
|
||||
}
|
||||
|
||||
public String getRespPubKey() {
|
||||
return respPubKey;
|
||||
}
|
||||
|
||||
public void setRespPubKey(String respPubKey) {
|
||||
this.respPubKey = respPubKey;
|
||||
}
|
||||
|
||||
public String getReqParamEncryptKey() {
|
||||
return reqParamEncryptKey;
|
||||
}
|
||||
|
||||
public void setReqParamEncryptKey(String reqParamEncryptKey) {
|
||||
this.reqParamEncryptKey = reqParamEncryptKey;
|
||||
}
|
||||
|
||||
public String getCertProtectionPwd() {
|
||||
return certProtectionPwd;
|
||||
}
|
||||
|
||||
public void setCertProtectionPwd(String certProtectionPwd) {
|
||||
this.certProtectionPwd = certProtectionPwd;
|
||||
}
|
||||
|
||||
public KeyConfigure() {
|
||||
}
|
||||
|
||||
public KeyConfigure(String keyId, String priKey, KeySignTypeEnum keySignType, boolean respSignSwitch,
|
||||
RespSignAlgorithmEnum respSignAlgorithm, String respPubKey, String reqParamEncryptKey,
|
||||
String certProtectionPwd, GrayConfigure grayConfigure) {
|
||||
this.keyId = keyId;
|
||||
this.priKey = priKey;
|
||||
this.keySignType = keySignType;
|
||||
this.respSignSwitch = respSignSwitch;
|
||||
this.respSignAlgorithm = respSignAlgorithm;
|
||||
this.respPubKey = respPubKey;
|
||||
this.reqParamEncryptKey = reqParamEncryptKey;
|
||||
this.certProtectionPwd = certProtectionPwd;
|
||||
this.grayConfigure = grayConfigure;
|
||||
}
|
||||
|
||||
public GrayConfigure getGrayConfigure() {
|
||||
return grayConfigure;
|
||||
}
|
||||
|
||||
public void setGrayConfigure(GrayConfigure grayConfigure) {
|
||||
this.grayConfigure = grayConfigure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cfcaCertFilename CFCA证书文件名(全路径)
|
||||
* @param cfcaProtectionPwd CFCA证书私钥的保护密码,用于提取CFCA证书私钥
|
||||
* @param keySignType 签名算法
|
||||
* @return
|
||||
*/
|
||||
public void setCfcaProperties(String cfcaCertFilename, String cfcaProtectionPwd, KeySignTypeEnum keySignType) {
|
||||
KeyStore keyStore;
|
||||
FileInputStream fis = null;
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
try {
|
||||
keyStore = KeyStore.getInstance("PKCS12");
|
||||
fis = new FileInputStream(cfcaCertFilename);
|
||||
char[] password = cfcaProtectionPwd == null ? null : cfcaProtectionPwd.toCharArray();
|
||||
keyStore.load(fis, password);
|
||||
Enumeration<String> aliases = keyStore.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
String alias = aliases.nextElement();
|
||||
X509Certificate x509Certificate = (X509Certificate) keyStore.getCertificate(alias);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(x509Certificate.getSerialNumber()).append("|");
|
||||
sb.append(x509Certificate.getSigAlgName()).append("|");
|
||||
sb.append(x509Certificate.getIssuerDN()).append("|");
|
||||
sb.append(x509Certificate.getSubjectDN()).append("|");
|
||||
sb.append(sdf.format(x509Certificate.getNotBefore())).append("-")
|
||||
.append(sdf.format(x509Certificate.getNotAfter()));
|
||||
this.xCfcaBasic = Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
|
||||
this.priKey = Base64.encode(privateKey.getEncoded());
|
||||
this.keySignType = keySignType;
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getXCfcaBasic() {
|
||||
return xCfcaBasic;
|
||||
}
|
||||
|
||||
public String getEnterpriseBearer() {
|
||||
return enterpriseBearer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param enterpriseId 企业ID
|
||||
* @param productId 产品ID
|
||||
* @param acctNo 企业账户号
|
||||
*/
|
||||
public void setEnterpriseBearer(String enterpriseId, String productId, String acctNo) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(enterpriseId).append("&");
|
||||
sb.append(productId).append("&");
|
||||
sb.append(acctNo);
|
||||
this.enterpriseBearer = "Bearer " + Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.cib.fintech.dfp.open.sdk.enums;
|
||||
|
||||
/**
|
||||
* 应用请求报文签名算法枚举
|
||||
*
|
||||
* @author tangchongcan, zengminghua
|
||||
*/
|
||||
public enum KeySignTypeEnum {
|
||||
|
||||
/**
|
||||
* SHA256WITHRSA("2", "SHA256withRSA")
|
||||
*/
|
||||
SHA256WITHRSA("2", "SHA256WithRSA"),
|
||||
|
||||
/**
|
||||
* SM3WITHSM2("3", "SM3WithSM2")
|
||||
*/
|
||||
SM3WITHSM2("3", "SM3WithSM2");
|
||||
|
||||
/**
|
||||
* 根据编码找枚举
|
||||
*
|
||||
* @param code 编码
|
||||
* @return KeyStatusEnum
|
||||
*/
|
||||
public static KeySignTypeEnum getByCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
if (values() == null) {
|
||||
return null;
|
||||
}
|
||||
for (KeySignTypeEnum t : values()) {
|
||||
if (t.getCode().equals(code)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
private final String label;
|
||||
|
||||
KeySignTypeEnum(String code, String label) {
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public KeySignTypeEnum returnEnum(String persistedValue) {
|
||||
return getByCode(persistedValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.cib.fintech.dfp.open.sdk.enums;
|
||||
|
||||
/**
|
||||
* HTTP请求方法枚举
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public enum ReqMethodEnum {
|
||||
|
||||
/**
|
||||
* HTTP请求方法枚举
|
||||
*/
|
||||
POST, GET, PUT, DELETE;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package com.cib.fintech.dfp.open.sdk.enums;
|
||||
|
||||
/**
|
||||
* 响应报文验签算法枚举
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public enum RespSignAlgorithmEnum {
|
||||
|
||||
/**
|
||||
* SHA1WITHRSA("1", "SHA1withRSA")
|
||||
*/
|
||||
SHA1WITHRSA("1", "SHA1WithRSA"),
|
||||
|
||||
/**
|
||||
* SHA256WITHRSA("2", "SHA256withRSA")
|
||||
*/
|
||||
SHA256WITHRSA("2", "SHA256WithRSA"),
|
||||
|
||||
/**
|
||||
* SM3WITHSM2("3", "SM3WithSM2")
|
||||
*/
|
||||
SM3WITHSM2("3", "SM3WithSM2");
|
||||
|
||||
/**
|
||||
* 根据编码找枚举
|
||||
*
|
||||
* @param code 编码
|
||||
* @return KeyStatusEnum
|
||||
*/
|
||||
public static RespSignAlgorithmEnum getByCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
if (values() == null) {
|
||||
return null;
|
||||
}
|
||||
for (RespSignAlgorithmEnum t : values()) {
|
||||
if (t.getCode().equals(code)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
private final String label;
|
||||
|
||||
private RespSignAlgorithmEnum(String code, String label) {
|
||||
this.code = code;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public RespSignAlgorithmEnum returnEnum(String persistedValue) {
|
||||
return getByCode(persistedValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.cib.fintech.dfp.open.sdk.exception;
|
||||
|
||||
/**
|
||||
* SDK异常枚举
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public enum SdkExType {
|
||||
|
||||
/**
|
||||
* 异常码枚举
|
||||
*/
|
||||
CONN_ERR("OPEN25801", "通讯错误或超时,交易未决"),
|
||||
|
||||
PARAM_ERR("OPEN25802", "参数校验错误"),
|
||||
|
||||
UNKNOWN("OPEN25803", "未知错误,请检查是否为最新版本SDK或是否配置错误"),
|
||||
|
||||
RESPONSE_SIGN_ERR("OPEN25804", "响应报文验签失败,请检查验签公钥或验签算法配置"),
|
||||
|
||||
FILE_ERROR_RESULT("OPEN25805", "文件读取或写入失败"),
|
||||
|
||||
REQ_PARAM_ENCRYPT_ERR("OPEN25806", "字段加/解密失败,请检查字段加密密钥或字段值"),
|
||||
|
||||
KEY_CONFIGURE_ERR("OPEN25807", "当前keyId未找到配置对象信息,请检查keyConfigures集合中是否保存"),
|
||||
|
||||
CALLBACK_REQUEST_VERIFY_ERR("OPEN25808", "回调请求报文验签失败,请检查验签公钥或验签算法配置"),
|
||||
|
||||
CALLBACK_RESPONSE_SIGN_ERR("OPEN25809", "回调响应报文签名失败,请检查签名私钥或签名算法配置"),
|
||||
|
||||
CALLBACK_BODY_ENCRYPT_ERR("OPEN25810", "回调报文加/解密失败,请检查报文加/解密钥或报文值"),
|
||||
|
||||
NET_SIGN_ERR("OPEN25812", "数字信封制作失败,请检查签名/验签密钥或加密算法配置");
|
||||
|
||||
/**
|
||||
* 根据编码找枚举
|
||||
*
|
||||
* @param code 编码
|
||||
* @return SdkExType
|
||||
*/
|
||||
public static SdkExType getByCode(String code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (SdkExType t : values()) {
|
||||
if (t.getCode().equals(code)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String code;
|
||||
|
||||
private final String msg;
|
||||
|
||||
private SdkExType(String code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public SdkExType returnEnum(String persistedValue) {
|
||||
return getByCode(persistedValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.cib.fintech.dfp.open.sdk.exception;
|
||||
|
||||
/**
|
||||
* SDK异常
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class SdkException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 7634266621864608219L;
|
||||
|
||||
/**
|
||||
* 异常码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 异常信息
|
||||
*/
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 异常跟踪ID
|
||||
*/
|
||||
private String traceId;
|
||||
|
||||
public SdkException(SdkExType code) {
|
||||
this.code = code.getCode();
|
||||
this.msg = code.getMsg();
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
public void setTraceId(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{\"code\":\"" + code + "\",\"msg\":\"[" + code + "]" + msg + "\",\"traceId\":\"OPEN-00-LOCAL-" + code
|
||||
.substring(code.length() - 3, code.length()) + "\"}";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
package com.cib.fintech.dfp.open.sdk.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cib.fintech.dfp.open.sdk.config.Configure;
|
||||
import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
import com.cib.fintech.dfp.open.sdk.util.*;
|
||||
import com.cib.fintech.dfp.open.sdk.util.Base64;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 通用请求器接口
|
||||
* <p>
|
||||
* 如果需要使用自己的HTTP通讯库,请继承自该接口
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public abstract class AbstractTransService {
|
||||
|
||||
/**
|
||||
* 发送请求
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param method 请求方法
|
||||
* @param authInfo 请求头部签名信息
|
||||
* @param headParams 头部请求参数集合
|
||||
* @param bodyParam BODY请求参数
|
||||
* @param filePath 文件路径
|
||||
* @param bodyParamMap BODY请求参数集合
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @param bodyJson BODY请求参数JSON字符串
|
||||
* @return 通讯返回结果
|
||||
* @throws Exception
|
||||
*/
|
||||
public abstract Object send(String url, ReqMethodEnum method, String authInfo, Map<String, String> headParams,
|
||||
String bodyParam, String filePath, Map<String, String> bodyParamMap, KeyConfigure keyConfigure,
|
||||
String bodyJson) throws Exception;
|
||||
|
||||
/**
|
||||
* 获取Configure指定的通讯类实例
|
||||
*
|
||||
* @return 通讯类实例
|
||||
* @throws ClassNotFoundException
|
||||
* @throws InstantiationException
|
||||
* @throws IllegalAccessException
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static AbstractTransService getInstance()
|
||||
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
|
||||
Class c = Class.forName(Configure.getHttpsRequestClassName());
|
||||
return (AbstractTransService) c.newInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通讯接口
|
||||
*
|
||||
* @param url 请求地址
|
||||
* @param method 请求方法
|
||||
* @param authInfo 请求头部签名信息
|
||||
* @param headParams 头部请求参数
|
||||
* @param bodyParamString BODY请求参数
|
||||
* @param filePath 文件路径
|
||||
* @param bodyParamMap BODY请求参数集合
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @param bodyJson BODY请求参数JSON字符串
|
||||
* @return 通讯返回字符串
|
||||
*/
|
||||
protected static Object txn(String url, ReqMethodEnum method, String authInfo, Map<String, String> headParams,
|
||||
String bodyParamString, String filePath, Map<String, String> bodyParamMap, KeyConfigure keyConfigure,
|
||||
String bodyJson) throws SdkException {
|
||||
Object resp = null;
|
||||
try {
|
||||
AbstractTransService transService = AbstractTransService.getInstance();
|
||||
resp = transService
|
||||
.send(url, method, authInfo, headParams, bodyParamString, filePath, bodyParamMap, keyConfigure,
|
||||
bodyJson);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof IOException) {
|
||||
throw new SdkException(SdkExType.CONN_ERR);
|
||||
} else if (e instanceof SdkException) {
|
||||
throw (SdkException) e;
|
||||
} else {
|
||||
throw new SdkException(SdkExType.UNKNOWN);
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求地址
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected String getPostUrl() {
|
||||
String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
|
||||
return (SdkUtil.notBlank(url) && url.endsWith("/")) ? url.substring(0, url.length() - 1) : url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算头部签名信息
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param uri 请求URI
|
||||
* @param signParams 参数集合
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @return 头部认证信息内容
|
||||
*/
|
||||
protected String getAuthInfo(ReqMethodEnum method, String uri, Map<String, String> signParams,
|
||||
KeyConfigure keyConfigure) {
|
||||
String timestamp = SdkUtil.getDateTime();
|
||||
|
||||
// 待签参数拼接
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(keyConfigure.getKeyId());
|
||||
builder.append("&");
|
||||
builder.append(timestamp);
|
||||
builder.append("&");
|
||||
builder.append(nonce);
|
||||
builder.append("&");
|
||||
builder.append(method);
|
||||
builder.append("&");
|
||||
builder.append(uri);
|
||||
if (signParams != null && signParams.size() != 0) {
|
||||
List<String> keyList = new ArrayList<String>(signParams.keySet());
|
||||
Collections.sort(keyList);
|
||||
for (String key : keyList) {
|
||||
builder.append("&");
|
||||
builder.append(key);
|
||||
builder.append("=");
|
||||
builder.append(signParams.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
// 组装请求头部签名信息
|
||||
String user = keyConfigure.getKeyId() + "_" + timestamp + "_" + nonce;
|
||||
String pwd;
|
||||
if (KeySignTypeEnum.SHA256WITHRSA.equals(keyConfigure.getKeySignType())) {
|
||||
pwd = Signature.signatureByRSA(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
|
||||
} else {
|
||||
pwd = Signature.signatureBySM2(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
|
||||
}
|
||||
|
||||
return "Basic " + Base64.encode((user + ":" + pwd).getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算头部签名信息(数字信封)
|
||||
*
|
||||
* @param method 请求方法
|
||||
* @param uri 请求URI
|
||||
* @param signParams 参数集合
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @return 头部认证信息内容
|
||||
*/
|
||||
protected String getNetSignAuthInfo(ReqMethodEnum method, String uri, String commKey, Map<String, String> signParams,
|
||||
KeyConfigure keyConfigure) throws Exception {
|
||||
String timestamp = SdkUtil.getDateTime();
|
||||
|
||||
// 待签参数拼接
|
||||
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(keyConfigure.getKeyId());
|
||||
builder.append("&");
|
||||
builder.append(timestamp);
|
||||
builder.append("&");
|
||||
builder.append(nonce);
|
||||
builder.append("&");
|
||||
builder.append(method);
|
||||
builder.append("&");
|
||||
builder.append(uri);
|
||||
builder.append("&");
|
||||
builder.append(commKey);
|
||||
|
||||
if (signParams != null && signParams.size() != 0) {
|
||||
List<String> keyList = new ArrayList<String>(signParams.keySet());
|
||||
Collections.sort(keyList);
|
||||
for (String key : keyList) {
|
||||
builder.append("&");
|
||||
builder.append(key);
|
||||
builder.append("=");
|
||||
builder.append(signParams.get(key));
|
||||
}
|
||||
}
|
||||
String encCommKey = Signature.encryptBySM2PublicKey(keyConfigure.getRespPubKey(), commKey.getBytes(StandardCharsets.UTF_8));
|
||||
// 组装请求头部信息
|
||||
String user = keyConfigure.getKeyId() + "_" + timestamp + "_" + nonce + "_" + encCommKey;
|
||||
String pwd;
|
||||
if (KeySignTypeEnum.SHA256WITHRSA.equals(keyConfigure.getKeySignType())) {
|
||||
pwd = Signature.signatureByRSA(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
|
||||
} else {
|
||||
pwd = Signature.signatureBySM2(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
|
||||
}
|
||||
|
||||
return "Basic " + Base64.encode((user + ":" + pwd).getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密请求参数
|
||||
*
|
||||
* @param bodyParamMap
|
||||
* @param bodyParamJson
|
||||
* @param commKey
|
||||
* @return
|
||||
*/
|
||||
public Map<String, String> getEncryptBody(Map<String, String> bodyParamMap, String commKey) throws SdkException {
|
||||
String bodyParamJsonString = JSON.toJSONString(bodyParamMap);
|
||||
Map<String, String> encryptBodyParamMap = new HashMap<>(2);
|
||||
try {
|
||||
encryptBodyParamMap.put("ENC_DATA", SymmetricEncrypt.sm4Encrypt(bodyParamJsonString, commKey));
|
||||
return encryptBodyParamMap;
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.NET_SIGN_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求处理
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param headParams 头部信息请求参数
|
||||
* @param urlParams URL请求参数
|
||||
* @param bodyParamMap BODY请求参数集合
|
||||
* @param filePath 文件路径
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @param bodyJson BODY请求参数JSON字符串
|
||||
* @return
|
||||
*/
|
||||
public Object exec(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams,
|
||||
Map<String, String> urlParams, Map<String, String> bodyParamMap, String filePath, KeyConfigure keyConfigure,
|
||||
String bodyJson) {
|
||||
|
||||
// 准备签名参数
|
||||
Map<String, String> signParams = new HashMap<String, String>(64);
|
||||
if (SdkUtil.notEmpty(headParams)) {
|
||||
signParams.putAll(headParams);
|
||||
}
|
||||
if (SdkUtil.notEmpty(urlParams)) {
|
||||
signParams.putAll(urlParams);
|
||||
}
|
||||
if (SdkUtil.notEmpty(bodyParamMap)) {
|
||||
signParams.putAll(bodyParamMap);
|
||||
}
|
||||
if (SdkUtil.isJSONString(bodyJson)) {
|
||||
signParams.put("BODY", bodyJson);
|
||||
}
|
||||
|
||||
// 计算签名值
|
||||
reqUri = SdkUtil.checkReqUri(reqUri);
|
||||
String authInfo = this.getAuthInfo(reqMethod, reqUri, signParams, keyConfigure);
|
||||
|
||||
// 拼接请求参数
|
||||
String urlParam = SdkUtil.jointMap(urlParams);
|
||||
String bodyParamString = SdkUtil.jointMap(bodyParamMap);
|
||||
|
||||
// 拼接请求URL
|
||||
StringBuilder urlBuilder = new StringBuilder();
|
||||
urlBuilder.append(this.getPostUrl());
|
||||
urlBuilder.append(reqUri);
|
||||
switch (reqMethod) {
|
||||
case GET:
|
||||
case POST:
|
||||
if (SdkUtil.notBlank(urlParam)) {
|
||||
urlBuilder.append(urlBuilder.toString().contains("?") ? "&" : "?");
|
||||
urlBuilder.append(urlParam);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 执行通讯请求
|
||||
Object response = "";
|
||||
try {
|
||||
response = txn(urlBuilder.toString(), reqMethod, authInfo, headParams, bodyParamString, filePath,
|
||||
bodyParamMap, keyConfigure, bodyJson);
|
||||
} catch (SdkException ex) {
|
||||
|
||||
if (SdkExType.CONN_ERR.getCode().equals(ex.getCode())) {
|
||||
String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
|
||||
response = ex.toString().replace("通讯错误", url + "通讯错误");
|
||||
} else {
|
||||
response = ex.toString();
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行请求处理
|
||||
*
|
||||
* @param reqUri 请求资源URI
|
||||
* @param reqMethod 请求方法
|
||||
* @param bodyParamMap BODY请求参数集合
|
||||
* @param filePath 文件路径
|
||||
* @param keyConfigure 应用配置对象
|
||||
* @param bodyJson BODY请求参数JSON字符串
|
||||
* @return
|
||||
*/
|
||||
public Object execNetSign(String reqUri, ReqMethodEnum reqMethod, Map<String, String> headParams, Map<String, String> urlParams,
|
||||
Map<String, String> bodyParamMap, String filePath, KeyConfigure keyConfigure,
|
||||
String bodyJson) throws Exception {
|
||||
|
||||
// 生成通讯对称秘钥
|
||||
String commKey = SymmetricEncrypt.generateSM4Key();
|
||||
// 准备签名参数
|
||||
Map<String, String> signParams = new HashMap<String, String>(64);
|
||||
if (SdkUtil.notEmpty(headParams)) {
|
||||
signParams.putAll(headParams);
|
||||
}
|
||||
if (SdkUtil.notEmpty(urlParams)) {
|
||||
signParams.putAll(urlParams);
|
||||
}
|
||||
if (SdkUtil.notEmpty(bodyParamMap)) {
|
||||
signParams.putAll(bodyParamMap);
|
||||
}
|
||||
if (SdkUtil.isJSONString(bodyJson)) {
|
||||
signParams.put("BODY", bodyJson);
|
||||
}
|
||||
|
||||
// 计算签名值
|
||||
reqUri = SdkUtil.checkReqUri(reqUri);
|
||||
String authInfo = this.getNetSignAuthInfo(reqMethod, reqUri, commKey, signParams, keyConfigure);
|
||||
// 加密签名请求体
|
||||
Map<String, String> encryptBodyParamMap = this.getEncryptBody(bodyParamMap, commKey);
|
||||
|
||||
// 拼接请求参数
|
||||
// String bodyParamString = SdkUtil.jointMap(encryptBodyParamMap);
|
||||
|
||||
// 拼接请求URL
|
||||
StringBuilder urlBuilder = new StringBuilder();
|
||||
urlBuilder.append(this.getPostUrl());
|
||||
urlBuilder.append(reqUri);
|
||||
String bodyParamString = SdkUtil.jointMap(encryptBodyParamMap);
|
||||
// 执行通讯请求
|
||||
Object response = "";
|
||||
try {
|
||||
response = txn(urlBuilder.toString(), reqMethod, authInfo, null, bodyParamString, filePath,
|
||||
encryptBodyParamMap, keyConfigure, bodyJson);
|
||||
//响应报文解密
|
||||
JSONObject responseObject = JSON.parseObject((String) response);
|
||||
String encData = responseObject.getString("ENC_DATA");
|
||||
if (encData == null || encData.equals("")) {
|
||||
return response;
|
||||
}
|
||||
response = SymmetricEncrypt.sm4Decrypt(encData, commKey);
|
||||
} catch (SdkException ex) {
|
||||
|
||||
if (SdkExType.CONN_ERR.getCode().equals(ex.getCode())) {
|
||||
String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
|
||||
response = ex.toString().replace("通讯错误", url + "通讯错误");
|
||||
} else {
|
||||
response = ex.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
/**
|
||||
* Base64工具类
|
||||
*
|
||||
* @author cibfintech
|
||||
*/
|
||||
public final class Base64 {
|
||||
|
||||
static private final int BASELENGTH = 128;
|
||||
static private final int LOOKUPLENGTH = 64;
|
||||
static private final int TWENTYFOURBITGROUP = 24;
|
||||
static private final int EIGHTBIT = 8;
|
||||
static private final int SIXTEENBIT = 16;
|
||||
static private final int FOURBYTE = 4;
|
||||
static private final int SIGN = -128;
|
||||
static private final char PAD = '=';
|
||||
static private final boolean F_DEBUG = false;
|
||||
static final private byte[] BASE64_ALPHABET = new byte[BASELENGTH];
|
||||
static final private char[] LOOKUP_BASE64_ALPHABET = new char[LOOKUPLENGTH];
|
||||
|
||||
static {
|
||||
for (int i = 0; i < BASELENGTH; ++i) {
|
||||
BASE64_ALPHABET[i] = -1;
|
||||
}
|
||||
for (int i = 'Z'; i >= 'A'; i--) {
|
||||
BASE64_ALPHABET[i] = (byte) (i - 'A');
|
||||
}
|
||||
for (int i = 'z'; i >= 'a'; i--) {
|
||||
BASE64_ALPHABET[i] = (byte) (i - 'a' + 26);
|
||||
}
|
||||
|
||||
for (int i = '9'; i >= '0'; i--) {
|
||||
BASE64_ALPHABET[i] = (byte) (i - '0' + 52);
|
||||
}
|
||||
|
||||
BASE64_ALPHABET['+'] = 62;
|
||||
BASE64_ALPHABET['/'] = 63;
|
||||
|
||||
for (int i = 0; i <= 25; i++) {
|
||||
LOOKUP_BASE64_ALPHABET[i] = (char) ('A' + i);
|
||||
}
|
||||
|
||||
for (int i = 26, j = 0; i <= 51; i++, j++) {
|
||||
LOOKUP_BASE64_ALPHABET[i] = (char) ('a' + j);
|
||||
}
|
||||
|
||||
for (int i = 52, j = 0; i <= 61; i++, j++) {
|
||||
LOOKUP_BASE64_ALPHABET[i] = (char) ('0' + j);
|
||||
}
|
||||
LOOKUP_BASE64_ALPHABET[62] = (char) '+';
|
||||
LOOKUP_BASE64_ALPHABET[63] = (char) '/';
|
||||
|
||||
}
|
||||
|
||||
private static boolean isWhiteSpace(char octect) {
|
||||
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
|
||||
}
|
||||
|
||||
private static boolean isPad(char octect) {
|
||||
return (octect == PAD);
|
||||
}
|
||||
|
||||
private static boolean isData(char octect) {
|
||||
return (octect < BASELENGTH && BASE64_ALPHABET[octect] != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes hex octects into Base64
|
||||
*
|
||||
* @param binaryData Array containing binaryData
|
||||
* @return Encoded Base64 array
|
||||
*/
|
||||
public static String encode(byte[] binaryData) {
|
||||
|
||||
if (binaryData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int lengthDataBits = binaryData.length * EIGHTBIT;
|
||||
if (lengthDataBits == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
|
||||
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
|
||||
int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
|
||||
char[] encodedData = null;
|
||||
|
||||
encodedData = new char[numberQuartet * 4];
|
||||
|
||||
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
|
||||
|
||||
int encodedIndex = 0;
|
||||
int dataIndex = 0;
|
||||
if (F_DEBUG) {
|
||||
System.out.println("number of triplets = " + numberTriplets);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberTriplets; i++) {
|
||||
b1 = binaryData[dataIndex++];
|
||||
b2 = binaryData[dataIndex++];
|
||||
b3 = binaryData[dataIndex++];
|
||||
|
||||
if (F_DEBUG) {
|
||||
System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3);
|
||||
}
|
||||
|
||||
l = (byte) (b2 & 0x0f);
|
||||
k = (byte) (b1 & 0x03);
|
||||
|
||||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
|
||||
byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
|
||||
|
||||
if (F_DEBUG) {
|
||||
System.out.println("val2 = " + val2);
|
||||
System.out.println("k4 = " + (k << 4));
|
||||
System.out.println("vak = " + (val2 | (k << 4)));
|
||||
}
|
||||
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val2 | (k << 4)];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[(l << 2) | val3];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[b3 & 0x3f];
|
||||
}
|
||||
|
||||
// form integral number of 6-bit groups
|
||||
if (fewerThan24bits == EIGHTBIT) {
|
||||
b1 = binaryData[dataIndex];
|
||||
k = (byte) (b1 & 0x03);
|
||||
if (F_DEBUG) {
|
||||
System.out.println("b1=" + b1);
|
||||
System.out.println("b1<<2 = " + (b1 >> 2));
|
||||
}
|
||||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[k << 4];
|
||||
encodedData[encodedIndex++] = PAD;
|
||||
encodedData[encodedIndex++] = PAD;
|
||||
} else if (fewerThan24bits == SIXTEENBIT) {
|
||||
b1 = binaryData[dataIndex];
|
||||
b2 = binaryData[dataIndex + 1];
|
||||
l = (byte) (b2 & 0x0f);
|
||||
k = (byte) (b1 & 0x03);
|
||||
|
||||
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
|
||||
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
|
||||
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val2 | (k << 4)];
|
||||
encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[l << 2];
|
||||
encodedData[encodedIndex++] = PAD;
|
||||
}
|
||||
|
||||
return new String(encodedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 data into octects
|
||||
*
|
||||
* @param encoded string containing Base64 data
|
||||
* @return Array containind decoded data.
|
||||
*/
|
||||
public static byte[] decode(String encoded) {
|
||||
|
||||
if (encoded == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
char[] base64Data = encoded.toCharArray();
|
||||
|
||||
/**
|
||||
* remove white spaces
|
||||
*/
|
||||
int len = removeWhiteSpace(base64Data);
|
||||
|
||||
/**
|
||||
* should be divisible by four
|
||||
*/
|
||||
if (len % FOURBYTE != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int numberQuadruple = (len / FOURBYTE);
|
||||
|
||||
if (numberQuadruple == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] decodedData = null;
|
||||
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
|
||||
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
|
||||
|
||||
int i = 0;
|
||||
int encodedIndex = 0;
|
||||
int dataIndex = 0;
|
||||
decodedData = new byte[(numberQuadruple) * 3];
|
||||
|
||||
for (; i < numberQuadruple - 1; i++) {
|
||||
|
||||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
|
||||
|| !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) {
|
||||
return null;
|
||||
}// if found "no data" just return null
|
||||
|
||||
b1 = BASE64_ALPHABET[d1];
|
||||
b2 = BASE64_ALPHABET[d2];
|
||||
b3 = BASE64_ALPHABET[d3];
|
||||
b4 = BASE64_ALPHABET[d4];
|
||||
|
||||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
|
||||
}
|
||||
|
||||
/**
|
||||
* if found "no data" just return null
|
||||
*/
|
||||
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
b1 = BASE64_ALPHABET[d1];
|
||||
b2 = BASE64_ALPHABET[d2];
|
||||
|
||||
d3 = base64Data[dataIndex++];
|
||||
d4 = base64Data[dataIndex++];
|
||||
/**
|
||||
* Check if they are PAD characters
|
||||
*/
|
||||
if (!isData((d3)) || !isData((d4))) {
|
||||
if (isPad(d3) && isPad(d4)) {
|
||||
/**
|
||||
* last 4 bits should be zero
|
||||
*/
|
||||
if ((b2 & 0xf) != 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] tmp = new byte[i * 3 + 1];
|
||||
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
|
||||
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
|
||||
return tmp;
|
||||
} else if (!isPad(d3) && isPad(d4)) {
|
||||
b3 = BASE64_ALPHABET[d3];
|
||||
/**
|
||||
* last 2 bits should be zero
|
||||
*/
|
||||
if ((b3 & 0x3) != 0) {
|
||||
return null;
|
||||
}
|
||||
byte[] tmp = new byte[i * 3 + 2];
|
||||
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
|
||||
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
return tmp;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else { // No PAD e.g 3cQl
|
||||
b3 = BASE64_ALPHABET[d3];
|
||||
b4 = BASE64_ALPHABET[d4];
|
||||
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
|
||||
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
|
||||
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
|
||||
|
||||
}
|
||||
|
||||
return decodedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove WhiteSpace from MIME containing encoded Base64 data.
|
||||
*
|
||||
* @param data the byte array of base64 data (with WS)
|
||||
* @return the new length
|
||||
*/
|
||||
private static int removeWhiteSpace(char[] data) {
|
||||
if (data == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// count characters that's not whitespace
|
||||
int newSize = 0;
|
||||
int len = data.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isWhiteSpace(data[i])) {
|
||||
data[newSize++] = data[i];
|
||||
}
|
||||
}
|
||||
return newSize;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 回调相关工具类
|
||||
*
|
||||
* @author wenjunxin
|
||||
* @version 4.4.0
|
||||
*/
|
||||
public class CallbackUtil {
|
||||
|
||||
/**
|
||||
* 对回调请求进行解密验签
|
||||
*
|
||||
* @param keyId 应用ID(请求头中获得,字段名:Keyid,示例:KYONLYFORTEST012345678)
|
||||
* @param timestamp 时间戳(请求头中获得,字段名:Timestamp,示例:20210810185848 其中时间格式为yyyyMMddHHmmss)
|
||||
* @param nonce 随机数(请求头中获得,字段名:Nonce,示例:CGYHt6YaCn7aFm8B76yXTW1Q)
|
||||
* @param signValue 签名值(请求头中获得,字段名:Signature,示例:T3J8o4zY5eGvG4to7xkGfqTZN3Nk4w8NSKG/ADdxhFy+OhhebHUS8t4Wk+w3HSA4K2UXJA5o8AUXm/QAa98FFg==)
|
||||
* @param body 回调请求数据(请求体中获得,参数名:ciphertext,加密内容,示例:{"ciphertext":"xq1hzkLPC4JQC6FVE7nCzroDjWK+3WtYGrYuwpgGsX4="} )
|
||||
* @param decryptKey 解密秘钥(sm4加密,示例:iij8IGO+6+ZVpRshrug3IQ==)
|
||||
* @param publicKey 验签公钥(sm2加密,示例:BCfPH6Alxb18eLXup8zF4nw3IZrf44FOZYSIlEaI1ePmWijbrBHiEGSWZzMcBbbcytRoRFLftlp/g45Y/m2b8zE=)
|
||||
* @return 解密验签后的body(sm4解密sm2签名,Json字符串,解密后示例:{xxx:xxx,xxx:xxx})
|
||||
*/
|
||||
public static String decryptAndVerify(String keyId, String timestamp, String nonce, String signValue, String body,
|
||||
String decryptKey, String publicKey) throws SdkException {
|
||||
|
||||
//解密后的body
|
||||
String decryptedBody;
|
||||
// 解密
|
||||
try {
|
||||
decryptedBody = SymmetricEncrypt.sm4Decrypt(body, decryptKey);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
|
||||
}
|
||||
|
||||
// 验签
|
||||
if (SdkUtil.notBlank(signValue)) {
|
||||
StringBuilder signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
|
||||
.append(nonce).append("&").append(decryptedBody);
|
||||
try {
|
||||
byte[] bytes = signParams.toString().getBytes(Const.CHARSET);
|
||||
if (!VerifyRespSignature.verifyBySM2(bytes, signValue, publicKey)) {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
} else {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
|
||||
return decryptedBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对回调请求进行解密验签(V2版本)
|
||||
*
|
||||
* @param keyId 应用ID(请求头中获得,字段名:Keyid,示例:KYONLYFORTEST012345678)
|
||||
* @param timestamp 时间戳(请求头中获得,字段名:Timestamp,示例:20210810185848 其中时间格式为yyyyMMddHHmmss)
|
||||
* @param nonce 随机数(请求头中获得,字段名:Nonce,示例:CGYHt6YaCn7aFm8B76yXTW1Q)
|
||||
* @param pwd 对称秘钥(请求头中获得,字段名:Pwd)
|
||||
* @param signValue 签名值(请求头中获得,字段名:Signature,示例:T3J8o4zY5eGvG4to7xkGfqTZN3Nk4w8NSKG/ADdxhFy+OhhebHUS8t4Wk+w3HSA4K2UXJA5o8AUXm/QAa98FFg==)
|
||||
* @param body 回调请求数据(请求体中获得,参数名:ciphertext,加密内容,示例:{"ciphertext":"xq1hzkLPC4JQC6FVE7nCzroDjWK+3WtYGrYuwpgGsX4="} )
|
||||
* @param priKey 解密秘钥
|
||||
* @param respPubKey 验签公钥
|
||||
* @return 解密验签后的body(sm4解密sm2签名,Json字符串,解密后示例:{xxx:xxx,xxx:xxx})
|
||||
*/
|
||||
public static Map<String, String> decryptAndVerifyV2(String keyId, String timestamp, String nonce, String pwd, String signValue, String body,
|
||||
String priKey, String respPubKey) throws SdkException {
|
||||
|
||||
Map<String, String> resultMap = new HashMap<>(2);
|
||||
try {
|
||||
// 解密对称秘钥
|
||||
byte[] pwdByte = Signature.decryptBySM2PrivateKey(priKey, Base64.decode(pwd));
|
||||
assert pwdByte != null;
|
||||
pwd = new String(pwdByte, StandardCharsets.UTF_8);
|
||||
resultMap.put("pwd", pwd);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
|
||||
}
|
||||
|
||||
//解密后的body
|
||||
String decryptedBody;
|
||||
// 解密
|
||||
try {
|
||||
decryptedBody = SymmetricEncrypt.sm4Decrypt(body, pwd);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
|
||||
}
|
||||
|
||||
// 验签
|
||||
if (SdkUtil.notBlank(signValue)) {
|
||||
StringBuilder signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
|
||||
.append(nonce).append("&").append(pwd).append("&").append(decryptedBody);
|
||||
try {
|
||||
byte[] bytes = signParams.toString().getBytes(Const.CHARSET);
|
||||
if (!VerifyRespSignature.verifyBySM2(bytes, signValue, respPubKey)) {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
resultMap.put("body", decryptedBody);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
} else {
|
||||
throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调响应签名
|
||||
*
|
||||
* @param keyId 应用ID
|
||||
* @param timestamp 时间戳
|
||||
* @param nonce 随机数
|
||||
* @param body 响应内容
|
||||
* @param privateKey 私钥
|
||||
* @return 签名值
|
||||
*/
|
||||
public static String signature(String keyId, String timestamp, String nonce,
|
||||
String body, String privateKey) throws SdkException {
|
||||
StringBuilder signParams;
|
||||
if (SdkUtil.notBlank(keyId) && SdkUtil.notBlank(timestamp) &&
|
||||
SdkUtil.notBlank(nonce) && SdkUtil.notBlank(body)) {
|
||||
signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
|
||||
.append(nonce).append("&").append(body);
|
||||
} else {
|
||||
throw new SdkException(SdkExType.CALLBACK_RESPONSE_SIGN_ERR);
|
||||
}
|
||||
return Signature.signatureBySM2(signParams.toString(), privateKey, Const.CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回调响应加密
|
||||
*
|
||||
* @param body 需要加密的内容
|
||||
* @param encryptKey 加密密钥
|
||||
* @return 密文(Base64 encode)
|
||||
*/
|
||||
public static String encrypt(String body, String encryptKey) throws SdkException {
|
||||
String encryptedBody;
|
||||
try {
|
||||
encryptedBody = SymmetricEncrypt.sm4Encrypt(body, encryptKey);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
|
||||
}
|
||||
return encryptedBody;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* 全局静态常量
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public final class Const {
|
||||
|
||||
/**
|
||||
* 编码字符集:UTF-8
|
||||
*/
|
||||
public static final String CHARSET = "UTF-8";
|
||||
|
||||
/**
|
||||
* 发送请求时head中的认证信息KEY
|
||||
*/
|
||||
public static final String POST_HEAD_KEY = "Authorization";
|
||||
|
||||
/**
|
||||
* 时间格式化器
|
||||
*/
|
||||
public static final SimpleDateFormat FORMATTER_TIME = new SimpleDateFormat("yyyyMMddHHmmss");
|
||||
|
||||
/**
|
||||
* 签名加密类型:RSA
|
||||
*/
|
||||
public static final String SIGN_TYPE = "RSA";
|
||||
|
||||
/**
|
||||
* 加密算法
|
||||
*/
|
||||
public static final String SIGN_ALGORITHMS = "SHA256WithRSA";
|
||||
|
||||
/**
|
||||
* EC算法
|
||||
*/
|
||||
public static final String ALGORITHM_EC = "EC";
|
||||
|
||||
/**
|
||||
* SM3WithSM2算法
|
||||
*/
|
||||
public static final String ALGORITHM_SM3_WITH_SM2 = "SM3WithSM2";
|
||||
|
||||
/**
|
||||
* 下载成功返回消息
|
||||
*/
|
||||
public static final String FILE_SUCCESS_RESULT = "{\"code\":\"OPEN25800\",\"msg\":\"下载成功\",\"traceId\":\"OPEN-00-LOCAL-800\"}";
|
||||
|
||||
/**
|
||||
* 文件下载的uri
|
||||
*/
|
||||
public static final String FILE_DOWNLOAD_URI = "/api/open/downloadFile";
|
||||
|
||||
/**
|
||||
* 文件上传的uri
|
||||
*/
|
||||
public static final String FILE_UPLOAD_URI = "/api/open/uploadFile";
|
||||
|
||||
/**
|
||||
* 异常返回码前缀
|
||||
*/
|
||||
public static final String RESP_EXCEPTION_CODE = "\"code\":\"OPEN";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 文件工具
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class FileUtil {
|
||||
|
||||
public static final char[] HEX_CHAR = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
/**
|
||||
* @param filePath 文件路径
|
||||
* @return file bytes
|
||||
* @throws IOException 读取文件错误
|
||||
*/
|
||||
public static byte[] readFileByBytes(String filePath) throws IOException {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException(filePath);
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(((int) file.length()));
|
||||
BufferedInputStream in = null;
|
||||
try {
|
||||
in = new BufferedInputStream(new FileInputStream(file));
|
||||
int bufSize = 1024;
|
||||
byte[] buffer = new byte[bufSize];
|
||||
int len;
|
||||
while (-1 != (len = in.read(buffer, 0, bufSize))) {
|
||||
baos.write(buffer, 0, len);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
baos.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件的特征值
|
||||
* <p>
|
||||
* 支持的算法如:"MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @param algorithm 算法
|
||||
* @return hashValue 特征值
|
||||
*/
|
||||
public static String fileHash(String filePath, String algorithm) throws Exception {
|
||||
MessageDigest messagedigest = null;
|
||||
try {
|
||||
messagedigest = MessageDigest.getInstance(algorithm);
|
||||
|
||||
InputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(filePath);
|
||||
byte[] buffer = new byte[1024];
|
||||
int numRead = 0;
|
||||
while ((numRead = fis.read(buffer)) > 0) {
|
||||
messagedigest.update(buffer, 0, numRead);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// ex.printStackTrace();
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
|
||||
return ex.toString();
|
||||
}
|
||||
return toHexString(messagedigest.digest());
|
||||
}
|
||||
|
||||
public static String toHexString(byte[] b) {
|
||||
StringBuilder sb = new StringBuilder(b.length * 2);
|
||||
for (int i = 0; i < b.length; i++) {
|
||||
sb.append(HEX_CHAR[(b[i] & 0xf0) >>> 4]);
|
||||
sb.append(HEX_CHAR[b[i] & 0x0f]);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SDK工具类
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class SdkUtil {
|
||||
|
||||
private static final String REQ_SEPARATOR = "/";
|
||||
|
||||
/**
|
||||
* 检查请求URI
|
||||
*
|
||||
* @param reqUri 请求URI
|
||||
* @return 处理后的URI
|
||||
*/
|
||||
public static String checkReqUri(String reqUri) {
|
||||
if (reqUri == null || reqUri.length() == 0) {
|
||||
return reqUri;
|
||||
}
|
||||
if (!reqUri.startsWith(REQ_SEPARATOR)) {
|
||||
reqUri = REQ_SEPARATOR + reqUri;
|
||||
}
|
||||
if (reqUri.endsWith(REQ_SEPARATOR)) {
|
||||
reqUri = reqUri.substring(0, reqUri.length() - 1);
|
||||
}
|
||||
return reqUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map拼接为字符串
|
||||
*
|
||||
* @param paramsMap 参数集
|
||||
* @return 拼接后的参数
|
||||
*/
|
||||
public static String jointMap(Map<String, String> paramsMap) {
|
||||
try {
|
||||
StringBuilder mapBuilder = new StringBuilder();
|
||||
if (paramsMap != null && paramsMap.size() != 0) {
|
||||
for (String key : paramsMap.keySet()) {
|
||||
mapBuilder.append(URLEncoder.encode(key, Const.CHARSET));
|
||||
mapBuilder.append("=");
|
||||
mapBuilder.append(null == paramsMap.get(key) ?
|
||||
null :
|
||||
URLEncoder.encode(paramsMap.get(key), Const.CHARSET));
|
||||
mapBuilder.append("&");
|
||||
}
|
||||
}
|
||||
if (mapBuilder.length() > 1) {
|
||||
mapBuilder.setLength(mapBuilder.length() - 1);
|
||||
}
|
||||
return mapBuilder.toString();
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取格式化的时间:yyyyMMddHHmmss
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public static String getDateTime() {
|
||||
return Const.FORMATTER_TIME.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为空
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean notBlank(String str) {
|
||||
if (null != str && "".equals(str) == false && "".equals(str.trim()) == false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断Map是否为空
|
||||
*
|
||||
* @param map 集合
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean notEmpty(Map<String, String> map) {
|
||||
if (map != null && map.size() != 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字符串是否为JSON格式
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 判断结果
|
||||
*/
|
||||
public static boolean isJSONString(String str) {
|
||||
if (null != str && str.startsWith("{") && str.endsWith("}")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.ASN1Sequence;
|
||||
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.engines.SM2Engine;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.util.BigIntegers;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 签名验签工具类
|
||||
*
|
||||
* @author zengminghua, tangchongcan
|
||||
*/
|
||||
public class Signature {
|
||||
|
||||
private static final BouncyCastleProvider BC = new BouncyCastleProvider();
|
||||
|
||||
private static final int RS_LEN = 32;
|
||||
|
||||
private static final String ALGORITHM_EC = "EC";
|
||||
|
||||
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
|
||||
private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
|
||||
x9ECParameters.getG(), x9ECParameters.getN());
|
||||
private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
|
||||
x9ECParameters.getG(), x9ECParameters.getN());
|
||||
|
||||
/**
|
||||
* RSA签名
|
||||
*
|
||||
* @param content 待签内容
|
||||
* @param privateKey 开发者私钥
|
||||
* @param charset 字符编码
|
||||
* @return 签名值
|
||||
*/
|
||||
public static String signatureByRSA(String content, String privateKey, String charset) {
|
||||
try {
|
||||
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
|
||||
KeyFactory keyf = KeyFactory.getInstance(Const.SIGN_TYPE);
|
||||
PrivateKey priKey = keyf.generatePrivate(priPKCS8);
|
||||
|
||||
java.security.Signature signature = java.security.Signature.getInstance(Const.SIGN_ALGORITHMS);
|
||||
|
||||
signature.initSign(priKey);
|
||||
signature.update(content.getBytes(charset));
|
||||
|
||||
byte[] signed = signature.sign();
|
||||
return Base64.encode(signed);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM2签名
|
||||
*
|
||||
* @param content 待签内容
|
||||
* @param privateKey 开发者私钥
|
||||
* @param charset 字符编码
|
||||
* @return
|
||||
*/
|
||||
public static String signatureBySM2(String content, String privateKey, String charset) {
|
||||
byte[] privateKeyBytes = Base64.decode(privateKey);
|
||||
BCECPrivateKey bcecPrivateKey = getPrivateKeyFromD(BigIntegers.fromUnsignedByteArray(privateKeyBytes));
|
||||
try {
|
||||
byte[] contentBytes = content.getBytes(charset);
|
||||
byte[] signed = rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(contentBytes, bcecPrivateKey));
|
||||
return Base64.encode(signed);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用SM2公钥加密数据
|
||||
*
|
||||
* @param encodedPublicKey Base64编码的公钥
|
||||
* @param contentBytes 待加密内容
|
||||
* @return Base64编码后的密文数据
|
||||
*/
|
||||
public static String encryptBySM2PublicKey(String encodedPublicKey, byte[] contentBytes) throws Exception {
|
||||
try {
|
||||
byte[] publicKeyBytes = Base64.decode(encodedPublicKey);
|
||||
if (publicKeyBytes.length != 64 && publicKeyBytes.length != 65) {
|
||||
throw new Exception("err key length");
|
||||
}
|
||||
|
||||
BigInteger x, y;
|
||||
if (publicKeyBytes.length > 64) {
|
||||
x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 1, 32);
|
||||
y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 33, 32);
|
||||
} else {
|
||||
x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 0, 32);
|
||||
y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 32, 32);
|
||||
}
|
||||
BCECPublicKey bcecPublicKey = getPublicKeyFromXY(x, y);
|
||||
byte[] results = changeC1C2C3ToC1C3C2(encryptBySM2PublicKeyOld(contentBytes, bcecPublicKey));
|
||||
return Base64.encode(results);
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用SM2私钥解密
|
||||
*
|
||||
* @param encodedPrivateKey Base64编码的私钥
|
||||
* @param contentBytes 待解密内容
|
||||
* @return 明文数据(byte数组)
|
||||
*/
|
||||
public static byte[] decryptBySM2PrivateKey(String encodedPrivateKey, byte[] contentBytes) throws Exception {
|
||||
try {
|
||||
byte[] privateKeyBytes = Base64.decode(encodedPrivateKey);
|
||||
BCECPrivateKey bcecPrivateKey = getPrivateKeyFromD(BigIntegers.fromUnsignedByteArray(privateKeyBytes));
|
||||
return decryptBySM2PrivateKeyOld(changeC1C3C2ToC1C2C3(contentBytes), bcecPrivateKey);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c2||c3
|
||||
*
|
||||
* @param data
|
||||
* @param bcecPrivateKey
|
||||
* @return
|
||||
*/
|
||||
public static byte[] decryptBySM2PrivateKeyOld(byte[] data, BCECPrivateKey bcecPrivateKey)
|
||||
throws InvalidCipherTextException {
|
||||
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
|
||||
ecDomainParameters);
|
||||
SM2Engine sm2Engine = new SM2Engine();
|
||||
sm2Engine.init(false, ecPrivateKeyParameters);
|
||||
return sm2Engine.processBlock(data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
|
||||
*
|
||||
* @param c1c3c2
|
||||
* @return
|
||||
*/
|
||||
private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
|
||||
//sm2p256v1的这个固定65
|
||||
final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
|
||||
// 可看GMNamedCurves、ECCurve代码。
|
||||
final int c3Len = 32;
|
||||
byte[] result = new byte[c1c3c2.length];
|
||||
//c1: 0->65
|
||||
System.arraycopy(c1c3c2, 0, result, 0, c1Len);
|
||||
//c2
|
||||
System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len);
|
||||
//c3
|
||||
System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* c1||c2||c3
|
||||
*
|
||||
* @param data
|
||||
* @param bcecPublicKey
|
||||
* @return
|
||||
*/
|
||||
public static byte[] encryptBySM2PublicKeyOld(byte[] data, BCECPublicKey bcecPublicKey)
|
||||
throws InvalidCipherTextException {
|
||||
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(),
|
||||
ecDomainParameters);
|
||||
SM2Engine sm2Engine = new SM2Engine();
|
||||
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
|
||||
return sm2Engine.processBlock(data, 0, data.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
|
||||
*
|
||||
* @param c1c2c3
|
||||
* @return
|
||||
*/
|
||||
private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
|
||||
//sm2p256v1的这个固定65
|
||||
final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
|
||||
// 可看GMNamedCurves、ECCurve代码。
|
||||
final int c3Len = 32;
|
||||
byte[] result = new byte[c1c2c3.length];
|
||||
//c1
|
||||
System.arraycopy(c1c2c3, 0, result, 0, c1Len);
|
||||
//c3
|
||||
System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len);
|
||||
//c2
|
||||
System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BCECPrivateKey getPrivateKeyFromD(BigInteger d) {
|
||||
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
|
||||
return new BCECPrivateKey(Const.ALGORITHM_EC, ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
|
||||
}
|
||||
|
||||
private static BCECPublicKey getPublicKeyFromXY(BigInteger x, BigInteger y) {
|
||||
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
|
||||
ecParameterSpec);
|
||||
return new BCECPublicKey(ALGORITHM_EC, ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
|
||||
}
|
||||
|
||||
private static byte[] bigIntToFixedLengthBytes(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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
|
||||
*
|
||||
* @param rsDer rs in asn1 format
|
||||
* @return sign result in plain byte array
|
||||
*/
|
||||
private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
|
||||
ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
|
||||
byte[] r = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
|
||||
byte[] s = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
|
||||
byte[] result = new byte[RS_LEN * 2];
|
||||
System.arraycopy(r, 0, result, 0, r.length);
|
||||
System.arraycopy(s, 0, result, RS_LEN, s.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] signSm3WithSm2Asn1Rs(byte[] msg, PrivateKey privateKey)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
java.security.Signature signer = java.security.Signature.getInstance(Const.ALGORITHM_SM3_WITH_SM2, BC);
|
||||
signer.initSign(privateKey, new SecureRandom());
|
||||
signer.update(msg, 0, msg.length);
|
||||
byte[] sig = signer.sign();
|
||||
return sig;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* 字段加密工具类
|
||||
* <p>
|
||||
* 支持对称加密算法 AES 的加密、解密,本类涉及的byte编码均为UTF-8
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class SymmetricEncrypt {
|
||||
|
||||
private static final BouncyCastleProvider BC = new BouncyCastleProvider();
|
||||
|
||||
/**
|
||||
* 字段加密算法:SM4
|
||||
*/
|
||||
private static final String ALGORITHM_SM4 = "SM4";
|
||||
|
||||
/**
|
||||
* 密码器类型,算法/模式/补码方式
|
||||
*/
|
||||
private static final String ALGORITHM_SM4_CBC_PKCS5 = "SM4/CBC/PKCS5Padding";
|
||||
|
||||
/**
|
||||
* 字段加密算法:AES
|
||||
*/
|
||||
private static final String ALGORITHM_AES = "AES";
|
||||
|
||||
/**
|
||||
* 密码器类型,算法/模式/补码方式
|
||||
*/
|
||||
private static final String ALGORITHM_AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
|
||||
|
||||
/**
|
||||
* 密钥长度
|
||||
*/
|
||||
private static final int SYMMETRIC_KEY_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* 安全随机数算法
|
||||
*/
|
||||
private static final String SECURE_RANDOM_ALGORITHMS = "SHA1PRNG";
|
||||
|
||||
/**
|
||||
* 密钥长度
|
||||
*/
|
||||
private static final int SM4_KEY_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* AES对称加密
|
||||
*
|
||||
* @param content 待加密内容
|
||||
* @param reqParamEncryptKey 字段加密密钥
|
||||
* @return result 密文(Base64 encode)
|
||||
*/
|
||||
public static String aesEncrypt(String content, String reqParamEncryptKey) throws Exception {
|
||||
|
||||
try {
|
||||
// 创建密码器
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_AES_CBC_PKCS5);
|
||||
|
||||
// 初始化向量值
|
||||
IvParameterSpec iv = new IvParameterSpec(initIv(ALGORITHM_AES_CBC_PKCS5));
|
||||
|
||||
// 使用密钥初始化,设置为 [加密模式] 的密码器
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(reqParamEncryptKey), iv);
|
||||
byte[] result = cipher.doFinal(content.getBytes(Const.CHARSET));
|
||||
|
||||
// 通过Base64转码返回
|
||||
return Base64.encode(result);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密操作
|
||||
*
|
||||
* @param content 待解密内容
|
||||
* @param reqParamEncryptKey 字段加密密钥
|
||||
* @return result 明文
|
||||
*/
|
||||
public static String aesDecrypt(String content, String reqParamEncryptKey) throws Exception {
|
||||
|
||||
try {
|
||||
// 创建密码器
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_AES_CBC_PKCS5);
|
||||
|
||||
// 初始化向量值
|
||||
IvParameterSpec iv = new IvParameterSpec(initIv(ALGORITHM_AES_CBC_PKCS5));
|
||||
|
||||
// 使用密钥初始化,设置为 [解密模式] 的密码器
|
||||
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(reqParamEncryptKey), iv);
|
||||
|
||||
byte[] result = cipher.doFinal(Base64.decode(content));
|
||||
return new String(result, Const.CHARSET);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密钥对象
|
||||
*
|
||||
* @param symmetricKey 密钥
|
||||
* @return
|
||||
*/
|
||||
private static SecretKeySpec getSecretKey(String symmetricKey) throws Exception {
|
||||
|
||||
// 返回生成指定算法密钥生成器的 KeyGenerator 对象
|
||||
KeyGenerator keyGenerator = null;
|
||||
|
||||
try {
|
||||
// 指定为AES的KeyGenerator
|
||||
keyGenerator = KeyGenerator.getInstance(ALGORITHM_AES);
|
||||
|
||||
// 指定随机数
|
||||
SecureRandom random = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHMS);
|
||||
random.setSeed(symmetricKey.getBytes(Const.CHARSET));
|
||||
|
||||
keyGenerator.init(SYMMETRIC_KEY_LENGTH, random);
|
||||
SecretKey secretKey = keyGenerator.generateKey();
|
||||
byte[] enCodeFormat = secretKey.getEncoded();
|
||||
|
||||
// 转换为AES专用密钥
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, ALGORITHM_AES);
|
||||
return secretKeySpec;
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成SM4密钥,返回Base64编码后的密钥值(长度:24)
|
||||
*
|
||||
* @param
|
||||
* @return
|
||||
*/
|
||||
public static String generateSM4Key() {
|
||||
byte[] keyBytes = generateSM4KeyBytes();
|
||||
String stringKey = Base64.encode(keyBytes);
|
||||
return stringKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成SM4密钥,返回byte数组
|
||||
*
|
||||
* @param
|
||||
* @return
|
||||
*/
|
||||
public static byte[] generateSM4KeyBytes() {
|
||||
KeyGenerator keyGenerator;
|
||||
try {
|
||||
keyGenerator = KeyGenerator.getInstance(ALGORITHM_SM4, BC);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
keyGenerator.init(SM4_KEY_LENGTH, new SecureRandom());
|
||||
SecretKey secretKey = keyGenerator.generateKey();
|
||||
return secretKey.getEncoded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始向量的方法, 全部为0
|
||||
* <p>
|
||||
*
|
||||
* @param algorithm
|
||||
* @return
|
||||
*/
|
||||
private static byte[] initIv(String algorithm) {
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(algorithm);
|
||||
int blockSize = cipher.getBlockSize();
|
||||
byte[] iv = new byte[blockSize];
|
||||
for (int i = 0; i < blockSize; ++i) {
|
||||
iv[i] = 0;
|
||||
}
|
||||
return iv;
|
||||
} catch (Exception e) {
|
||||
|
||||
int blockSize = 16;
|
||||
byte[] iv = new byte[blockSize];
|
||||
for (int i = 0; i < blockSize; ++i) {
|
||||
iv[i] = 0;
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始向量的方法, 全部为0
|
||||
*
|
||||
* @param algorithm
|
||||
* @return
|
||||
*/
|
||||
private static byte[] initBCIv(String algorithm) {
|
||||
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(algorithm, BC);
|
||||
int blockSize = cipher.getBlockSize();
|
||||
byte[] iv = new byte[blockSize];
|
||||
for (int i = 0; i < blockSize; ++i) {
|
||||
iv[i] = 0;
|
||||
}
|
||||
return iv;
|
||||
} catch (Exception e) {
|
||||
|
||||
int blockSize = 16;
|
||||
byte[] iv = new byte[blockSize];
|
||||
for (int i = 0; i < blockSize; ++i) {
|
||||
iv[i] = 0;
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM4加密
|
||||
*
|
||||
* @param content 待加密内容
|
||||
* @param reqParamEncryptKey 加密密钥
|
||||
* @return 密文(Base64 encode)
|
||||
*/
|
||||
public static String sm4Encrypt(String content, String reqParamEncryptKey) throws Exception {
|
||||
|
||||
try {
|
||||
// 创建密码器
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_SM4_CBC_PKCS5, BC);
|
||||
|
||||
// 初始化向量值
|
||||
IvParameterSpec iv = new IvParameterSpec(initBCIv(ALGORITHM_SM4_CBC_PKCS5));
|
||||
Key sm4Key = new SecretKeySpec(Base64.decode(reqParamEncryptKey), ALGORITHM_SM4);
|
||||
// 使用密钥初始化,设置为 [加密模式] 的密码器
|
||||
cipher.init(Cipher.ENCRYPT_MODE, sm4Key, iv);
|
||||
|
||||
// 加密
|
||||
byte[] result = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// 通过Base64转码返回
|
||||
return Base64.encode(result);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM4解密操作
|
||||
*
|
||||
* @param content 待解密内容
|
||||
* @param reqParamEncryptKey 解密密钥
|
||||
* @return 明文
|
||||
*/
|
||||
public static String sm4Decrypt(String content, String reqParamEncryptKey) throws Exception {
|
||||
|
||||
try {
|
||||
// 创建密码器
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM_SM4_CBC_PKCS5, BC);
|
||||
|
||||
// 初始化向量值
|
||||
IvParameterSpec iv = new IvParameterSpec(initBCIv(ALGORITHM_SM4_CBC_PKCS5));
|
||||
Key sm4Key = new SecretKeySpec(Base64.decode(reqParamEncryptKey), ALGORITHM_SM4);
|
||||
// 使用密钥初始化,设置为 [解密模式] 的密码器
|
||||
cipher.init(Cipher.DECRYPT_MODE, sm4Key, iv);
|
||||
|
||||
// 解密
|
||||
byte[] result = cipher.doFinal(Base64.decode(content));
|
||||
|
||||
// 返回明文
|
||||
return new String(result, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.cib.fintech.dfp.open.sdk.util;
|
||||
|
||||
import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
|
||||
import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
|
||||
import com.cib.fintech.dfp.open.sdk.exception.SdkException;
|
||||
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||
import org.bouncycastle.asn1.ASN1Integer;
|
||||
import org.bouncycastle.asn1.DERSequence;
|
||||
import org.bouncycastle.asn1.gm.GMNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.util.BigIntegers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 响应报文验签工具类
|
||||
*
|
||||
* @author zengminghua
|
||||
*/
|
||||
public class VerifyRespSignature {
|
||||
|
||||
private static final BouncyCastleProvider BC = new BouncyCastleProvider();
|
||||
|
||||
private static final int RS_LEN = 32;
|
||||
|
||||
private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
|
||||
private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
|
||||
x9ECParameters.getG(), x9ECParameters.getN());
|
||||
|
||||
/**
|
||||
* 响应报文验签
|
||||
*
|
||||
* @param timestamp 响应报文头部的时间戳
|
||||
* @param nonce 响应报文头部的随机值
|
||||
* @param responseSignValue 响应报文头部的签名值
|
||||
* @param retn 响应报文
|
||||
* @throws SdkException
|
||||
*/
|
||||
public static void verify(String timestamp, String nonce, String responseSignValue, Object retn,
|
||||
KeyConfigure keyConfigure) throws SdkException {
|
||||
|
||||
if (SdkUtil.notBlank(timestamp) && SdkUtil.notBlank(responseSignValue)) {
|
||||
try {
|
||||
String respPubKey = keyConfigure.getRespPubKey();
|
||||
String verifyAlgorithm = keyConfigure.getRespSignAlgorithm().getLabel();
|
||||
byte[] bodyContentBytes;
|
||||
if (retn instanceof String) {
|
||||
String content = (String) retn;
|
||||
bodyContentBytes = content.getBytes(Const.CHARSET);
|
||||
} else {
|
||||
bodyContentBytes = (byte[]) retn;
|
||||
}
|
||||
|
||||
// 待验签bytes内容为时间戳bytes+报文bodyBytes
|
||||
byte[] timestampBytes = timestamp.getBytes(Const.CHARSET);
|
||||
byte[] nonceBytes = nonce.getBytes(Const.CHARSET);
|
||||
byte[] contentBytes = new byte[timestampBytes.length + nonceBytes.length + bodyContentBytes.length];
|
||||
System.arraycopy(timestampBytes, 0, contentBytes, 0, timestampBytes.length);
|
||||
System.arraycopy(nonceBytes, 0, contentBytes, timestampBytes.length, nonceBytes.length);
|
||||
System.arraycopy(bodyContentBytes, 0, contentBytes, timestampBytes.length + nonceBytes.length,
|
||||
bodyContentBytes.length);
|
||||
|
||||
boolean flag;
|
||||
if (RespSignAlgorithmEnum.SM3WITHSM2.equals(keyConfigure.getRespSignAlgorithm())) {
|
||||
flag = verifyBySM2(contentBytes, responseSignValue, respPubKey);
|
||||
} else {
|
||||
flag = verifyByRSA(contentBytes, responseSignValue, respPubKey, verifyAlgorithm);
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
|
||||
}
|
||||
} else {
|
||||
throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验签,支持SHA1WithRSA、SHA256WithRSA算法
|
||||
*
|
||||
* @param contentBytes 待验签名的内容
|
||||
* @param sign 签名值
|
||||
* @param publicKey 公钥
|
||||
* @param verifyAlgorithm 验签算法
|
||||
* @return 验签结果(true / false)
|
||||
*/
|
||||
private static boolean verifyByRSA(byte[] contentBytes, String sign, String publicKey, String verifyAlgorithm) {
|
||||
try {
|
||||
byte[] b = Base64.decode(publicKey);
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(b);
|
||||
KeyFactory factoty = KeyFactory.getInstance(Const.SIGN_TYPE);
|
||||
PublicKey pubKey = factoty.generatePublic(keySpec);
|
||||
java.security.Signature signature = java.security.Signature.getInstance(verifyAlgorithm);
|
||||
signature.initVerify(pubKey);
|
||||
signature.update(contentBytes);
|
||||
|
||||
return signature.verify(Base64.decode(sign));
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SM3WithSM2验签
|
||||
*
|
||||
* @param contentBytes 待验签名的内容
|
||||
* @param sign 签名值
|
||||
* @param publicKey 公钥
|
||||
* @return
|
||||
*/
|
||||
public static boolean verifyBySM2(byte[] contentBytes, String sign, String publicKey) {
|
||||
byte[] publicKeyBytes = Base64.decode(publicKey);
|
||||
if (publicKeyBytes.length != 64 && publicKeyBytes.length != 65) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BigInteger x, y;
|
||||
if (publicKeyBytes.length > 64) {
|
||||
x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 1, 32);
|
||||
y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 33, 32);
|
||||
} else {
|
||||
x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 0, 32);
|
||||
y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 32, 32);
|
||||
}
|
||||
BCECPublicKey bcecPublicKey = getPublicKeyFromXY(x, y);
|
||||
byte[] rs = Base64.decode(sign);
|
||||
return verifySm3WithSm2Asn1Rs(contentBytes, rsPlainByteArrayToAsn1(rs), bcecPublicKey);
|
||||
}
|
||||
|
||||
private static BCECPublicKey getPublicKeyFromXY(BigInteger x, BigInteger y) {
|
||||
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
|
||||
ecParameterSpec);
|
||||
return new BCECPublicKey(Const.ALGORITHM_EC, ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
|
||||
*
|
||||
* @param sign in plain byte array
|
||||
* @return rs result in asn1 format
|
||||
*/
|
||||
public 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 rs in <b>asn1 format</b>
|
||||
* @param publicKey
|
||||
* @return
|
||||
*/
|
||||
private static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] rs, PublicKey publicKey) {
|
||||
try {
|
||||
Signature signature = Signature.getInstance(Const.ALGORITHM_SM3_WITH_SM2, BC);
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(msg, 0, msg.length);
|
||||
return signature.verify(rs);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -225,6 +225,16 @@
|
|||
<artifactId>weixin-java-pay</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 国密 / 签名,与 demo 的 1.60 对齐;Readme 要求 jdk15on 1.60+ -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ZXing 二维码生成 -->
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,12 @@
|
|||
<artifactId>like-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 兴业数金开放银行 SDK -->
|
||||
<dependency>
|
||||
<groupId>org.mdd</groupId>
|
||||
<artifactId>dfp-open-sdk</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- SaToken -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<module>like-admin</module>
|
||||
<module>like-front</module>
|
||||
<module>like-common</module>
|
||||
<module>dfp-open-sdk</module>
|
||||
<module>mozhe-enrollment</module>
|
||||
<module>like-generator</module>
|
||||
</modules>
|
||||
|
|
@ -56,6 +57,9 @@
|
|||
|
||||
<weixin.version>4.4.0</weixin.version>
|
||||
<zxing.version>3.5.1</zxing.version>
|
||||
<jdk15on.version>1.60</jdk15on.version>
|
||||
<!-- 兴业数金开放银行 SDK(AbstractTransService)依赖 fastjson 1.x,与工程内 fastjson2 并存 -->
|
||||
<fastjson-legacy.version>1.2.83</fastjson-legacy.version>
|
||||
</properties>
|
||||
|
||||
<!-- 依赖声明 -->
|
||||
|
|
@ -263,12 +267,37 @@
|
|||
<version>${zxing.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${jdk15on.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>${jdk15on.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 全局工具 -->
|
||||
<dependency>
|
||||
<groupId>org.mdd</groupId>
|
||||
<artifactId>like-common</artifactId>
|
||||
<version>${like.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 兴业数金 dfp-open-sdk(银行示例 SDK 源码模块) -->
|
||||
<dependency>
|
||||
<groupId>org.mdd</groupId>
|
||||
<artifactId>dfp-open-sdk</artifactId>
|
||||
<version>${like.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson-legacy.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue