一、问题描述
微信小程序获取手机号,官方通常会返回密文数据给我们,此时就需要我们自行解密数据。在揭秘的数据过程中会发现,第一次授权获取手机号会出现错误,再次获取的时候就能够正常获取。
错误信息一般分两种:
密文后端解密的javax.crypto.BadPaddingException: pad block corrupted(后端语言为java)错误;
密文前端解密的Illegal Buffer at WXBizDataCrypt.define.push.WXBizDataCrypt.decryptData错误。
二、错误原因
在回调里面又调用一次wx.login()导致登录状态被刷新,此时用code换取的sessionKey,不是的当时encryptedData对应的sessionKey,这个时候就导致了解密失败。
例如:
由于**@getphonenumber是获取用户手机号回调方法,然后又在回调中调用了一次login()**,导致出现上述问题。
三、解决方法
3.1 不推荐方法
当出现上述问题的时候,可以弹出提示,引导用户再点击一次授权,出现两次错误的几率还是不高的,业务允许的话,可以这么干。
3.2 正确方法
试想一下,出现问题的原因是啥,当然是在回调里面又重新login()了一下,那么能不能把login()放在回调外面。
也就是先调用login()方法获取code(不要在@getphonenumbe里面调用login),然后在获取sessionKey,解密数据。
四、附录
4.1 后端解密方法
import com.alibaba.fastjson.JSONObject;
WXBizDataCryptUtil util = new WXBizDataCryptUtil(properties.getAppId(), dto.getSessionKey());
JSONObject data = util.decrypt(dto.getEncryptedData(), dto.getIv());
import com.alibaba.fastjson.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;
import java.util.Base64;
/**
* 微信数据解密工具
*
* @date 2023-06-08 16:51:26
*/
public class WXBizDataCryptUtil {
public static JSONObject getMoreInfoFromEncryptedData(String appId, String sessionKey, String encryptedData, String iv) {
WXBizDataCryptUtil pc = new WXBizDataCryptUtil(appId, sessionKey);
JSONObject decrypt = pc.decrypt(encryptedData, iv);
return decrypt;
}
private String appId;
private String sessionKey;
public WXBizDataCryptUtil(String appId, String sessionKey) {
this.appId = appId;
this.sessionKey = sessionKey;
}
/**
* 解密成json
*
* @param encryptedData
* @param iv
* @return
*/
public JSONObject decrypt(String encryptedData, String iv) {
byte[] encryptedDataDecode = Base64.getDecoder().decode(encryptedData);
byte[] sessionKeyDecode = Base64.getDecoder().decode(this.sessionKey);
byte[] ivDecode = Base64.getDecoder().decode(iv);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] decodeData = decode(encryptedDataDecode, sessionKeyDecode, ivDecode);
String stringData = new String(decodeData);
JSONObject jsonObject = JSONObject.parseObject(stringData);
return jsonObject;
}
/**
* 解密算法 AES-128-CBC
* 填充模式 PKCS#7
*
* @param encryptedDataDecode 目标密文
* @return
* @throws Exception
*/
private byte[] decode(byte[] encryptedDataDecode, byte[] sessionKeyDecode, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(sessionKeyDecode, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIv(iv));// 初始化
byte[] result = cipher.doFinal(encryptedDataDecode);
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 处理iv
*
* @param iv
* @return
* @throws Exception
*/
private AlgorithmParameters generateIv(byte[] iv) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}
4.2 前端解密
getPhoneNumber(e) {
if (e.detail.errMsg === 'getPhoneNumber:ok') {
getOpenId({
code: this.wxCode
})
.then(openIdRes => {
if (!openIdRes.success) {
this.$refs.page.closeLoading()
showErrorToast(this.$refs.uToast, {
position: 'top',
message: openIdRes.message
})
return
}
const data = openIdRes.data
const wxCryto = new WXBizDataCrypt(data.appId, data.sessionKey)
const encryptData = wxCryto.decryptData(e.detail.encryptedData, e.detail.iv)
console.log('解密结果', encryptData)
})
.catch(err => {
console.log(err)
this.$refs.page.closeLoading()
showErrorToast(this.$refs.uToast, {
position: 'top',
message: '获取微信参数异常, 请重试'
})
})
}
}
var crypto = require('crypto')
function WXBizDataCrypt(appId, sessionKey) {
this.appId = appId
this.sessionKey = sessionKey
}
WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
// base64 decode
var sessionKey = new Buffer(this.sessionKey, 'base64')
encryptedData = new Buffer(encryptedData, 'base64')
iv = new Buffer(iv, 'base64')
try {
// 解密
var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true)
var decoded = decipher.update(encryptedData, 'binary', 'utf8')
decoded += decipher.final('utf8')
decoded = JSON.parse(decoded)
} catch (err) {
throw new Error('Illegal Buffer')
}
if (decoded.watermark.appid !== this.appId) {
throw new Error('Illegal Buffer')
}
return decoded
}
module.exports = WXBizDataCrypt