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(); + + + /** + * 获取微信支付序列号,针对不同的验签模式有所区别: + * + * @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"