提交 15b6d39f 编写于 作者: J Javen205

支持国密

上级 ac8477b9
......@@ -51,5 +51,11 @@
<artifactId>xk-time</artifactId>
<version>${xk.time.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
......@@ -18,29 +18,32 @@ public enum AuthTypeEnum {
/**
* 国密
*/
SM("WECHATPAY2-SM2-WITH-SM3", "国密算法"),
SM2("WECHATPAY2-SM2-WITH-SM3", "AEAD_SM4_GCM", "国密算法"),
/**
* RSA
*/
RSA("WECHATPAY2-SHA256-RSA2048", "RSA算法"),
RSA("WECHATPAY2-SHA256-RSA2048", "AEAD_AES_256_GCM", "RSA算法"),
;
private final String url;
private final String code;
private final String platformCertAlgorithm;
private final String desc;
AuthTypeEnum(String url, String desc) {
this.url = url;
AuthTypeEnum(String code, String platformCertAlgorithm, String desc) {
this.code = code;
this.platformCertAlgorithm = platformCertAlgorithm;
this.desc = desc;
}
/**
* 获取枚举URL
* 获取枚举编码
*
* @return 枚举编码
*/
public String getUrl() {
return url;
public String getCode() {
return code;
}
/**
......@@ -51,4 +54,13 @@ public enum AuthTypeEnum {
public String getDesc() {
return desc;
}
/**
* 获取平台证书算法
*
* @return 平台证书算法
*/
public String getPlatformCertAlgorithm() {
return platformCertAlgorithm;
}
}
......@@ -14,13 +14,18 @@ import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.ijpay.core.XmlHelper;
import com.ijpay.core.constant.IJPayConstants;
import com.ijpay.core.enums.AuthTypeEnum;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.model.CertificateModel;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
......@@ -28,21 +33,11 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.security.*;
import java.security.cert.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
/**
* <p>IJPay 让支付触手可及,封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
......@@ -63,6 +58,138 @@ public class PayKit {
*/
public static final String CLASS_PATH_PREFIX = "classpath:";
/**
* 获取国密证书私钥
*
* @param privateKey 私钥
* @return 返回值
* @throws Exception 异常信息
*/
public static PrivateKey getSmPrivateKey(String privateKey) throws Exception {
byte[] encPrivate = Base64.decode(privateKey);
return getSmPrivateKey(encPrivate);
}
/**
* 获取国密证书公钥
*
* @param publicKey 公钥
* @return 返回值
* @throws Exception 异常信息
*/
public static PublicKey getSmPublicKey(String publicKey) throws Exception {
byte[] encPublic = Base64.decode(publicKey);
return getSmPublicKey(encPublic);
}
/**
* 获取国密证书私钥
*
* @param encPrivate 私钥
* @return 返回值
* @throws Exception 异常信息
*/
public static PrivateKey getSmPrivateKey(byte[] encPrivate) throws Exception {
KeyFactory keyFact = KeyFactory.getInstance("EC", new BouncyCastleProvider());
return keyFact.generatePrivate(new PKCS8EncodedKeySpec(encPrivate));
}
/**
* 获取国密证书公钥
*
* @param encPublic 公钥
* @return 返回值
* @throws Exception 异常信息
*/
public static PublicKey getSmPublicKey(byte[] encPublic) throws Exception {
KeyFactory keyFact = KeyFactory.getInstance("EC", new BouncyCastleProvider());
return keyFact.generatePublic(new X509EncodedKeySpec(encPublic));
}
/**
* 签名
*
* @param privateKey 私钥
* @param content 需要签名的内容
* @return 返回结果
* @throws Exception 异常信息
*/
public static String sm2SignWithSm3(String privateKey, String content) throws Exception {
PrivateKey smPrivateKey = getSmPrivateKey(privateKey);
return sm2SignWithSm3(smPrivateKey, content);
}
/**
* 签名
*
* @param privateKey 私钥
* @param content 需要签名的内容
* @return 返回结果
* @throws Exception 异常信息
*/
public static String sm2SignWithSm3(PrivateKey privateKey, String content) throws Exception {
// 生成SM2sign with sm3 签名验签算法实例
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString()
, new BouncyCastleProvider());
// 使用私钥签名,初始化签名实例
signature.initSign(privateKey);
// 签名原文
byte[] plainText = content.getBytes(StandardCharsets.UTF_8);
// 写入签名原文到算法中
signature.update(plainText);
// 计算签名值
byte[] signatureValue = signature.sign();
return Base64.encode(signatureValue);
}
/**
* SM3 Hash
*
* @param content 原始内容
* @return 返回结果
* @throws Exception 异常信息
*/
public static byte[] sm3Hash(String content) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SM3", new BouncyCastleProvider());
byte[] contentDigest = digest.digest(content.getBytes(StandardCharsets.UTF_8));
return Arrays.copyOf(contentDigest, 16);
}
/**
* 下载平台证书以及回调通知加解密
*
* @param key3 APIv3密钥
* @param cipherText 密文
* @param nonce 随机串
* @param associatedData 附加数据
* @return 解密后的明文
* @throws Exception 异常信息
*/
public static String sm4DecryptToString(String key3, String cipherText, String nonce, String associatedData) throws Exception {
Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", new BouncyCastleProvider());
byte[] keyByte = PayKit.sm3Hash(key3);
SecretKeySpec key = new SecretKeySpec(keyByte, "SM4");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
return new String(cipher.doFinal(Base64.decode(cipherText)), StandardCharsets.UTF_8);
}
public static boolean sm4Verify(String publicKey, String plainText, String originalSignature) throws Exception {
PublicKey smPublicKey = getSmPublicKey(publicKey);
return sm4Verify(smPublicKey, plainText, originalSignature);
}
public static boolean sm4Verify(PublicKey publicKey, String data, String originalSignature) throws Exception {
Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString()
, new BouncyCastleProvider());
signature.initVerify(publicKey);
// 写入待验签的签名原文到算法中
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.decode(originalSignature.getBytes(StandardCharsets.UTF_8)));
}
/**
* 生成16进制的 sha256 字符串
*
......@@ -321,7 +448,7 @@ public class PayKit {
* @return 构造后带待签名串
*/
public static String buildSignMessage(ArrayList<String> signMessage) {
if (signMessage == null || signMessage.size() <= 0) {
if (signMessage == null || signMessage.size() == 0) {
return null;
}
StringBuilder sbf = new StringBuilder();
......@@ -339,8 +466,8 @@ public class PayKit {
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(ArrayList<String> signMessage, String keyPath) throws Exception {
return createSign(buildSignMessage(signMessage), keyPath);
public static String createSign(ArrayList<String> signMessage, String keyPath, String authType) throws Exception {
return createSign(buildSignMessage(signMessage), keyPath, authType);
}
/**
......@@ -364,13 +491,16 @@ public class PayKit {
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, String keyPath) throws Exception {
public static String createSign(String signMessage, String keyPath, String authType) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
// 获取商户私钥
PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
PrivateKey privateKey = PayKit.getPrivateKey(keyPath, authType);
// 生成签名
if (StrUtil.equals(authType, AuthTypeEnum.SM2.getCode())) {
return sm2SignWithSm3(privateKey, signMessage);
}
return RsaKit.encryptByPrivateKey(signMessage, privateKey);
}
......@@ -418,8 +548,8 @@ public class PayKit {
* @return {@link String} 商户私钥
* @throws Exception 异常信息
*/
public static String getPrivateKeyStr(String keyPath) throws Exception {
return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath));
public static String getPrivateKeyStr(String keyPath, String authType) throws Exception {
return RsaKit.getPrivateKeyStr(getPrivateKey(keyPath, authType));
}
/**
......@@ -429,12 +559,12 @@ public class PayKit {
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKey(String keyPath) throws Exception {
public static PrivateKey getPrivateKey(String keyPath, String authType) throws Exception {
String originalKey = getCertFileContent(keyPath);
if (StrUtil.isEmpty(originalKey)) {
throw new RuntimeException("商户私钥证书获取失败");
}
return getPrivateKeyByKeyContent(originalKey);
return getPrivateKeyByKeyContent(originalKey, authType);
}
/**
......@@ -444,8 +574,11 @@ public class PayKit {
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKeyByKeyContent(String originalKey) throws Exception {
public static PrivateKey getPrivateKeyByKeyContent(String originalKey, String authType) throws Exception {
String privateKey = getPrivateKeyByContent(originalKey);
if (StrUtil.equals(authType, AuthTypeEnum.SM2.getCode())) {
return getSmPrivateKey(privateKey);
}
return RsaKit.loadPrivateKey(privateKey);
}
......@@ -470,7 +603,8 @@ public class PayKit {
*/
public static X509Certificate getCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
Security.addProvider(new BouncyCastleProvider());
CertificateFactory cf = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
......@@ -550,11 +684,6 @@ public class PayKit {
if (null == notAfter) {
return false;
}
// 证书颁发者
Principal issuerDn = model.getIssuerDn();
if (null == issuerDn || !issuerDn.getName().contains(IJPayConstants.ISSUER)) {
return false;
}
// 证书CN字段
if (StrUtil.isNotEmpty(mchId)) {
Principal subjectDn = model.getSubjectDn();
......
......@@ -378,7 +378,7 @@ public class WxPayKit {
* @throws Exception 错误信息
*/
public static Map<String, String> jsApiCreateSign(String appId, String prepayId, String keyPath) throws Exception {
return jsApiCreateSign(appId, prepayId, PayKit.getPrivateKey(keyPath));
return jsApiCreateSign(appId, prepayId, PayKit.getPrivateKey(keyPath, AuthTypeEnum.RSA.getCode()));
}
/**
......@@ -451,7 +451,7 @@ public class WxPayKit {
* @throws Exception 错误信息
*/
public static Map<String, String> appCreateSign(String appId, String partnerId, String prepayId, String keyPath) throws Exception {
return appCreateSign(appId, partnerId, prepayId, PayKit.getPrivateKey(keyPath));
return appCreateSign(appId, partnerId, prepayId, PayKit.getPrivateKey(keyPath, AuthTypeEnum.RSA.getCode()));
}
/**
......@@ -533,7 +533,7 @@ public class WxPayKit {
long timestamp, String authType) throws Exception {
// 构建签名参数
String buildSignMessage = PayKit.buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
String signature = PayKit.createSign(buildSignMessage, keyPath);
String signature = PayKit.createSign(buildSignMessage, keyPath, authType);
// 根据平台规则生成请求头 authorization
return PayKit.getAuthorization(mchId, serialNo, nonceStr, String.valueOf(timestamp), signature, authType);
}
......@@ -579,7 +579,7 @@ public class WxPayKit {
String serialNo, String keyPath, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = PayKit.generateStr();
return buildAuthorization(method, urlSuffix, mchId, serialNo, keyPath, body, nonceStr, timestamp, authType);
......@@ -601,9 +601,8 @@ public class WxPayKit {
String serialNo, PrivateKey privateKey, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = PayKit.generateStr();
return buildAuthorization(method, urlSuffix, mchId, serialNo, privateKey, body, nonceStr, timestamp, authType);
}
......@@ -621,7 +620,8 @@ public class WxPayKit {
String body = (String) map.get("body");
String nonceStr = (String) map.get("nonceStr");
String timestamp = (String) map.get("timestamp");
return verifySignature(signature, body, nonceStr, timestamp, PayKit.getCertFileInputStream(certPath));
String signatureType = (String) map.get("Wechatpay-Signature-Type");
return verifySignature(signatureType, signature, body, nonceStr, timestamp, PayKit.getCertFileInputStream(certPath));
}
/**
......@@ -636,8 +636,14 @@ public class WxPayKit {
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String signature = response.getHeader("Wechatpay-Signature");
String signatureType = response.getHeader("Wechatpay-Signature-Type");
String body = response.getBody();
return verifySignature(signature, body, nonceStr, timestamp, PayKit.getCertFileInputStream(certPath));
System.out.println("timestamp:" + timestamp);
System.out.println("nonceStr:" + nonceStr);
System.out.println("signature:" + signature);
System.out.println("signatureType:" + signatureType);
System.out.println("body:" + body);
return verifySignature(signatureType, signature, body, nonceStr, timestamp, PayKit.getCertFileInputStream(certPath));
}
/**
......@@ -652,8 +658,9 @@ public class WxPayKit {
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String signature = response.getHeader("Wechatpay-Signature");
String signatureType = response.getHeader("Wechatpay-Signature-Type");
String body = response.getBody();
return verifySignature(signature, body, nonceStr, timestamp, certInputStream);
return verifySignature(signatureType, signature, body, nonceStr, timestamp, certInputStream);
}
/**
......@@ -670,7 +677,8 @@ public class WxPayKit {
String body = (String) map.get("body");
String nonceStr = (String) map.get("nonceStr");
String timestamp = (String) map.get("timestamp");
return verifySignature(signature, body, nonceStr, timestamp, certInputStream);
String signatureType = (String) map.get("Wechatpay-Signature-Type");
return verifySignature(signatureType, signature, body, nonceStr, timestamp, certInputStream);
}
/**
......@@ -708,6 +716,7 @@ public class WxPayKit {
/**
* 验证签名
*
* @param signatureType 签名类型
* @param signature 待验证的签名
* @param body 应答主体
* @param nonce 随机串
......@@ -716,11 +725,14 @@ public class WxPayKit {
* @return 签名结果
* @throws Exception 异常信息
*/
public static boolean verifySignature(String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
public static boolean verifySignature(String signatureType, String signature, String body, String nonce, String timestamp, InputStream certInputStream) throws Exception {
String buildSignMessage = PayKit.buildSignMessage(timestamp, nonce, body);
// 获取证书
X509Certificate certificate = PayKit.getCertificate(certInputStream);
PublicKey publicKey = certificate.getPublicKey();
if (StrUtil.equals(signatureType, AuthTypeEnum.SM2.getCode())) {
return PayKit.sm4Verify(publicKey, buildSignMessage, signature);
}
return RsaKit.checkByPublicKey(buildSignMessage, signature, publicKey);
}
......
......@@ -13,14 +13,7 @@ import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.enums.WxApiEnum;
import com.ijpay.wxpay.enums.WxDomain;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v2.CouponApiEnum;
import com.ijpay.wxpay.enums.v2.DepositApiEnum;
import com.ijpay.wxpay.enums.v2.EntrustPayApiEnum;
import com.ijpay.wxpay.enums.v2.FacePayApiEnum;
import com.ijpay.wxpay.enums.v2.PayApiEnum;
import com.ijpay.wxpay.enums.v2.ProfitSharingApiEnum;
import com.ijpay.wxpay.enums.v2.RedPackApiEnum;
import com.ijpay.wxpay.enums.v2.TransferApiEnum;
import com.ijpay.wxpay.enums.v2.*;
import java.io.File;
import java.io.InputStream;
......@@ -340,17 +333,37 @@ public class WxPayApi {
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @param authType 认证类型
* @return {@link IJPayHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
String serialNo, String platSerialNo, String keyPath, String body, String authType) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String nonceStr = WxPayKit.generateStr();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, null);
}
/**
* V3 接口统一执行入口
*
* @param method {@link RequestMethodEnum} 请求方法
* @param urlPrefix 可通过 {@link WxDomain}来获取
* @param urlSuffix 可通过 {@link WxApiEnum} 来获取,URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param serialNo 商户 API 证书序列号
* @param platSerialNo 平台序列号
* @param keyPath apiclient_key.pem 证书路径
* @param body 接口请求参数
* @return {@link IJPayHttpResponse} 请求返回的结果
* @throws Exception 接口执行异常
*/
public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, String keyPath, String body) throws Exception {
String authType = AuthTypeEnum.RSA.getCode();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, authType);
}
/**
* V3 接口统一执行入口
*
......@@ -368,7 +381,7 @@ public class WxPayApi {
public static IJPayHttpResponse v3(RequestMethodEnum method, String urlPrefix, String urlSuffix, String mchId,
String serialNo, String platSerialNo, PrivateKey privateKey, String body) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = WxPayKit.generateStr();
return v3(method, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, privateKey, body, nonceStr, timestamp, authType, null);
}
......@@ -391,7 +404,7 @@ public class WxPayApi {
String mchId, String serialNo, String platSerialNo, String keyPath,
Map<String, String> params) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = WxPayKit.generateStr();
if (null != params && !params.keySet().isEmpty()) {
urlSuffix = urlSuffix.concat("?").concat(PayKit.createLinkString(params, true));
......@@ -417,7 +430,7 @@ public class WxPayApi {
String mchId, String serialNo, String platSerialNo, PrivateKey privateKey,
Map<String, String> params) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = WxPayKit.generateStr();
if (null != params && !params.keySet().isEmpty()) {
urlSuffix = urlSuffix.concat("?").concat(PayKit.createLinkString(params, true));
......@@ -441,7 +454,7 @@ public class WxPayApi {
*/
public static IJPayHttpResponse v3(String urlPrefix, String urlSuffix, String mchId, String serialNo, String platSerialNo, String keyPath, String body, File file) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = WxPayKit.generateStr();
return v3(RequestMethodEnum.UPLOAD, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, keyPath, body, nonceStr, timestamp, authType, file);
}
......@@ -463,7 +476,7 @@ public class WxPayApi {
public static IJPayHttpResponse v3(String urlPrefix, String urlSuffix, String mchId, String serialNo,
String platSerialNo, PrivateKey privateKey, String body, File file) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String authType = AuthTypeEnum.RSA.getUrl();
String authType = AuthTypeEnum.RSA.getCode();
String nonceStr = WxPayKit.generateStr();
return v3(RequestMethodEnum.UPLOAD, urlPrefix, urlSuffix, mchId, serialNo, platSerialNo, privateKey, body, nonceStr, timestamp, authType, file);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册