无关风月
2025-04-28 e7e7a3a2e5cd9aefa3e71dd05bbc5f6f4b88a1c0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
package com.dsh.activity.util.wx;
 
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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.util.PemUtil;
import lombok.SneakyThrows;
import org.apache.http.impl.client.CloseableHttpClient;
 
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
/**
 * 微信支付V3 API 签名验证工具类
 */
public class WXPaySignatureCertificateUtil {
 
    // 保存微信平台证书
    private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();
    // 缓存公钥,避免频繁IO操作
    private static PublicKey cachedPublicKey = null;
    // 缓存私钥,避免频繁IO操作
    private static PrivateKey cachedPrivateKey = null;
 
    /**
     * 获取HTTP客户端,用于微信支付API请求
     */
    public static CloseableHttpClient getWechatPayClient() throws IOException {
        PrivateKey merchantPrivateKey = getPrivateKey();
        // 构建支付客户端
        return WechatPayHttpClientBuilder.create()
                .withMerchant(WxV3PayConfig.Mch_ID, WxV3PayConfig.mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(getVerifier()))
                .build();
    }
 
    /**
     * 获取自动更新的证书验证器
     */
    public static AutoUpdateCertificatesVerifier getVerifier() {
        String mchSerialNo = WxV3PayConfig.mchSerialNo;
        AutoUpdateCertificatesVerifier verifier = verifierMap.get(mchSerialNo);
 
        if (verifier == null) {
            synchronized (WXPaySignatureCertificateUtil.class) {
                verifier = verifierMap.get(mchSerialNo);
                if (verifier == null) {
                    try {
                        PrivateKey privateKey = getPrivateKey();
                        PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey);
                        WechatPay2Credentials credentials = new WechatPay2Credentials(WxV3PayConfig.Mch_ID, signer);
                        verifier = new AutoUpdateCertificatesVerifier(
                                credentials, WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
                        verifierMap.put(mchSerialNo, verifier);
                    } catch (Exception e) {
                        throw new RuntimeException("初始化证书验证器失败", e);
                    }
                }
            }
        }
 
        return verifier;
    }
 
    /**
     * 使用商户私钥对数据进行签名
     *
     * @param message 待签名数据
     * @return Base64编码的签名结果
     */
    public static String sign(String message) {
        try {
            PrivateKey privateKey = getPrivateKey();
            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(privateKey);
            signature.update(message.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(signature.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名失败", e);
        }
    }
 
    /**
     * 使用微信支付平台公钥验证签名
     *
     * @param message 原始消息
     * @param signature Base64编码的签名
     * @return 验证结果
     */
    public static boolean verifySign(String message, String signature) {
        try {
            PublicKey publicKey = getWechatPublicKey();
            Signature sig = Signature.getInstance("SHA256withRSA");
            sig.initVerify(publicKey);
            sig.update(message.getBytes(StandardCharsets.UTF_8));
            return sig.verify(Base64.getDecoder().decode(signature));
        } catch (Exception e) {
            throw new RuntimeException("验签失败", e);
        }
    }
 
    /**
     * 验证微信支付通知消息的签名
     *
     * @param request HTTP请求
     * @param body 请求体内容
     * @return 验证结果
     */
    public static boolean verifyNotify(HttpServletRequest request, String body) {
        try {
            // 获取必要的HTTP头信息
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
 
            // 拼接验签字符串
            String message = Stream.of(timestamp, nonce, body)
                    .collect(Collectors.joining("\n", "", "\n"));
 
            // 使用自动更新的证书验证器进行验证
            return getVerifier().verify(serialNo, message.getBytes(StandardCharsets.UTF_8), signature);
        } catch (Exception e) {
            throw new RuntimeException("验证微信支付通知签名失败", e);
        }
    }
 
    /**
     * APP支付签名
     */
    public static String appPaySign(String timestamp, String nonceStr, String prepayId) {
        String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        return sign(message);
    }
 
    /**
     * JSAPI支付签名
     */
    public static String jsApiPaySign(String timestamp, String nonceStr, String prepayId) {
        String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, "prepay_id="+prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        return sign(message);
    }
 
    /**
     * 构建App支付参数
     */
    public static Map<String, String> buildAppPayParams(String prepayId) {
        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = generateNonceStr();
            String sign = appPaySign(timestamp, nonceStr, prepayId);
 
            Map<String, String> params = new HashMap<>();
            params.put("appid", WxV3PayConfig.APP_ID);
            params.put("partnerid", WxV3PayConfig.Mch_ID);
            params.put("prepayid", prepayId);
            params.put("package", "Sign=WXPay");
            params.put("noncestr", nonceStr);
            params.put("timestamp", timestamp);
            params.put("sign", sign);
 
            return params;
        } catch (Exception e) {
            throw new RuntimeException("构建App支付参数失败", e);
        }
    }
 
    /**
     * 构建JSAPI支付参数
     */
    public static Map<String, String> buildJsApiPayParams(String prepayId) {
        try {
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = generateNonceStr();
            String sign = jsApiPaySign(timestamp, nonceStr, prepayId);
 
            Map<String, String> params = new HashMap<>();
            params.put("appId", WxV3PayConfig.APP_ID);
            params.put("timeStamp", timestamp);
            params.put("nonceStr", nonceStr);
            params.put("package", "prepay_id=" + prepayId);
            params.put("signType", "RSA");
            params.put("paySign", sign);
 
            return params;
        } catch (Exception e) {
            throw new RuntimeException("构建JSAPI支付参数失败", e);
        }
    }
 
    /**
     * 生成随机字符串
     */
    private static String generateNonceStr() {
        return java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
    }
 
    /**
     * 获取商户私钥
     */
    public static PrivateKey getPrivateKey() {
        if (cachedPrivateKey != null) {
            return cachedPrivateKey;
        }
 
        try {
            String filePath = "D:\\玩湃v3参数\\apiclient_key.pem";
            String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
 
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
 
            KeyFactory kf = KeyFactory.getInstance("RSA");
            cachedPrivateKey = kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
            return cachedPrivateKey;
        } catch (IOException e) {
            throw new RuntimeException("读取私钥文件失败", e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式", e);
        }
    }
 
    /**
     * 获取微信支付平台公钥
     */
    public static PublicKey getWechatPublicKey() {
        if (cachedPublicKey != null) {
            return cachedPublicKey;
        }
 
        try {
            // 使用验证器获取最新的证书
            X509Certificate certificate = getVerifier().getValidCertificate();
            cachedPublicKey = certificate.getPublicKey();
            return cachedPublicKey;
        } catch (Exception e) {
            throw new RuntimeException("获取微信平台公钥失败", e);
        }
    }
 
    /**
     * 从PEM格式文件加载公钥
     */
    public static PublicKey loadPublicKeyFromFile(String filePath) {
        try {
            String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8);
 
            String publicKeyPEM = content
                    .replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");
 
            byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return keyFactory.generatePublic(new X509EncodedKeySpec(encoded));
        } catch (Exception e) {
            throw new RuntimeException("加载公钥文件失败", e);
        }
    }
 
    /**
     * 从证书文件加载公钥
     */
    public static PublicKey loadPublicKeyFromCert(String filePath) {
        try (FileInputStream inputStream = new FileInputStream(filePath)) {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream);
            return cert.getPublicKey();
        } catch (Exception e) {
            throw new RuntimeException("加载证书文件失败", e);
        }
    }
}