前要: 今天调试一下微信授权登录的时候老是第一次报错解密失败pad block corrupted,第二次授权的时候正常,因为第一次已经获取到手机号码!
后端代码:
public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) {
try {
// 加密秘钥
byte[] keyByte = Base64.decodeBase64(sessionKey);
// 偏移量
byte[] ivByte = Base64.decodeBase64(iv);
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyByte.length % base != 0) {
int groups = keyByte.length / base + 1;
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
keyByte = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters); // 初始化
// 被加密的数据
byte[] dataByte = Base64.decodeBase64(encryptedData);
byte[] resultByte = cipher.doFinal(dataByte);
if (null != resultByte && resultByte.length > 0) {
String result = new String(resultByte, StandardCharsets.UTF_8);
log.info("解析微信加密数据==>{}", result);
return JSONObject.parseObject(result);
}
} catch (BadPaddingException e) {
log.error("解析微信加密数据失败", e);
throw Exceptions.fail(WECHAT_SESSION_EXPIRED);
}
return null;
}
报错提示:
javax.crypto.BadPaddingException: pad block corrupted
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2088)
at com.saic.ebiz.service.util.AESUtils.getUserInfo(AESUtils.java:62)
at com.saic.ebiz.service.util.AESUtils.main(AESUtils.java:96)
Exception in thread "main" java.lang.NullPointerException
at com.saic.ebiz.service.util.AESUtils.main(AESUtils.java:96)
查找了官方文档原来不是后端代码问题,而是前端代码问题~是微信的bug。不知道有没有解决,但我们自己可以去解决:就是执行顺序问题
原因: uni.login获取session_key,而sessionKey又是解密encryptedData的密钥,所以一旦我们的uni.login在uni.getUserInfo(getPhoneNumber)之后获取,我们存储的sessionKey绝对不是当前获取encryptedData的密钥。
- 这是因为调用了uni.login后通过code获得的session_key是新的session_key.
- 所以,在调用uni.login之前获的加密数据不是用新得session_key加密的数据。
- 在调用uni.login之后获得的加密数据,才是新得的session_key加密的数据。
本来的执行顺序:
- 先触发getPhoneNumber获取了手机号的加密数据。
- 然后才调用的uni.login获取code。
- 再通过code取到用户的session_key 。
- 最后再用session_key,手机号的加密数据和向量解密获取手机号。
正确的做法应该是:
- 首先调用的uni.login获取code
- 再通过code取到用户session_key 。
- 再触发getPhoneNumber获取了手机号的加密数据
- 最后再用session_key,手机号的加密数据和向量解密获取手机号。
前端保证uni.login获取到的code在getUserInfo(getPhoneNumbe)操作之前。也可以是后端,因为项目是graphql,所以我是前端保证就行。社区还提供另外一种方案Cipher cipher = Cipher.getInstance(“AES/CBC/PKCS7Padding”, “BC”);中BC去掉,不知道行不行!可以试一下!!!
setTab(index) {
let that = this;
that.tabIndex = index;
if (index == 1) {
uni.login({
provider: 'weixin',
success: function(loginRes) {
that.loginResCode = loginRes.code;
}
})
}
},
getPhoneNumber: function(e) {
console.log('aaaaaaaa', e)
if (e.detail.errMsg != "getPhoneNumber:ok") {
return console.log('用户拒绝请求')
}
let self = this;
let fromData = {
rawData: e.detail.encryptedData,
iv: e.detail.iv,
code: self.loginResCode
}
self.$api.wxLogin(fromData).then(res => {
if (res.data.code == 200) {
self.$store.commit('changeUserInfo', res.data.data);
self.$store.commit('changeLoginStatus', true);
self.$store.commit('changeToken', res.header.accesstoken || res.header.accessToken);
// uni.navigateBack();
var pages = getCurrentPages(); // 当前页面
var beforePage = pages[pages.length - 2]; // 前一个页面
if (beforePage) {
uni.navigateBack()
} else {
uni.switchTab({
url: '/pages/home/home'
})
}
} else {
uni.showModal({
title: '提示',
content: res.data.msg,
showCancel: false,
success: function(res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
}
})
},