WechatSignatureUtils.java
5.34 KB
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
package com.diligrp.cashier.pipeline.util;
import com.diligrp.cashier.shared.util.ObjectUtils;
import com.diligrp.cashier.shared.util.RandomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 微信签名验签工具类
*/
public class WechatSignatureUtils {
private static final Logger LOG = LoggerFactory.getLogger(WechatSignatureUtils.class);
// 常用字符常量
private static String EMPTY_STR = "";
private static String ENTER_STR = "\n";
// 微信认证类型 - 请求时使用
private static final String TOKEN_FORMAT = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%s\",serial_no=\"%s\",signature=\"%s\"";
/**
* 获取微信支付的认证信息和签名信息
*
* @param mchId - 服务商商户号
* @param method - 请求方法: GET/POST/PUT/**
* @param uri - 请求URL
* @param payload - POST请求消息体
* @param privateKey - Base64编码的私钥
* @param keySerialNo - 商户API证书(公钥)序列号
* @return 认证信息和签名信息,通过Http Header传输,格式 Authorization: 认证类型 签名信息
* @throws Exception 签名异常
*/
public static String authorization(String mchId, String method, String uri, String payload,
PrivateKey privateKey, String keySerialNo) throws Exception {
String nonce = RandomUtils.randomString(32);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// No need handle uri here, the caller ensure the normal and valid URL
// URL url = new URL("https://www.diligrp.com/ddd?aaa=x"); url.getPath() = /ddd url.getQuery() = "aa=x"
String message = Stream.of(method, uri, timestamp, nonce, payload).map(o -> o == null ? EMPTY_STR : o)
.collect(Collectors.joining(ENTER_STR, EMPTY_STR, ENTER_STR));
LOG.debug("\n---Wechat Platform Sign Param---\n{}\n--------------------------------", message);
String signature = signature(message, privateKey);
return String.format(TOKEN_FORMAT, mchId, nonce, timestamp, keySerialNo, signature);
}
/**
* @see WechatSignatureUtils#authorization(String, String, String, String, PrivateKey, String)
*/
public static String authorization(String mchId, String method, String uri, PrivateKey privateKey,
String keySerialNo) throws Exception {
return authorization(mchId, method, uri, null, privateKey, keySerialNo);
}
/**
* 微信支付签名
*
* @param message - 被签名字符串
* @param privateKey - 服务商私钥
* @return 签名数据
* @throws Exception 签名异常
*/
public static String signature(String message, PrivateKey privateKey) throws Exception {
byte[] packet = message.getBytes(StandardCharsets.UTF_8);
Signature signature = Signature.getInstance(WechatConstants.SIGN_ALGORITHM);
signature.initSign(privateKey);
signature.update(packet);
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 微信支付数据验签
*
* @param payload - POST请求消息体
* @param timestamp - 时间戳
* @param nonce - 随机字符串
* @param sign - 签名数据
* @param publicKey - 微信支付平台公钥
* @return 是否验签成功
* @throws Exception 验签异常
*/
public static boolean verify(String payload, String timestamp, String nonce, String sign, PublicKey publicKey) throws Exception {
byte[] signBytes = Base64.getDecoder().decode(sign);
StringBuilder message = new StringBuilder();
message.append(timestamp).append("\n").append(nonce).append("\n").append(payload).append("\n");
byte[] packet = message.toString().getBytes(StandardCharsets.UTF_8);
Signature signature = Signature.getInstance(WechatConstants.SIGN_ALGORITHM);
signature.initVerify(publicKey);
signature.update(packet);
return signature.verify(signBytes);
}
/**
* 报文解密, 证书和微信回调时使用
*
* @param payload - Base64编码的待解密的消息
* @param nonce - 解密使用随机字符串
* @param extraData - 解密使用的附加数据包, 可为空
* @param apiV3Key - 微信平台apiV3Key
* @return 消息明文
* @throws Exception 解密异常
*/
public static String decrypt(String payload, String nonce, String extraData, SecretKeySpec apiV3Key) throws Exception {
byte[] packet = Base64.getDecoder().decode(payload);
byte[] nonceBytes = nonce.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(WechatConstants.AESGCM_ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(WechatConstants.TAG_LENGTH_BIT, nonceBytes);
cipher.init(Cipher.DECRYPT_MODE, apiV3Key, spec);
if (ObjectUtils.isNotEmpty(extraData)) {
cipher.updateAAD(extraData.getBytes(StandardCharsets.UTF_8));
}
return new String(cipher.doFinal(packet), StandardCharsets.UTF_8);
}
}