Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import me.chanjar.weixin.common.enums.WxType;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.codec.binary.Base64;

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

private String sha256(String data, String sessionKey) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(sessionKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(Base64.decodeBase64(sessionKey), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
Expand Down Expand Up @@ -57,7 +58,7 @@ public WxMaInternetResponse getUserEncryptKey(String openid, String sessionKey)
private WxMaInternetResponse getWxMaInternetResponse(String url) throws WxErrorException {
String responseContent = this.wxMaService.post(url, "");
WxMaInternetResponse response = WxMaGsonBuilder.create().fromJson(responseContent, WxMaInternetResponse.class);
if (response.getErrcode() == -1) {
if (response.getErrcode() != null && response.getErrcode() != 0) {
throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
}
return response;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,87 @@ public static String decryptAnotherWay(String sessionKey, String encryptedData,
}
}

/**
* 使用用户加密 key 对数据进行 AES-128-CBC 解密(用于小程序加密网络通道).
*
* <pre>
* 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
* encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码)
* iv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码)
* </pre>
*
* @param encryptKey 用户加密 key(Base64 编码)
* @param hexIv 加密 iv(Hex 编码)
* @param encryptedData 加密数据(Base64 编码)
* @return 解密后的字符串
*/
public static String decryptWithEncryptKey(String encryptKey, String hexIv, String encryptedData) {
try {
byte[] keyBytes = Base64.decodeBase64(encryptKey);
byte[] ivBytes = hexToBytes(hexIv);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle getUserEncryptKey iv as 16-byte IV

decryptWithEncryptKey unconditionally hex-decodes iv (hexToBytes), but the project’s own getUserEncryptKey response examples use 16-character values like 6003f73ec441c386 (WxMaInternetResponse/WxMaInternetUserKeyInfo), which become only 8 bytes after hex decoding; AES-CBC requires a 16-byte IV, so this path will throw at runtime for documented API-shaped inputs and make the new encrypt/decrypt helpers unusable in that scenario.

Useful? React with 👍 / 👎.

byte[] dataBytes = Base64.decodeBase64(encryptedData);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(ivBytes));
return new String(cipher.doFinal(dataBytes), UTF_8);
} catch (Exception e) {
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decryptWithEncryptKey catches all Exception and wraps it into WxRuntimeException, which means invalid hexIv (from hexToBytes’s IllegalArgumentException) won’t be observable as IllegalArgumentException by callers and the specific validation message is lost; is that intended given the Javadoc contract on hexToBytes? Other locations where this applies: weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141.

Severity: medium

Other Locations
  • weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java:141

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

throw new WxRuntimeException("AES解密失败!", e);
}
Comment on lines +101 to +114
}

/**
* 使用用户加密 key 对数据进行 AES-128-CBC 加密(用于小程序加密网络通道).
*
* <pre>
* 参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/user-encryptkey.html
* encryptKey 来自 getUserEncryptKey 接口返回的 encrypt_key 字段(Base64 编码)
* iv 来自 getUserEncryptKey 接口返回的 iv 字段(Hex 编码)
* </pre>
*
* @param encryptKey 用户加密 key(Base64 编码)
* @param hexIv 加密 iv(Hex 编码)
* @param data 待加密的明文字符串
* @return 加密后的数据(Base64 编码)
*/
public static String encryptWithEncryptKey(String encryptKey, String hexIv, String data) {
try {
byte[] keyBytes = Base64.decodeBase64(encryptKey);
byte[] ivBytes = hexToBytes(hexIv);

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE,
new SecretKeySpec(keyBytes, "AES"),
new IvParameterSpec(ivBytes));
return Base64.encodeBase64String(cipher.doFinal(data.getBytes(UTF_8)));
} catch (Exception e) {
throw new WxRuntimeException("AES加密失败!", e);
}
Comment on lines +131 to +143
}

/**
* 将 Hex 字符串转换为字节数组.
*
* @param hex Hex 字符串(长度必须为偶数,只包含 0-9 和 a-f/A-F 字符)
* @return 字节数组
* @throws IllegalArgumentException 如果输入不是合法的 Hex 字符串
*/
private static byte[] hexToBytes(String hex) {
if (hex == null || hex.length() % 2 != 0) {
throw new IllegalArgumentException("无效的十六进制字符串格式:长度必须为偶数");
}
int len = hex.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int high = Character.digit(hex.charAt(i), 16);
int low = Character.digit(hex.charAt(i + 1), 16);
if (high == -1 || low == -1) {
throw new IllegalArgumentException("无效的十六进制字符串格式:包含非法字符 '" + hex.charAt(high == -1 ? i : i + 1) + "'");
}
data[i / 2] = (byte) ((high << 4) + low);
}
Comment on lines +153 to +166
return data;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxMaCryptUtilsTest {
// 模拟来自 getUserEncryptKey 接口返回的 encrypt_key(Base64)和 iv(Hex,32位即16字节)
private static final String ENCRYPT_KEY = "VI6BpyrK9XH4i4AIGe86tg==";
private static final String HEX_IV = "6003f73ec441c3866003f73ec441c386";

Comment on lines +17 to +20
@Test
public void testDecrypt() {
String sessionKey = "7MG7jbTToVVRWRXVA885rg==";
Expand All @@ -32,4 +36,31 @@ public void testDecryptAnotherWay() {
assertThat(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr))
.isEqualTo(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr));
}

/**
* 测试使用用户加密 key(来自小程序加密网络通道)进行加密和解密的对称性.
* encrypt_key 为 Base64 编码的 16 字节 AES-128 密钥,iv 为 Hex 编码的 16 字节初始向量。
*/
@Test
public void testEncryptAndDecryptWithEncryptKey() {
String plainText = "{\"userId\":\"12345\",\"amount\":100}";

String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only assert encrypt/decrypt round-trip symmetry, so they can still pass even if the algorithm/encoding doesn’t match WeChat’s expected wire format (both sides could be consistently wrong). Consider adding at least one fixed test vector (known plaintext → expected ciphertext, or vice versa) from the official doc/examples to validate interoperability.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

assertThat(encrypted).isNotNull().isNotEmpty();

String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
assertThat(decrypted).isEqualTo(plainText);
}

/**
* 测试加密网络通道的加解密对称性(不同明文).
*/
@Test
public void testEncryptDecryptSymmetryWithEncryptKey() {
String plainText = "hello miniprogram";

String encrypted = WxMaCryptUtils.encryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, plainText);
String decrypted = WxMaCryptUtils.decryptWithEncryptKey(ENCRYPT_KEY, HEX_IV, encrypted);
assertThat(decrypted).isEqualTo(plainText);
}
Comment on lines +44 to +65
}