增加银行sdk代码, 更改pom, 添加依赖

This commit is contained in:
mirage 2026-03-25 11:32:01 +08:00
parent 6056e5282e
commit e910e65cd8
24 changed files with 3727 additions and 0 deletions

View File

@ -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 + bcpkixjdk15on 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
package com.cib.fintech.dfp.open.sdk.enums;
/**
* HTTP请求方法枚举
*
* @author zengminghua
*/
public enum ReqMethodEnum {
/**
* HTTP请求方法枚举
*/
POST, GET, PUT, DELETE;
}

View File

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

View File

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

View File

@ -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()) + "\"}";
}
}

View File

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

View File

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

View File

@ -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 解密验签后的bodysm4解密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 解密验签后的bodysm4解密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;
}
}

View File

@ -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";
}

View File

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

View File

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

View File

@ -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;
// 可看GMNamedCurvesECCurve代码
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;
// 可看GMNamedCurvesECCurve代码
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;
}
}

View File

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

View File

@ -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);
}
}
/**
* 验签支持SHA1WithRSASHA256WithRSA算法
*
* @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;
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>
<!-- 兴业数金开放银行 SDKAbstractTransService依赖 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>