diff --git a/README.md b/README.md
index 44ffd3f..f90673c 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,16 @@
[微信支付API v3](https://wechatpay-api.gitbook.io/wechatpay-api-v3/)的[Apache HttpClient](https://hc.apache.org/httpcomponents-client-ga/index.html)扩展,实现了请求签名的生成和应答签名的验证。
-如果你是使用Apache HttpClient的商户开发者,可以使用它构造`HttpClient`。得到的`HttpClient`在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
+> [!IMPORTANT]
+> 我们强烈建议你改为使用 [WechatPay-Java](https://github.com/wechatpay-apiv3/wechatpay-java),该SDK同样支持 Apache HttpClient 且提供了更完善的功能,本库未来只会进行必要的修复更新。
## 项目状态
-当前版本`0.4.4`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
+当前版本`0.6.0`为测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
## 升级指引
-若你使用的版本为`0.3.0`,升级前请参考[升级指南](UPGRADING.md)。
+若你使用的版本为`<=0.5.0`,升级前请参考[升级指南](UPGRADING.md)。
## 环境要求
@@ -27,7 +28,7 @@
在你的`build.gradle`文件中加入如下的依赖
```groovy
-implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.4.4'
+implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.6.0'
```
### Maven
@@ -37,17 +38,18 @@ implementation 'com.github.wechatpay-apiv3:wechatpay-apache-httpclient:0.4.4'
com.github.wechatpay-apiv3
wechatpay-apache-httpclient
- 0.4.4
+ 0.6.0
```
## 名词解释
-+ 商户API证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称CA)签发,以防证书被伪造或篡改。如何获取请见[商户API证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#shang-hu-api-zheng-shu)。
-+ 商户API私钥。商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件apiclient_key.pem中。注:不要把私钥文件暴露在公共场合,如上传到Github,写在客户端代码等。
-+ 微信支付平台证书。平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行应答签名的验证。获取平台证书需通过[获取平台证书列表](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu)接口下载。
-+ 证书序列号。每个证书都有一个由CA颁发的唯一编号,即证书序列号。如何查看证书序列号请看[这里](https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-hao)。
-+ API v3密钥。为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。API v3密钥是加密时使用的对称密钥。商户可以在【商户平台】->【API安全】的页面设置该密钥。
++ **商户API证书**,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称CA)签发,以防证书被伪造或篡改。如何获取请见[商户API证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#shang-hu-api-zheng-shu)。
++ **商户API私钥**。商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件apiclient_key.pem中。注:不要把私钥文件暴露在公共场合,如上传到Github,写在客户端代码等。
++ **微信支付平台证书**。平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行应答签名的验证。获取平台证书需通过[获取平台证书列表](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu)接口下载。
++ **微信支付公钥**。由微信支付生成,商户可以使用该公钥进行应答签名、回调签名的验证,详见:[微信支付公钥](https://pay.weixin.qq.com/doc/v3/merchant/4012153196)。
++ **证书序列号**。每个证书都有一个由CA颁发的唯一编号,即证书序列号。如何查看证书序列号请看[这里](https://wechatpay-api.gitbook.io/wechatpay-api-v3/chang-jian-wen-ti/zheng-shu-xiang-guan#ru-he-cha-kan-zheng-shu-xu-lie-hao)。
++ **API v3密钥**。为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。API v3密钥是加密时使用的对称密钥。商户可以在【商户平台】->【API安全】的页面设置该密钥。
## 开始
@@ -58,7 +60,7 @@ import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
//...
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
- .withWechatPay(wechatpayCertificates);
+ .withWechatPay(wechatpayPublicKeyId, wechatPayPublicKey);
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
@@ -73,7 +75,8 @@ CloseableHttpResponse response = httpClient.execute(...);
+ `merchantId`商户号。
+ `merchantSerialNumber`商户API证书的证书序列号。
+ `merchantPrivateKey`商户API私钥,如何加载商户API私钥请看[常见问题](#如何加载商户私钥)。
-+ `wechatpayCertificates`微信支付平台证书。你也可以使用后面章节提到的“[定时更新平台证书功能](#定时更新平台证书功能)”,而不需要关心平台证书的来龙去脉。
++ `wechatpayPublicKeyId`微信支付公钥ID,登录商户平台可获取,详见:[获取微信支付公钥](https://pay.weixin.qq.com/doc/v3/merchant/4013053249#1.-%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%85%AC%E9%92%A5)
++ `wechatPayPublicKey`微信支付公钥,登录商户平台可获取,详见:[获取微信支付公钥](https://pay.weixin.qq.com/doc/v3/merchant/4013053249#1.-%E8%8E%B7%E5%8F%96%E5%BE%AE%E4%BF%A1%E6%94%AF%E4%BB%98%E5%85%AC%E9%92%A5)
### 示例:获取平台证书
@@ -177,27 +180,30 @@ Credentials credentials = new WechatPay2Credentials(merchantId, new Signer() {
});
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withCredentials(credentials)
- .withWechatPay(wechatpayCertificates);
+ .withWechatPay(wechatpayPublicKeyId, wechatPayPublicKey);
```
## 定时更新平台证书功能
-版本>=`0.4.0`可使用 CertificatesManager.getVerifier(mchId) 得到的验签器替代默认的验签器。它会定时下载和更新商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu) (默认下载间隔为UPDATE_INTERVAL_MINUTE)。
+> [!IMPORTANT]
+> 新注册的商户使用「微信支付公钥」验签,不需要下载和更新平台证书。仅尚未完全迁移至「微信支付公钥」验签的旧商户才需要此能力。
+
+版本>=`0.4.0`可使用 CertificatesManager.getVerifier(merchantId) 得到的验签器替代默认的验签器。它会定时下载和更新商户对应的[微信支付平台证书](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/zheng-shu#ping-tai-zheng-shu) (默认下载间隔为UPDATE_INTERVAL_MINUTE)。
示例代码:
```java
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
-certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
- new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
+ new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
-verifier = certificatesManager.getVerifier(mchId);
+verifier = certificatesManager.getVerifier(merchantId);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
- .withValidator(new WechatPay2Validator(verifier))
+ .withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
@@ -217,13 +223,13 @@ CloseableHttpResponse response = httpClient.execute(...);
### 加密
-使用` RsaCryptoUtil.encryptOAEP(String, X509Certificate)`进行公钥加密。示例代码如下。
+使用` RsaCryptoUtil.encryptOAEP(String, PublicKey publicKey)`进行公钥加密。示例代码如下。
```java
// 建议从Verifier中获得微信支付平台证书,或使用预先下载到本地的平台证书文件中
-X509Certificate wechatpayCertificate = verifier.getValidCertificate();
+PublicKey publicKey = verifier.getValidPublicKey();
try {
- String ciphertext = RsaCryptoUtil.encryptOAEP(text, wechatpayCertificate);
+ String ciphertext = RsaCryptoUtil.encryptOAEP(text, publicKey);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
@@ -277,15 +283,40 @@ try (FileInputStream ins1 = new FileInputStream(file)) {
2. 使用`NotificationHandler`构造一个回调通知处理器,需设置验证器、apiV3密钥。调用`parse(request)`得到回调通知`notification`。
示例请参考下列代码。
+
```java
// 构建request,传入必要参数
- NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
+NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
-NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
+
+// 如果已经初始化了 NotificationHandler 则直接使用,否则根据具体情况创建一个
+
+// 1. 如果你使用的是微信支付公私钥,则使用公钥初始化 Verifier 以创建 NotificationHandler
+NotificationHandler handler = new NotificationHandler(
+ new PublicKeyVerifier(wechatPayPublicKeyId, wechatPayPublicKey),
+ apiV3Key.getBytes(StandardCharsets.UTF_8)
+);
+
+// 2. 如果你使用的事微信支付平台证书,则从 CertificatesManager 获取 Verifier 以创建 NotificationHandler
+NotificationHandler handler = new NotificationHandler(
+ certificatesManager.getVerifier(merchantId),
+ apiV3Key.getBytes(StandardCharsets.UTF_8)
+);
+
+// 3. 如果你正在进行微信支付平台证书到微信支付公私钥的灰度切换,希望保持切换兼容,则需要使用 MixVerifier 创建 NotificationHandler
+Verifier mixVerifier = new MixVerifier(
+ new PublicKeyVerifier(wechatPayPublicKeyId, wechatPayPublicKey),
+ certificatesManager.getVerifier(merchantId)
+);
+NotificationHandler handler = new NotificationHandler(
+ mixVerifier,
+ apiV3Key.getBytes(StandardCharsets.UTF_8)
+);
+
// 验签和解析请求体
Notification notification = handler.parse(request);
// 从notification中获取解密报文
@@ -306,11 +337,11 @@ System.out.println(notification.getDecryptData());
商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件`apiclient_key.pem`中。商户开发者可以使用方法`PemUtil.loadPrivateKey()`加载证书。
```java
-# 示例:私钥存储在文件
+// 示例:私钥存储在文件
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream("/path/to/apiclient_key.pem"));
-# 示例:私钥为String字符串
+// 示例:私钥为String字符串
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(privateKey.getBytes("utf-8")));
```
@@ -321,7 +352,7 @@ PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
```java
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
+ .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(response -> true) // NOTE: 设置一个空的应答签名验证器,**不要**用在业务请求
.build();
```
diff --git a/UPGRADING.md b/UPGRADING.md
index 7afebd7..6e1a7c2 100644
--- a/UPGRADING.md
+++ b/UPGRADING.md
@@ -1,7 +1,15 @@
# 升级指南
+
+## 从 0.5.0 升级至 0.6.0
+`interface Verifier` 不再提供 `getValidCertificate` 接口,请换用 `getValidPublicKey` 接口。
+请注意 `getValidCertificate` 与 `getValidPublicKey` 并不能等价替换,但其返回值都可以用于调用 `RsaCryptoUtil.encryptOAEP` 实现加密。
+
## 从 0.3.0 升级至 0.4.0
+
版本`0.4.0`提供了支持多商户号的[定时更新平台证书功能](README.md#定时更新平台证书功能),不兼容版本`0.3.0`。推荐升级方式如下:
+
- 若你使用了`ScheduledUpdateCertificatesVerifier`,请使用`CertificatesManager`替换:
+
```diff
-verifier = new ScheduledUpdateCertificatesVerifier(
- new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
@@ -9,9 +17,10 @@
+// 获取证书管理器实例
+certificatesManager = CertificatesManager.getInstance();
+// 向证书管理器增加需要自动更新平台证书的商户信息
-+certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
-+ new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
++certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
++ new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+// 从证书管理器中获取verifier
-+verifier = certificatesManager.getVerifier(mchId);
++verifier = certificatesManager.getVerifier(merchantId);
```
+
- 若你使用了`getLatestCertificate`方法,请使用`getValidCertificate`方法替换。
diff --git a/build.gradle b/build.gradle
index 36ba7a1..7cec3c9 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,22 +5,22 @@ plugins {
}
group 'com.github.wechatpay-apiv3'
-version '0.4.4'
+version '0.6.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
- jcenter()
+ mavenCentral()
}
ext {
publishedArtifactId = project.name
httpclient_version = "4.5.13"
- slf4j_version = "1.7.30"
- junit_version = "4.12"
- jackson_version = "2.12.5"
+ slf4j_version = "1.7.36"
+ junit_version = "4.13.2"
+ jackson_version = "2.13.4.2"
}
jar {
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/SignatureExec.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/SignatureExec.java
index 0ca0ae9..092dd6a 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/SignatureExec.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/SignatureExec.java
@@ -1,5 +1,6 @@
package com.wechat.pay.contrib.apache.httpclient;
+import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SERIAL;
import static org.apache.http.HttpHeaders.AUTHORIZATION;
import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES;
import static org.apache.http.HttpStatus.SC_OK;
@@ -81,6 +82,7 @@ private CloseableHttpResponse executeWithSignature(HttpRoute route, HttpRequestW
}
// 添加认证信息
request.addHeader(AUTHORIZATION, credentials.getSchema() + " " + credentials.getToken(request));
+ request.addHeader(WECHAT_PAY_SERIAL, validator.getSerialNumber());
// 执行
CloseableHttpResponse response = mainExec.execute(route, request, context, execAware);
// 对成功应答验签
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/Validator.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/Validator.java
index 4a56811..758f930 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/Validator.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/Validator.java
@@ -10,4 +10,5 @@ public interface Validator {
boolean validate(CloseableHttpResponse response) throws IOException;
+ String getSerialNumber();
}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/WechatPayHttpClientBuilder.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/WechatPayHttpClientBuilder.java
index 0c9ef9f..c67529c 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/WechatPayHttpClientBuilder.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/WechatPayHttpClientBuilder.java
@@ -2,14 +2,17 @@
import com.wechat.pay.contrib.apache.httpclient.auth.CertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.PublicKeyVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.List;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.execchain.ClientExecChain;
+import org.apache.http.HttpHost;
/**
* @author xy-peng
@@ -21,6 +24,7 @@ public class WechatPayHttpClientBuilder extends HttpClientBuilder {
private Credentials credentials;
private Validator validator;
+
private WechatPayHttpClientBuilder() {
super();
@@ -51,6 +55,18 @@ public WechatPayHttpClientBuilder withWechatPay(List certificat
return this;
}
+ public WechatPayHttpClientBuilder withWechatPay(String publicKeyId, PublicKey publicKey) {
+ this.validator = new WechatPay2Validator(new PublicKeyVerifier(publicKeyId, publicKey));
+ return this;
+ }
+
+ public WechatPayHttpClientBuilder withProxy(HttpHost proxy) {
+ if (proxy != null) {
+ this.setProxy(proxy);
+ }
+ return this;
+ }
+
/**
* Please use {@link #withWechatPay(List)} instead
*
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/AutoUpdateCertificatesVerifier.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/AutoUpdateCertificatesVerifier.java
index cf4987c..f607c3c 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/AutoUpdateCertificatesVerifier.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/AutoUpdateCertificatesVerifier.java
@@ -7,12 +7,14 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.contrib.apache.httpclient.Credentials;
+import com.wechat.pay.contrib.apache.httpclient.Validator;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
+import java.security.PublicKey;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
@@ -57,6 +59,19 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
protected volatile Instant lastUpdateTime;
protected CertificatesVerifier verifier;
+ private static final Validator emptyValidator =
+ new Validator() {
+ @Override
+ public boolean validate(CloseableHttpResponse response) throws IOException {
+ return true;
+ };
+
+ @Override
+ public String getSerialNumber() {
+ return "";
+ }
+ };
+
public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key) {
this(credentials, apiV3Key, TimeUnit.HOURS.toMinutes(1));
}
@@ -94,14 +109,19 @@ public boolean verify(String serialNumber, byte[] message, String signature) {
}
@Override
- public X509Certificate getValidCertificate() {
- return verifier.getValidCertificate();
+ public PublicKey getValidPublicKey() {
+ return verifier.getValidPublicKey();
+ }
+
+ @Override
+ public String getSerialNumber() {
+ return verifier.getSerialNumber();
}
protected void autoUpdateCert() throws IOException, GeneralSecurityException {
try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withCredentials(credentials)
- .withValidator(verifier == null ? (response) -> true : new WechatPay2Validator(verifier))
+ .withValidator(verifier == null ? emptyValidator : new WechatPay2Validator(verifier))
.build()) {
HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH);
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/CertificatesVerifier.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/CertificatesVerifier.java
index 681d925..5e52f31 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/CertificatesVerifier.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/CertificatesVerifier.java
@@ -3,6 +3,7 @@
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateExpiredException;
@@ -67,7 +68,6 @@ public boolean verify(String serialNumber, byte[] message, String signature) {
return verify(cert, message, signature);
}
- @Override
public X509Certificate getValidCertificate() {
X509Certificate latestCert = null;
for (X509Certificate x509Cert : certificates.values()) {
@@ -83,5 +83,16 @@ public X509Certificate getValidCertificate() {
throw new NoSuchElementException("没有有效的微信支付平台证书");
}
}
+
+
+ @Override
+ public PublicKey getValidPublicKey() {
+ return getValidCertificate().getPublicKey();
+ }
+
+ @Override
+ public String getSerialNumber() {
+ return getValidCertificate().getSerialNumber().toString(16).toUpperCase();
+ }
}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/MixVerifier.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/MixVerifier.java
new file mode 100644
index 0000000..59dfb85
--- /dev/null
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/MixVerifier.java
@@ -0,0 +1,54 @@
+package com.wechat.pay.contrib.apache.httpclient.auth;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.PublicKey;
+import java.util.Objects;
+
+
+/**
+ * MixVerifier 混合Verifier,仅用于切换平台证书与微信支付公钥时提供兼容
+ *
+ * 本实例需要使用一个 PublicKeyVerifier + 一个 Verifier 初始化,前者提供微信支付公钥验签,后者提供平台证书验签
+ */
+public class MixVerifier implements Verifier {
+ private static final Logger log = LoggerFactory.getLogger(MixVerifier.class);
+
+ final PublicKeyVerifier publicKeyVerifier;
+ final Verifier certificateVerifier;
+
+ public MixVerifier(PublicKeyVerifier publicKeyVerifier, Verifier certificateVerifier) {
+ if (publicKeyVerifier == null) {
+ throw new IllegalArgumentException("publicKeyVerifier cannot be null");
+ }
+
+ this.publicKeyVerifier = publicKeyVerifier;
+ this.certificateVerifier = certificateVerifier;
+ }
+
+ @Override
+ public boolean verify(String serialNumber, byte[] message, String signature) {
+ if (Objects.equals(publicKeyVerifier.getSerialNumber(), serialNumber)) {
+ return publicKeyVerifier.verify(serialNumber, message, signature);
+ }
+
+ if (certificateVerifier != null) {
+ return certificateVerifier.verify(serialNumber, message, signature);
+ }
+
+ log.error("找不到证书序列号对应的证书,序列号:{}", serialNumber);
+ return false;
+ }
+
+ @Override
+ public PublicKey getValidPublicKey() {
+ return publicKeyVerifier.getValidPublicKey();
+ }
+
+ @Override
+ public String getSerialNumber() {
+ return publicKeyVerifier.getSerialNumber();
+ }
+}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/PublicKeyVerifier.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/PublicKeyVerifier.java
new file mode 100644
index 0000000..8ff2d66
--- /dev/null
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/PublicKeyVerifier.java
@@ -0,0 +1,44 @@
+package com.wechat.pay.contrib.apache.httpclient.auth;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Base64;
+
+public class PublicKeyVerifier implements Verifier {
+ protected final PublicKey publicKey;
+ protected final String publicKeyId;
+
+ public PublicKeyVerifier(String publicKeyId, PublicKey publicKey) {
+ this.publicKey = publicKey;
+ this.publicKeyId = publicKeyId;
+ }
+
+ @Override
+ public boolean verify(String serialNumber, byte[] message, String signature) {
+ try {
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initVerify(publicKey);
+ sign.update(message);
+ return sign.verify(Base64.getDecoder().decode(signature));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
+ } catch (SignatureException e) {
+ throw new RuntimeException("签名验证过程发生了错误", e);
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("无效的证书", e);
+ }
+ }
+
+ @Override
+ public PublicKey getValidPublicKey() {
+ return publicKey;
+ }
+
+ @Override
+ public String getSerialNumber() {
+ return publicKeyId;
+ }
+}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/Verifier.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/Verifier.java
index 9d954bb..9d9346f 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/Verifier.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/Verifier.java
@@ -1,18 +1,37 @@
package com.wechat.pay.contrib.apache.httpclient.auth;
-import java.security.cert.X509Certificate;
+import java.security.PublicKey;
/**
* @author xy-peng
*/
public interface Verifier {
-
+ /**
+ * @param serialNumber 微信支付序列号(微信支付公钥ID 或 平台证书序列号)
+ * @param message 验签的原文
+ * @param signature 验签的签名
+ * @return 验证是否通过
+ */
boolean verify(String serialNumber, byte[] message, String signature);
/**
- * 获取合法的平台证书
- *
- * @return 合法证书
+ * 获取合法的公钥,针对不同的验签模式有所区别
+ *
+ * - 如果是微信支付公钥验签:则为微信支付公钥
+ * - 如果是平台证书验签:则为平台证书内部的公钥
+ *
+ * @return 合法公钥
+ */
+ PublicKey getValidPublicKey();
+
+
+ /**
+ * 获取微信支付序列号,针对不同的验签模式有所区别:
+ *
+ * - 如果是微信支付公钥验签:则为公钥ID,以
PUB_KEY_ID_
开头
+ * - 如果是平台证书验签:则为平台证书序列号
+ *
+ * @return 微信支付序列号
*/
- X509Certificate getValidCertificate();
+ String getSerialNumber();
}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/WechatPay2Validator.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/WechatPay2Validator.java
index b265db8..d9e8b95 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/WechatPay2Validator.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/auth/WechatPay2Validator.java
@@ -67,6 +67,11 @@ public final boolean validate(CloseableHttpResponse response) throws IOException
return true;
}
+ @Override
+ public final String getSerialNumber() {
+ return verifier.getSerialNumber();
+ }
+
protected final void validateParameters(CloseableHttpResponse response) {
Header firstHeader = response.getFirstHeader(REQUEST_ID);
if (firstHeader == null) {
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java
index 3c8cac1..3f08f4c 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/cert/CertificatesManager.java
@@ -5,17 +5,20 @@
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
import com.wechat.pay.contrib.apache.httpclient.Credentials;
+import com.wechat.pay.contrib.apache.httpclient.Validator;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
+import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory;
import com.wechat.pay.contrib.apache.httpclient.util.CertSerializeUtil;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateExpiredException;
@@ -25,9 +28,11 @@
import java.util.Base64;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
+import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
@@ -43,8 +48,8 @@
*/
public class CertificatesManager {
- private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class);
protected static final int UPDATE_INTERVAL_MINUTE = 1440;
+ private static final Logger log = LoggerFactory.getLogger(CertificatesManager.class);
/**
* 证书下载地址
*/
@@ -53,7 +58,11 @@ public class CertificatesManager {
private volatile static CertificatesManager instance = null;
private ConcurrentHashMap apiV3Keys = new ConcurrentHashMap<>();
- private ConcurrentHashMap> certificates = new ConcurrentHashMap<>();
+ private HttpProxyFactory proxyFactory;
+ private HttpHost proxy;
+
+ private ConcurrentHashMap> certificates =
+ new ConcurrentHashMap<>();
private ConcurrentHashMap credentialsMap = new ConcurrentHashMap<>();
/**
@@ -61,54 +70,18 @@ public class CertificatesManager {
*/
private ScheduledExecutorService executor;
- /**
- * 内部验签器
- */
- private class DefaultVerifier implements Verifier {
-
- private String merchantId;
-
- private DefaultVerifier(String merchantId) {
- this.merchantId = merchantId;
- }
-
- @Override
- public boolean verify(String serialNumber, byte[] message, String signature) {
- if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
- throw new IllegalArgumentException("serialNumber或message或signature为空");
- }
- BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16);
- ConcurrentHashMap merchantCertificates = certificates.get(merchantId);
- X509Certificate certificate = merchantCertificates.get(serialNumber16Radix);
- if (certificate == null) {
- log.error("商户证书为空,serialNumber:{}", serialNumber);
- return false;
- }
- try {
- Signature sign = Signature.getInstance("SHA256withRSA");
- sign.initVerify(certificate);
- sign.update(message);
- return sign.verify(Base64.getDecoder().decode(signature));
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
- } catch (SignatureException e) {
- throw new RuntimeException("签名验证过程发生了错误", e);
- } catch (InvalidKeyException e) {
- throw new RuntimeException("无效的证书", e);
- }
- }
+ private static final Validator emptyValidator =
+ new Validator() {
+ @Override
+ public boolean validate(CloseableHttpResponse response) throws IOException {
+ return true;
+ };
- @Override
- public X509Certificate getValidCertificate() {
- X509Certificate certificate;
- try {
- certificate = CertificatesManager.this.getLatestCertificate(merchantId);
- } catch (NotFoundException e) {
- throw new NoSuchElementException("没有有效的微信支付平台证书");
- }
- return certificate;
- }
- }
+ @Override
+ public String getSerialNumber() {
+ return "";
+ }
+ };
private CertificatesManager() {
}
@@ -158,6 +131,28 @@ public synchronized void putMerchant(String merchantId, Credentials credentials,
}
}
+ /***
+ * 代理配置
+ *
+ * @param proxy 代理host
+ **/
+ public synchronized void setProxy(HttpHost proxy) {
+ this.proxy = proxy;
+ }
+
+ /**
+ * 设置代理工厂
+ *
+ * @param proxyFactory 代理工厂
+ */
+ public synchronized void setProxyFactory(HttpProxyFactory proxyFactory) {
+ this.proxyFactory = proxyFactory;
+ }
+
+ public synchronized HttpHost resolveProxy() {
+ return Objects.nonNull(proxyFactory) ? proxyFactory.buildHttpProxy() : proxy;
+ }
+
/**
* 停止自动更新平台证书,停止后无法再重新启动
*/
@@ -224,7 +219,6 @@ public Verifier getVerifier(String merchantId) throws NotFoundException {
return new DefaultVerifier(merchantId);
}
-
private void beginScheduleUpdate() {
executor = new SafeSingleScheduleExecutor();
Runnable runnable = () -> {
@@ -253,10 +247,11 @@ private void beginScheduleUpdate() {
*/
private synchronized void downloadAndUpdateCert(String merchantId, Verifier verifier, Credentials credentials,
byte[] apiV3Key) throws HttpCodeException, IOException, GeneralSecurityException {
+ proxy = resolveProxy();
try (CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withCredentials(credentials)
- .withValidator(verifier == null ? (response) -> true
- : new WechatPay2Validator(verifier))
+ .withValidator(verifier == null ? emptyValidator : new WechatPay2Validator(verifier))
+ .withProxy(proxy)
.build()) {
HttpGet httpGet = new HttpGet(CERT_DOWNLOAD_PATH);
httpGet.addHeader(ACCEPT, APPLICATION_JSON.toString());
@@ -311,4 +306,61 @@ private void updateCertificates() {
}
}
}
+
+ /**
+ * 内部验签器
+ */
+ private class DefaultVerifier implements Verifier {
+ private String merchantId;
+
+ private DefaultVerifier(String merchantId) {
+ this.merchantId = merchantId;
+ }
+
+ @Override
+ public boolean verify(String serialNumber, byte[] message, String signature) {
+ if (serialNumber.isEmpty() || message.length == 0 || signature.isEmpty()) {
+ throw new IllegalArgumentException("serialNumber或message或signature为空");
+ }
+ BigInteger serialNumber16Radix = new BigInteger(serialNumber, 16);
+ ConcurrentHashMap merchantCertificates = certificates.get(merchantId);
+ X509Certificate certificate = merchantCertificates.get(serialNumber16Radix);
+ if (certificate == null) {
+ log.error("商户证书为空,serialNumber:{}", serialNumber);
+ return false;
+ }
+ try {
+ Signature sign = Signature.getInstance("SHA256withRSA");
+ sign.initVerify(certificate);
+ sign.update(message);
+ return sign.verify(Base64.getDecoder().decode(signature));
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
+ } catch (SignatureException e) {
+ throw new RuntimeException("签名验证过程发生了错误", e);
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("无效的证书", e);
+ }
+ }
+
+ public X509Certificate getValidCertificate() {
+ X509Certificate certificate;
+ try {
+ certificate = CertificatesManager.this.getLatestCertificate(merchantId);
+ } catch (NotFoundException e) {
+ throw new NoSuchElementException("没有有效的微信支付平台证书");
+ }
+ return certificate;
+ }
+
+ @Override
+ public PublicKey getValidPublicKey() {
+ return getValidCertificate().getPublicKey();
+ }
+
+ @Override
+ public String getSerialNumber() {
+ return getValidCertificate().getSerialNumber().toString(16).toUpperCase();
+ }
+ }
}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java
new file mode 100644
index 0000000..59c7f22
--- /dev/null
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/proxy/HttpProxyFactory.java
@@ -0,0 +1,18 @@
+package com.wechat.pay.contrib.apache.httpclient.proxy;
+
+import org.apache.http.HttpHost;
+
+/**
+ * HttpProxyFactory 代理工厂
+ *
+ * @author ramzeng
+ */
+public interface HttpProxyFactory {
+
+ /**
+ * 构建代理
+ *
+ * @return 代理
+ */
+ HttpHost buildHttpProxy();
+}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/PemUtil.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/PemUtil.java
index f1ca48a..1174d93 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/PemUtil.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/PemUtil.java
@@ -6,6 +6,7 @@
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
@@ -13,6 +14,7 @@
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
@@ -52,6 +54,37 @@ public static PrivateKey loadPrivateKey(InputStream inputStream) {
return loadPrivateKey(privateKey);
}
+ public static PublicKey loadPublicKey(String publicKey) {
+ String keyString = publicKey
+ .replace("-----BEGIN PUBLIC KEY-----", "")
+ .replace("-----END PUBLIC KEY-----", "")
+ .replaceAll("\\s+", "");
+
+ try {
+ return KeyFactory.getInstance("RSA")
+ .generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
+ } catch (NoSuchAlgorithmException e) {
+ throw new UnsupportedOperationException(e);
+ } catch (InvalidKeySpecException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static PublicKey loadPublicKey(InputStream inputStream) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(2048);
+ byte[] buffer = new byte[1024];
+ String publicKey;
+ try {
+ for (int length; (length = inputStream.read(buffer)) != -1; ) {
+ os.write(buffer, 0, length);
+ }
+ publicKey = os.toString("UTF-8");
+ } catch (IOException e) {
+ throw new IllegalArgumentException("无效的公钥", e);
+ }
+ return loadPublicKey(publicKey);
+ }
+
public static X509Certificate loadCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
@@ -66,4 +99,5 @@ public static X509Certificate loadCertificate(InputStream inputStream) {
throw new RuntimeException("无效的证书", e);
}
}
+
}
diff --git a/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/RsaCryptoUtil.java b/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/RsaCryptoUtil.java
index 386657b..fbed9b8 100644
--- a/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/RsaCryptoUtil.java
+++ b/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/RsaCryptoUtil.java
@@ -4,6 +4,7 @@
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Base64;
import javax.crypto.BadPaddingException;
@@ -19,9 +20,21 @@ public class RsaCryptoUtil {
private static final String TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
public static String encryptOAEP(String message, X509Certificate certificate) throws IllegalBlockSizeException {
+ return encrypt(message, certificate, TRANSFORMATION);
+ }
+
+ public static String encrypt(String message, X509Certificate certificate, String transformation) throws IllegalBlockSizeException {
+ return encrypt(message, certificate.getPublicKey(), transformation);
+ }
+
+ public static String encryptOAEP(String message, PublicKey publicKey) throws IllegalBlockSizeException {
+ return encrypt(message, publicKey, TRANSFORMATION);
+ }
+
+ public static String encrypt(String message, PublicKey publicKey, String transformation) throws IllegalBlockSizeException {
try {
- Cipher cipher = Cipher.getInstance(TRANSFORMATION);
- cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
+ Cipher cipher = Cipher.getInstance(transformation);
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] data = message.getBytes(StandardCharsets.UTF_8);
byte[] ciphertext = cipher.doFinal(data);
return Base64.getEncoder().encodeToString(ciphertext);
@@ -36,8 +49,12 @@ public static String encryptOAEP(String message, X509Certificate certificate) th
}
public static String decryptOAEP(String ciphertext, PrivateKey privateKey) throws BadPaddingException {
+ return decrypt(ciphertext, privateKey, TRANSFORMATION);
+ }
+
+ public static String decrypt(String ciphertext, PrivateKey privateKey, String transformation) throws BadPaddingException {
try {
- Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+ Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] data = Base64.getDecoder().decode(ciphertext);
return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/AutoUpdateVerifierTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/AutoUpdateVerifierTest.java
index 990e57d..fb99754 100644
--- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/AutoUpdateVerifierTest.java
+++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/AutoUpdateVerifierTest.java
@@ -39,8 +39,8 @@ public class AutoUpdateVerifierTest {
private static final String serialNumber = "";
private static final String message = "";
private static final String signature = "";
- private static final String mchId = ""; // 商户号
- private static final String mchSerialNo = ""; // 商户证书序列号
+ private static final String merchantId = ""; // 商户号
+ private static final String merchantSerialNumber = ""; // 商户证书序列号
private static final String apiV3Key = ""; // API V3密钥
private CloseableHttpClient httpClient;
private AutoUpdateCertificatesVerifier verifier;
@@ -51,11 +51,11 @@ public void setup() {
//使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
- new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
+ new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
apiV3Key.getBytes(StandardCharsets.UTF_8));
httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
+ .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
}
diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java
index ea8ec37..464af3a 100644
--- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java
+++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/CertificatesManagerTest.java
@@ -10,6 +10,7 @@
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
+import com.wechat.pay.contrib.apache.httpclient.proxy.HttpProxyFactory;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import java.io.File;
import java.io.FileInputStream;
@@ -20,12 +21,14 @@
import java.security.PrivateKey;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -34,29 +37,30 @@ public class CertificatesManagerTest {
// 你的商户私钥
private static final String privateKey = "-----BEGIN PRIVATE KEY-----\n"
+ "-----END PRIVATE KEY-----\n";
- private static final String serialNumber = "";
- private static final String message = "";
- private static final String signature = "";
- private static final String mchId = ""; // 商户号
- private static final String mchSerialNo = ""; // 商户证书序列号
+ private static final String merchantId = ""; // 商户号
+ private static final String merchantSerialNumber = ""; // 商户证书序列号
private static final String apiV3Key = ""; // API V3密钥
- private CloseableHttpClient httpClient;
+ private static final HttpHost proxy = null;
CertificatesManager certificatesManager;
Verifier verifier;
+ private CloseableHttpClient httpClient;
@Before
public void setup() throws Exception {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
+ // 添加代理服务器
+ certificatesManager.setProxy(proxy);
// 向证书管理器增加需要自动更新平台证书的商户信息
- certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
- new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+ certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
+ new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
+ apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
- verifier = certificatesManager.getVerifier(mchId);
+ verifier = certificatesManager.getVerifier(merchantId);
// 构造httpclient
httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
+ .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.build();
}
@@ -134,4 +138,23 @@ public void uploadFileTest() throws Exception {
}
}
}
+
+ @Test
+ public void proxyFactoryTest() {
+ CertificatesManager certificatesManager = CertificatesManager.getInstance();
+ Assert.assertEquals(certificatesManager.resolveProxy(), proxy);
+ certificatesManager.setProxyFactory(new MockHttpProxyFactory());
+ HttpHost httpProxy = certificatesManager.resolveProxy();
+ Assert.assertNotEquals(httpProxy, proxy);
+ Assert.assertEquals(httpProxy.getHostName(), "127.0.0.1");
+ Assert.assertEquals(httpProxy.getPort(), 1087);
+ }
+
+ private static class MockHttpProxyFactory implements HttpProxyFactory {
+
+ @Override
+ public HttpHost buildHttpProxy() {
+ return new HttpHost("127.0.0.1", 1087);
+ }
+ }
}
diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java
index 6179e96..e0cb0d4 100644
--- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java
+++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/HttpClientBuilderTest.java
@@ -13,6 +13,7 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
+import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.function.Consumer;
@@ -25,14 +26,15 @@
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.HttpHost;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class HttpClientBuilderTest {
- private static final String mchId = "1900009191"; // 商户号
- private static final String mchSerialNo = "1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"; // 商户证书序列号
+ private static final String merchantId = "1900009191"; // 商户号
+ private static final String merchantSerialNumber = "1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"; // 商户证书序列号
private static final String requestBody = "{\n"
+ " \"stock_id\": \"9433645\",\n"
+ " \"stock_creator_mchid\": \"1900006511\",\n"
@@ -42,23 +44,24 @@ public class HttpClientBuilderTest {
// 你的商户私钥
private static final String privateKey = "-----BEGIN PRIVATE KEY-----\n"
+ "-----END PRIVATE KEY-----";
- // 你的微信支付平台证书
- private static final String certificate = "-----BEGIN CERTIFICATE-----\n"
- + "-----END CERTIFICATE-----";
+ // 微信支付公钥
+ private static final String wechatPayPublicKeyStr = "-----BEGIN PUBLIC KEY-----\n"
+ + "-----END PUBLIC KEY-----";
+ // 微信支付公钥ID
+ private static final String wechatpayPublicKeyId = "PUB_KEY_ID_";
private CloseableHttpClient httpClient;
+ private static final HttpHost proxy = null;
+
@Before
public void setup() {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
- X509Certificate wechatPayCertificate = PemUtil.loadCertificate(
- new ByteArrayInputStream(certificate.getBytes(StandardCharsets.UTF_8)));
-
- ArrayList listCertificates = new ArrayList<>();
- listCertificates.add(wechatPayCertificate);
+ PublicKey wechatPayPublicKey = PemUtil.loadPublicKey(wechatPayPublicKeyStr);
httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
- .withWechatPay(listCertificates)
+ .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
+ .withWechatPay(wechatpayPublicKeyId, wechatPayPublicKey)
+ .withProxy(proxy)
.build();
}
@@ -79,18 +82,6 @@ public void getCertificateTest() throws Exception {
}
- @Test
- public void getCertificatesWithoutCertTest() throws Exception {
- PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
-
- httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
- .withValidator(response -> true)
- .build();
-
- getCertificateTest();
- }
-
@Test
public void postNonRepeatableEntityTest() throws IOException {
HttpPost httpPost = new HttpPost(
diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/NotificationHandlerTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/NotificationHandlerTest.java
index 3ca01ea..5905036 100644
--- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/NotificationHandlerTest.java
+++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/NotificationHandlerTest.java
@@ -1,6 +1,8 @@
package com.wechat.pay.contrib.apache.httpclient;
+import com.wechat.pay.contrib.apache.httpclient.auth.MixVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.PublicKeyVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
@@ -10,6 +12,8 @@
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
+import java.security.PublicKey;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -18,15 +22,19 @@ public class NotificationHandlerTest {
private static final String privateKey = "-----BEGIN PRIVATE KEY-----\n"
+ "-----END PRIVATE KEY-----\n"; // 商户私钥
- private static final String mchId = ""; // 商户号
- private static final String mchSerialNo = ""; // 商户证书序列号
+ private static final String merchantId = ""; // 商户号
+ private static final String merchantSerialNumber = ""; // 商户证书序列号
private static final String apiV3Key = ""; // apiV3密钥
private static final String wechatPaySerial = ""; // 平台证书序列号
+ private static final String wechatPayPublicKeyStr = "-----BEGIN PUBLIC KEY-----\n"
+ + "-----END PUBLIC KEY-----"; // 微信支付公钥
+ private static final String wechatpayPublicKeyId = "PUB_KEY_ID_"; // 微信支付公钥ID
private static final String nonce = ""; // 请求头Wechatpay-Nonce
private static final String timestamp = "";// 请求头Wechatpay-Timestamp
private static final String signature = "";// 请求头Wechatpay-Signature
private static final String body = ""; // 请求体
- private Verifier verifier; // 验签器
+ private Verifier publicKeyVerifier; // 微信支付公钥验签器
+ private Verifier certificateVerifier; // 平台证书验签器
private static CertificatesManager certificatesManager; // 平台证书管理器
@Before
@@ -35,22 +43,59 @@ public void setup() throws Exception {
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
- certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
- new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+ certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
+ new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
+ apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
- verifier = certificatesManager.getVerifier(mchId);
+ certificateVerifier = certificatesManager.getVerifier(merchantId);
+ // 创建公钥验签器
+ PublicKey wechatPayPublicKey = PemUtil.loadPublicKey(wechatPayPublicKeyStr);
+ publicKeyVerifier = new PublicKeyVerifier(wechatpayPublicKeyId, wechatPayPublicKey);
}
- @Test
- public void notificationHandlerTest() throws Exception {
- // 构建request,传入必要参数
- NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
+ private NotificationRequest buildNotificationRequest() {
+ return new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
.withNonce(nonce)
.withTimestamp(timestamp)
.withSignature(signature)
.withBody(body)
.build();
- NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void handleNotificationWithPublicKeyVerifier() throws Exception {
+ NotificationRequest request = buildNotificationRequest();
+
+ // 使用微信支付公钥验签器:适用于已经完成「平台证书」-->「微信支付公钥」迁移的商户以及新申请的商户
+ NotificationHandler handler = new NotificationHandler(certificateVerifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
+
+ // 验签和解析请求体
+ Notification notification = handler.parse(request);
+ Assert.assertNotNull(notification);
+ System.out.println(notification.toString());
+ }
+
+ @Test
+ public void handleNotificationWithMixVerifier() throws Exception {
+ NotificationRequest request = buildNotificationRequest();
+
+ // 使用混合验签器:适用于正在进行「平台证书」-->「微信支付公钥」迁移的商户
+ Verifier mixVerifier = new MixVerifier((PublicKeyVerifier) publicKeyVerifier, certificateVerifier);
+ NotificationHandler handler = new NotificationHandler(mixVerifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
+
+ // 验签和解析请求体
+ Notification notification = handler.parse(request);
+ Assert.assertNotNull(notification);
+ System.out.println(notification.toString());
+ }
+
+ @Test
+ public void handleNotificationWithCertificateVerifier() throws Exception {
+ NotificationRequest request = buildNotificationRequest();
+
+ // 使用平台证书验签器:适用于尚未开始「平台证书」-->「微信支付公钥」迁移的旧商户
+ NotificationHandler handler = new NotificationHandler(certificateVerifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
+
// 验签和解析请求体
Notification notification = handler.parse(request);
Assert.assertNotNull(notification);
diff --git a/src/test/java/com/wechat/pay/contrib/apache/httpclient/RsaCryptoTest.java b/src/test/java/com/wechat/pay/contrib/apache/httpclient/RsaCryptoTest.java
index 19c00bf..15e5aa7 100644
--- a/src/test/java/com/wechat/pay/contrib/apache/httpclient/RsaCryptoTest.java
+++ b/src/test/java/com/wechat/pay/contrib/apache/httpclient/RsaCryptoTest.java
@@ -5,6 +5,7 @@
import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.apache.http.entity.ContentType.APPLICATION_JSON;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
@@ -14,9 +15,13 @@
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
+import java.util.Base64;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
@@ -29,13 +34,59 @@
public class RsaCryptoTest {
- private static final String mchId = ""; // 商户号
- private static final String mchSerialNo = ""; // 商户证书序列号
+ private static final String merchantId = ""; // 商户号
+ private static final String merchantSerialNumber = ""; // 商户证书序列号
private static final String apiV3Key = ""; // API V3密钥
- private static final String privateKey = "-----BEGIN PRIVATE KEY-----\n"
- + "-----END PRIVATE KEY-----\n"; // 商户API V3私钥
+ private static final String privateKey = ""; // 商户API V3私钥
private static final String wechatPaySerial = ""; // 平台证书序列号
+ private static final String certForEncrypt = "-----BEGIN CERTIFICATE-----\n" +
+ "MIIC4jCCAcoCCQCtzUA6NgI3njANBgkqhkiG9w0BAQsFADAzMQswCQYDVQQGEwJD\n" +
+ "TjERMA8GA1UECAwIc2hhbmdoYWkxETAPBgNVBAcMCHNoYW5naGFpMB4XDTIyMDUw\n" +
+ "OTIwMjE1NloXDTIzMDUwOTIwMjE1NlowMzELMAkGA1UEBhMCQ04xETAPBgNVBAgM\n" +
+ "CHNoYW5naGFpMREwDwYDVQQHDAhzaGFuZ2hhaTCCASIwDQYJKoZIhvcNAQEBBQAD\n" +
+ "ggEPADCCAQoCggEBALMGZq4BnKaX/VXeg9rLkpE7LqQ5uxgIfKMKSvLzCHA3ZfOR\n" +
+ "p9fl8DtD0/svTUJ0JNv/pFRjfNEmlzqSmAW922yBc4uGkDdqrgHmt4/fqsOXcdLt\n" +
+ "foL5txTdgYutq/127HOhxwixAlJA0PHk6QMuLmG4GN+dwQHWAtQROufgupXoPe6y\n" +
+ "B+w4y3GaCLXIoqgHJQDePFy4sYkNAeSlHFvomPz4RAivPemEiTh2AmJ+RTZa3qT7\n" +
+ "8ZzJNqIM0UKHgcPSsMGTzchC7sV9WIDbQZseflz2ZDJIepJeGq/4TSIXBcyd1yUY\n" +
+ "GWfQRb/l640C3Izj3nililXWFLCWW5dKBnUGqdMCAwEAATANBgkqhkiG9w0BAQsF\n" +
+ "AAOCAQEAo4LkShFg+btEjQUxuShD7SQeNh2DDvdCtEQo5IUY7wtgm95fDGgR1QTA\n" +
+ "9IElN0EpiyvHnPlsjisl8heCL/OnTvrvxJyOp64AiPO6l9j7/nbf9cMHXPOaZODa\n" +
+ "hS4rdokqUAswRA7wkiK6+hOPw/90+P7EPw6xCNRYTfl2ii5jirisrkc6iOW2nbUd\n" +
+ "MjFd3gRGBM/ks3oltGbQbTOwntrAb7wy5EYakdZoKix6CQlqZIdbDXJBEgdXPftt\n" +
+ "80ReqYWTWYyffHCuALMzmFw0fd6gFb/md2oIb13tcKCwiAe1mQmnudRsDH5b5Zps\n" +
+ "iSuewmex8WO7a4/lc2WWKpSb/8JwNQ==\n" +
+ "-----END CERTIFICATE-----"; // 用于测试加密功能的证书
+ private static final String privateKeyForDecrypt = "-----BEGIN PRIVATE KEY-----\n" +
+ "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCzBmauAZyml/1V\n" +
+ "3oPay5KROy6kObsYCHyjCkry8whwN2XzkafX5fA7Q9P7L01CdCTb/6RUY3zRJpc6\n" +
+ "kpgFvdtsgXOLhpA3aq4B5reP36rDl3HS7X6C+bcU3YGLrav9duxzoccIsQJSQNDx\n" +
+ "5OkDLi5huBjfncEB1gLUETrn4LqV6D3usgfsOMtxmgi1yKKoByUA3jxcuLGJDQHk\n" +
+ "pRxb6Jj8+EQIrz3phIk4dgJifkU2Wt6k+/GcyTaiDNFCh4HD0rDBk83IQu7FfViA\n" +
+ "20GbHn5c9mQySHqSXhqv+E0iFwXMndclGBln0EW/5euNAtyM4954pYpV1hSwlluX\n" +
+ "SgZ1BqnTAgMBAAECggEAUjhnYhVFb9GwPQbEAfGq796BblVBUylarLqmb2wk/PzE\n" +
+ "axgDQQnOyjk9m0g/MH0NDKkdPNCwW5JgtDrtbP2kT/IoMfVsOLdbEW538bDkyY29\n" +
+ "bgU7LEYpyoBs5cyuh+tdb0HmmlxJV6ODEwVx6s8D6EdXzSOzp/c1N1Zuel5g80V/\n" +
+ "oE5pTb6XBObrCq4ZmMT5y59pSroZDV+RlYZqYtXeCdni+9jzVb+7AM50wqp3D17M\n" +
+ "P9OZnYVyiKS9GEM68klXt3dCnp5P80WVLLupin3DODGdkU0kDFWZE+Hw8Xype5iP\n" +
+ "jgJMZwieOsniveAsIjwtRjh0yZ6xJe47G1JOGppK8QKBgQDuM5eyIJhhwkxbQ1ov\n" +
+ "PbRQbeHrdWUuWuruggg2BMXI3RQH5yELyysOY7rrxSob0L90ww7QLtvaw4GNAFsV\n" +
+ "/OpXs1bkn3QD3lCh4jskbe816iHnYpLKcdkewcIove3wVAaT5/VYeyW9R1mXZLFr\n" +
+ "sXAYef1Fys1yg6eM8GuiFGu7yQKBgQDAZtue4T1JNR4IEMXU4wRUmU2itu+7A2W6\n" +
+ "GskyKaXNvKt0g8ZawDIYEl+B35mRJ29O+8rGKIpqqMEfy+En9/aphouMu9S0cFfS\n" +
+ "n/H1M1B9cfscqyXnS/Ed1kCC9SlfkRXuJ+HhZQ7Zt95vHwf2ugYeg6GDtghC4JHA\n" +
+ "NIdntlOOuwKBgQCi8IvN/1n9VUmiDBp+wji7485soGtMIEkgSbaQLQeWdRQkq8gB\n" +
+ "J0MWnsXYTZCWYl704hEZ+1PM+3t9Fkc4bT9oKndAAIr9sm95rSVDsCe3u6bhfp5m\n" +
+ "+SXKUkQcVn+SrAer2ToNAoA4T7xLQUfUIRZKx/embCnJMaHFWRhnUIy5cQKBgQCG\n" +
+ "tHz3E8OQybuo8fVQQ1D42gxc66+UQ6CpV6+di0Mmc/2mqcvqJb3s1JBBoYcm9XEc\n" +
+ "33Tsn92pJ1VvKZMOJLFxp110vt0BJ9aVBJ6mibLE4VRqkfkLo0PBHAw2o+a/nhi4\n" +
+ "kPu4jsSC8hStwBAXUc6O9qHSUVQfXpMs+poCpsiBmQKBgQDO+B/xX6V6GQIrPgiF\n" +
+ "nKpSi566ouXcNxiMIb8w7nu4r/0mJ91roVD0N1TyCOVKTrY/R/4KsQV4pp2bQfV7\n" +
+ "3tYnrSVgBhPHfWkWQG+7sUXWRR5/c8jszKM+no/bsxmqsAJdK2ih/crHD7XrGgXL\n" +
+ "XWU1WCYDnWGKm3byXlLY1tFO/Q==\n" +
+ "-----END PRIVATE KEY-----"; // 用于测试解密功能的私钥
+
private CloseableHttpClient httpClient;
private CertificatesManager certificatesManager;
private Verifier verifier;
@@ -46,13 +97,14 @@ public void setup() throws Exception {
// 获取证书管理器实例
certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
- certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
- new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
+ certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
+ new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
+ apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
- verifier = certificatesManager.getVerifier(mchId);
+ verifier = certificatesManager.getVerifier(merchantId);
httpClient = WechatPayHttpClientBuilder.create()
- .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
- .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(mchId)))
+ .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
+ .withValidator(new WechatPay2Validator(certificatesManager.getVerifier(merchantId)))
.build();
}
@@ -64,18 +116,60 @@ public void after() throws IOException {
@Test
public void encryptTest() throws Exception {
String text = "helloworld";
- String ciphertext = RsaCryptoUtil
- .encryptOAEP(text, verifier.getValidCertificate());
+ String ciphertext = RsaCryptoUtil.encryptOAEP(text, verifier.getValidPublicKey());
System.out.println("ciphertext: " + ciphertext);
}
+ @Test
+ public void encryptWithPkcs1TransformationTest() throws Exception {
+ String text = "helloworld";
+ String transformation = "RSA/ECB/PKCS1Padding";
+ String encryptedText = RsaCryptoUtil.encrypt(text, PemUtil.loadCertificate(new ByteArrayInputStream(certForEncrypt.getBytes())), transformation);
+ //utilize the standard lib to verify the correctness of the encrypted result.
+ Cipher cipher = Cipher.getInstance(transformation);
+ cipher.init(Cipher.DECRYPT_MODE, PemUtil.loadPrivateKey(privateKeyForDecrypt));
+ String secretText = new String((cipher.doFinal(Base64.getDecoder().decode(encryptedText))));
+ assert(text.equals(secretText));
+ }
+
+ @Test
+ public void decryptWithPkcs1TransformationTest() throws BadPaddingException {
+ String encryptedText = "lmkkdBz5CH4Zk6KIEzbyenf+WtKe8nuU9j+t8HonOm4v1OfLRiYhvdcequOSuaz5vjdpX434XjV9Q5LGC8aOC" +
+ "DZs/8LoyR3m/6JpYa0nkGOh6Le2JvSPNXlSq9HUEoElBJD5KsxbsRoif0kuioBGSKvKB0xwIvVtn+S0H2GYya7TC1L/ddhGhI/yx" +
+ "ZgS/TI/Ppej3OzNmu0xA5RjpDR4rGAUrLvV7y/aM4mCN6WOaO6YsAnlGoSbK+P1sepeb0sCaJMClqbLE0Eoz2ve9FQ30w1Vgi5F0" +
+ "2rpDwcZO8EXAkub0L12BN4QWBNK8FaKlS4UZPAGAwutLK6Gylig54Quig==";
+ String transformation = "RSA/ECB/PKCS1Padding";
+ assertEquals("helloworld", RsaCryptoUtil.decrypt(encryptedText, PemUtil.loadPrivateKey(privateKeyForDecrypt), transformation));
+ }
+
+ @Test
+ public void encryptWithOaepTransformationTest() throws Exception {
+ String text = "helloworld";
+ String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+ String encryptedText = RsaCryptoUtil.encrypt(text, PemUtil.loadCertificate(new ByteArrayInputStream(certForEncrypt.getBytes())), transformation);
+ //utilize the standard lib to verify the correctness of the encrypted result.
+ Cipher cipher = Cipher.getInstance(transformation);
+ cipher.init(Cipher.DECRYPT_MODE, PemUtil.loadPrivateKey(privateKeyForDecrypt));
+ String secretText = new String((cipher.doFinal(Base64.getDecoder().decode(encryptedText))));
+ assert(text.equals(secretText));
+ }
+
+ @Test
+ public void decryptWithOaepTransformationTest() throws BadPaddingException {
+ String encryptedText = "FJ8/0ubyxnMZ0GN2YEUgJgDVPCwMrsTKuLFxycI3jvOAcVTDEEermn2F7+cUtmCYvD2TkHUMHvWeJB6/nSPBD" +
+ "eGuxA4bCr584h2w9bRvVrwtQlnv1HpF2WRdGAuPcgrQcZvMpiH2ysxgPrGPMs9WOr8etxf1FifI0DkMb6w7wl2BDPPK+RfRdZq7T" +
+ "9KBtH2IllVTLUbRSqDGIctgIxB7RMqd3s/eK0p2Qjui8AVgP4j5Spq6JjITgKn0VDOO4JwzU8Zl++BwveoJMkTN150XF5ot+ruZv" +
+ "lNgjP1Hez0/rFxY7gQvxrSDwgL5A9up6JRI741psfs/3HrzBJOBdvO73A==";
+ String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+ assertEquals("helloworld", RsaCryptoUtil.decrypt(encryptedText, PemUtil.loadPrivateKey(privateKeyForDecrypt), transformation));
+ }
+
@Test
public void postEncryptDataTest() throws Exception {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/smartguide/guides");
String text = "helloworld";
- String ciphertext = RsaCryptoUtil
- .encryptOAEP(text, verifier.getValidCertificate());
+ String ciphertext = RsaCryptoUtil.encryptOAEP(text, verifier.getValidPublicKey());
String data = "{\n"
+ " \"store_id\" : 1234,\n"