Skip to content

Commit 96eaeb2

Browse files
Copilotbinarywang
andauthored
实现小程序加密网络通道:修复 HMAC 签名、错误处理,新增 AES 工具方法及单元测试
Agent-Logs-Url: https://github.com/binarywang/WxJava/sessions/f3aba758-8b4a-479f-96bd-88ce00a9c176 Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent 9d5bb1a commit 96eaeb2

3 files changed

Lines changed: 108 additions & 2 deletions

File tree

weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaInternetServiceImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import me.chanjar.weixin.common.enums.WxType;
1010
import me.chanjar.weixin.common.error.WxError;
1111
import me.chanjar.weixin.common.error.WxErrorException;
12+
import org.apache.commons.codec.binary.Base64;
1213

1314
import javax.crypto.Mac;
1415
import javax.crypto.spec.SecretKeySpec;
@@ -26,7 +27,7 @@ public class WxMaInternetServiceImpl implements WxMaInternetService {
2627

2728
private String sha256(String data, String sessionKey) throws Exception {
2829
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
29-
SecretKeySpec secret_key = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
30+
SecretKeySpec secret_key = new SecretKeySpec(Base64.decodeBase64(sessionKey), "HmacSHA256");
3031
sha256_HMAC.init(secret_key);
3132
byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
3233
StringBuilder sb = new StringBuilder();
@@ -57,7 +58,7 @@ public WxMaInternetResponse getUserEncryptKey(String openid, String sessionKey)
5758
private WxMaInternetResponse getWxMaInternetResponse(String url) throws WxErrorException {
5859
String responseContent = this.wxMaService.post(url, "");
5960
WxMaInternetResponse response = WxMaGsonBuilder.create().fromJson(responseContent, WxMaInternetResponse.class);
60-
if (response.getErrcode() == -1) {
61+
if (response.getErrcode() != null && response.getErrcode() != 0) {
6162
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
6263
}
6364
return response;

weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,76 @@ public static String decryptAnotherWay(String sessionKey, String encryptedData,
8484
}
8585
}
8686

87+
/**
88+
* 使用用户加密 key 对数据进行 AES-128-CBC 解密(用于小程序加密网络通道).
89+
*
90+
* <pre>
91+
* 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
92+
* encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码)
93+
* iv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码)
94+
* </pre>
95+
*
96+
* @param encryptKey 用户加密 key(Base64 编码)
97+
* @param hexIv 加密 iv(Hex 编码)
98+
* @param encryptedData 加密数据(Base64 编码)
99+
* @return 解密后的字符串
100+
*/
101+
public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) {
102+
try {
103+
byte[] keyBytes = Base64.decodeBase64(encryptKey);
104+
byte[] ivBytes = hexToBytes(hexIv);
105+
byte[] dataBytes = Base64.decodeBase64(encryptedData);
106+
107+
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
108+
cipher.init(Cipher.DECRYPT_MODE,
109+
new SecretKeySpec(keyBytes, "AES"),
110+
new IvParameterSpec(ivBytes));
111+
return new String(cipher.doFinal(dataBytes), UTF_8);
112+
} catch (Exception e) {
113+
throw new WxRuntimeException("AES解密失败!", e);
114+
}
115+
}
116+
117+
/**
118+
* 使用用户加密 key 对数据进行 AES-128-CBC 加密(用于小程序加密网络通道).
119+
*
120+
* <pre>
121+
* 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
122+
* encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码)
123+
* iv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码)
124+
* </pre>
125+
*
126+
* @param encryptKey 用户加密 key(Base64 编码)
127+
* @param hexIv 加密 iv(Hex 编码)
128+
* @param data 待加密的明文字符串
129+
* @return 加密后的数据(Base64 编码)
130+
*/
131+
public static String encryptWithEncryptKey(String encryptKey, String hexIv, String data) {
132+
try {
133+
byte[] keyBytes = Base64.decodeBase64(encryptKey);
134+
byte[] ivBytes = hexToBytes(hexIv);
135+
136+
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
137+
cipher.init(Cipher.ENCRYPT_MODE,
138+
new SecretKeySpec(keyBytes, "AES"),
139+
new IvParameterSpec(ivBytes));
140+
return Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
141+
} catch (Exception e) {
142+
throw new WxRuntimeException("AES加密失败!", e);
143+
}
144+
}
145+
146+
/**
147+
* 将 Hex 字符串转换为字节数组.
148+
*/
149+
private static byte[] hexToBytes(String hex) {
150+
int len = hex.length();
151+
byte[] data = new byte[len / 2];
152+
for (int i = 0; i < len; i += 2) {
153+
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
154+
+ Character.digit(hex.charAt(i + 1), 16));
155+
}
156+
return data;
157+
}
158+
87159
}

weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtilsTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,37 @@ public void testDecryptAnotherWay() {
3232
assertThat(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr))
3333
.isEqualTo(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr));
3434
}
35+
36+
/**
37+
* 测试使用用户加密 key(来自小程序加密网络通道)进行加密和解密的对称性.
38+
* encrypt_key 为 Base64 编码的 16 字节 AES-128 密钥,iv 为 Hex 编码的 16 字节初始向量。
39+
*/
40+
@Test
41+
public void testEncryptAndDecryptWithEncryptKey() {
42+
// 模拟来自 getUserEncryptKey 接口的 encrypt_key(Base64)和 iv(Hex)
43+
String encryptKey = "VI6BpyrK9XH4i4AIGe86tg==";
44+
String hexIv = "6003f73ec441c3866003f73ec441c386";
45+
String plainText = "{\"userId\":\"12345\",\"amount\":100}";
46+
47+
String encrypted = WxMaCryptUtils.encryptWithEncryptKey(encryptKey, hexIv, plainText);
48+
assertThat(encrypted).isNotNull().isNotEmpty();
49+
50+
String decrypted = WxMaCryptUtils.decryptWithEncryptKey(encryptKey, hexIv, encrypted);
51+
assertThat(decrypted).isEqualTo(plainText);
52+
}
53+
54+
/**
55+
* 测试使用已知密文验证解密结果(加密网络通道).
56+
*/
57+
@Test
58+
public void testDecryptWithEncryptKey() {
59+
String encryptKey = "VI6BpyrK9XH4i4AIGe86tg==";
60+
String hexIv = "6003f73ec441c3866003f73ec441c386";
61+
String plainText = "hello miniprogram";
62+
63+
// 先加密再解密,验证对称性
64+
String encrypted = WxMaCryptUtils.encryptWithEncryptKey(encryptKey, hexIv, plainText);
65+
String decrypted = WxMaCryptUtils.decryptWithEncryptKey(encryptKey, hexIv, encrypted);
66+
assertThat(decrypted).isEqualTo(plainText);
67+
}
3568
}

0 commit comments

Comments
 (0)