为什么要发这一篇文章,如果你们是通过搜索找到这篇文章的,我已经明白你们的辛酸了。
ps:
该篇解析使用的是PKCS7Padding,懂的都懂。
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
不多说了,上代码(亲自实践可用):
先导入核心的jar依赖:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk16</artifactId> <version>1.46</version> </dependency>
WeChatUtil:
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import org.apache.tomcat.util.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.*; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.security.*; import java.security.spec.InvalidParameterSpecException; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @Component public class WeChatUtil { private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class); @Value("${wechat.applets.appid}") private String appId; @Value("${wechat.applets.appsecret}") private String appSecret; //算法名 public static final String KEY_NAME = "AES"; // 加解密算法/模式/填充方式 // ECB模式只用密钥即可对数据进行加密解密,CBC模式需要添加一个iv public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; /** * 微信小程序-个人信息解密 * @param encryptedData * @param sessionKey * @param iv * @return */ public static String getUserInfo(String encryptedData, String sessionKey, String iv) { String result = ""; // 被加密的数据 byte[] dataByte = Base64.decodeBase64(encryptedData); // 加密秘钥 byte[] keyByte = Base64.decodeBase64(sessionKey); // 偏移量 byte[] ivByte = Base64.decodeBase64(iv); try { // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要 int base = 16; if (keyByte.length % base != 0) { int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0); 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[] resultByte = cipher.doFinal(dataByte); if (null != resultByte && resultByte.length > 0) { result = new String(resultByte, "UTF-8"); } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidParameterSpecException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return result; } /** * 获取微信小程序 session_key 和 openid * * @param code 调用微信登陆返回的Code * @return */ public JSONObject getSessionKeyAndOpenid(String code) { //微信端登录code值 String wxCode = code; //请求地址 https://api.weixin.qq.com/sns/jscode2session String requestUrl = "https://api.weixin.qq.com/sns/jscode2session"; Map<String, String> requestUrlParam = new HashMap<>(); //开发者设置中的appId requestUrlParam.put("appid", appId); //开发者设置中的appSecret requestUrlParam.put("secret", appSecret); //小程序调用wx.login返回的code requestUrlParam.put("js_code", wxCode); //默认参数 authorization_code requestUrlParam.put("grant_type", "authorization_code"); //发送post请求读取调用微信 https://api.weixin.qq.com/sns/jscode2session 接口获取openid用户唯一标识 JSONObject jsonObject = JSON.parseObject(sendPost(requestUrl, requestUrlParam)); return jsonObject; } /** * 微信 数据解密<br/> * 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充<br/> * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)<br/> * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节<br/> * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节<br/> * * @param encrypted 目标密文 * @param session_key 会话ID * @param iv 加密算法的初始向量 */ public static String wxDecrypt(String encrypted, String session_key, String iv) { String json = null; byte[] encrypted64 = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(encrypted); byte[] key64 = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(session_key); byte[] iv64 = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(iv); byte[] data; try { init(); json = new String(decrypt(encrypted64, key64, generateIV(iv64))); } catch (Exception e) { e.printStackTrace(); } return json; } /** * 初始化密钥 */ public static void init() throws Exception { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); KeyGenerator.getInstance(KEY_NAME).init(128); } /** * 生成iv */ public static AlgorithmParameters generateIV(byte[] iv) throws Exception { // iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0 // Arrays.fill(iv, (byte) 0x00); AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_NAME); params.init(new IvParameterSpec(iv)); return params; } /** * 生成解密 */ public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv) throws Exception { Key key = new SecretKeySpec(keyBytes, KEY_NAME); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); // 设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, key, iv); return cipher.doFinal(encryptedData); } /** * 向指定 URL 发送POST方法的请求 * * @param url 发送请求的 URL * @return 所代表远程资源的响应结果 */ public String sendPost(String url, Map<String, ?> paramMap) { PrintWriter out = null; BufferedReader in = null; String result = ""; String param = ""; Iterator<String> it = paramMap.keySet().iterator(); while (it.hasNext()) { String key = it.next(); param += key + "=" + paramMap.get(key) + "&"; } try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { log.error(e.getMessage(), e); } //使用finally块来关闭输出流、输入流 finally { try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } }
工具栏里面涉及到封装写好的两个核心方法:
第一个
根据前端小程序传入的code获取 session_key 和 openid:
这个没什么问题,简简单单不多说。
第二个
前端拿着我们给他的 session_key 和 openid ,他给整回来了加密的数据,需要我们服务端帮忙解析,这个真的烦人!但是看到这里的你们,不用烦了,直接用我写的这个就行。
就到这,赶紧拿走工具类去解析吧。