近期项目需要做一个微信小程序解密拿到手机号:
微信下程序授权登录拿到手机号的流程图:
小程序可以通过各种前端接口获取微信提供的开放数据。考虑到开发者服务器也需要获取这些开放数据,微信会对这些数据做签名和加密处理。开发者后台拿到开放数据后可以对数据进行校验签名和解密,来保证数据不被篡改。
签名校验以及数据加解密涉及用户的会话密钥 session_key。 开发者应该事先通过 wx.login 登录流程获取会话密钥 session_key 并保存在服务器。为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境。
接口返回的加密数据(encryptedData) 进行对称解密。 解密算法如下:
对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充。
对称解密的目标密文为 Base64_Decode(encryptedData)。
对称解密秘钥 aeskey = Base64_Decode(session_key), aeskey 是16字节。
对称解密算法初始向量 为Base64_Decode(iv),其中iv由数据接口返回。
开始撸代码:
1:先看看刚开始写的代码:
controller层:
@PostMapping("/public/user/auth/phone")
public RtnResult<Object> authPhone( String encryData,String sessionKey,String iv) throws Exception {
log.info("解密手机号,入参为【{}】",sessionKey,);
return wxmpService.authPhone(String encryData,String sessionKey,String iv);
}
service层:
public RtnResult<Object> authPhone(String encryData, String sessionKey, String ivStr) throws Exception {
String phone =decryptPhone(encryData, sessionKey, ivStr);
log.info("通过seeionKey解密成功后的手机号:【{}】", phone);
return RtnResult.success(phone);
}
decryptPhone方法:
public String decryptPhone( String encryptedData, String sessionKey, String ivStr) throws Exception {
Base64 base64 = new Base64();
byte[] encData = base64.decode(encryptedData);
byte[] iv = base64.decode(ivStr);
byte[] key = base64.decode(sessionKey);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
String mapStr = new String(cipher.doFinal(encData),"UTF-8");
Map<String,Object> bodyMap = JacksonUtil.json2Map(mapStr);
return (String) bodyMap.get("phoneNumber");
}
开开心心的运行,以为大功告成,成功近在眼前。。。。。。嗨嗨嗨!!!
一运行,完了完了完了,报错了,问题出现在那?出了什么错呢???
String mapStr = new String(cipher.doFinal(encData),"UTF-8");//这一句出现异常
09:56:00.199 [http-nio-8080-exec-3] ERROR c.h.m.e.GlobalExceptionHandler - 系统异常信息被捕获
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:936)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
at javax.crypto.Cipher.doFinal(Cipher.java:2164)
at com.hczt.mall.wx.service.WxmpService.decryptPhone(WxmpService.java:139)
at com.hczt.mall.wx.service.WxmpService.authPhone(WxmpService.java:119)
at com.hczt.mall.wx.controller.WxmpController.authPhone(WxmpController.java:44)
at com.hczt.mall.wx.controller.WxmpController$$FastClassBySpringCGLIB$$fe73f753.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:746)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:112)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
这句话的意思就是:当填充密码进行解密时,输入长度必须是16的倍数,意思就是加密的数据encryData的长度不是16的倍数
打印传过来的三个数据的长度:
data长度:【157】,iv长度:【16】,key长度【16】//data的长度是157不是16的倍数
认真仔细查看,前台传过来的参数和后台接口的参数作比较:
前台传过来的的参数为:
encryData:7yMoGI85MIssud2hBeEpz9ib6BJ9epZVYYqNvqA+DPnhPfui1yeOh6+4AMlIpidzMjlKV3wr6eEqXR2+qAk7WmezN7CO7i/6HZNFPy+ZZ/p8RTyVSbxwytOnJkSf49cb/v7cgX10BnuJ4M6n7ORJY1EBVg7iBWh9IPaWCuBxogBBX3NScuFc4CFXn1loagb2G0bTgiY1CKFnrvpiOnMY4g==
后台接受的参数为:
解密手机号,encaryData入参为【7yMoGI85MIssud2hBeEpz9ib6BJ9epZVYYqNvqA DPnhPfui1yeOh6 4AMlIpidzMjlKV3wr6eEqXR2 qAk7WmezN7CO7i/6HZNFPy ZZ/p8RTyVSbxwytOnJkSf49cb/v7cgX10BnuJ4M6n7ORJY1EBVg7iBWh9IPaWCuBxogBBX3NScuFc4CFXn1loagb2G0bTgiY1CKFnrvpiOnMY4g==】
问题原因根本:
一看就发现问题所在,原来是"+"没有了。因为符号“+”和符号“/”是不允许出现在URL中的
解决办法:
那么不把数据放在路径上了,把数据封装到一个对象里,用@RequestBody来接受参数
,修改controller层
第一步:增加一个对象:
import lombok.Data;
@Data
public class WxAuthPhone {
String encryData;
String sessionKey;
String iv;
}
第二步:修改controller层
@PostMapping("/public/user/auth/phone")
public RtnResult<Object> authPhone(@RequestBody WxAuthPhone wxAuthPhone) throws Exception {
log.info("解密手机号,入参为【{}】",wxAuthPhone);
return wxmpService.authPhone(wxAuthPhone);
}
第三步:修改sevice层
public RtnResult<Object> authPhone(WxAuthPhone wxAuthPhone) throws Exception {
String phone =decryptPhone(wxAuthPhone.getEncryData(), wxAuthPhone.getSessionKey(), wxAuthPhone.getIv());
log.info("通过seeionKey解密成功后的手机号:【{}】", phone);
return RtnResult.success(phone);
}
测试结果:
手机号解密成功,手机号为【17865342342】