firemail
标题: 微信AEAD_AES_256_GCM加密文件解密 [打印本页]
作者: Qter 时间: 2023-4-22 16:15
标题: 微信AEAD_AES_256_GCM加密文件解密
谁适合看这篇文章
- 找不到AEAD_AES_256_GCM 解密过程或者算法的小伙伴
- 想了解一下AES加密算法的人
- 其他感兴趣的小伙伴
- 了解文件加解密的大概过程
一、前言大致流程: 微信绑定设备 → 在微信中发送文件到设备 → 微信会回调用户配置的回调地址 → 接收端将文件下载发给用户
我已经跑完绑定流程,现在想试试把文件发给用户试试看,回调我们服务器了,结果文件无法打开。 发送内容为一个excel,内容见: 下面是微信返回回调传过来文件相关信息:
"type": "xlsx", "download_url": "https://mmae.qpic.cn/204/20303/stodownload?filekey=30340201010420301e020200cc040253480410fdbdfa68107f86ef77746addde017c1e0202200c040d00000004627466730000000131&hy=SH&storeid=32303232303430323135343330363030306139376539306232356232373734313337623030623030303030306363&bizid=1023", "iv_base64": "e3fXzamUIuath/Hp", "encrypt_algo": "AEAD_AES_256_GCM", "name": "2222222.xlsx", "tag_base64": "KtCd2YFsoG92GcW5YT1utg==", "key_base64": "58Doa0yn2xRiHiy5TFk4p42iR8JmYNCAZ8oxAqh8erE="[color=rgba(140, 140, 140, 0.8)]复制代码直接通过download_url下载,下载下来是一个“stodownload”的文件,加上excel的后缀名,无法正常打开。
从返回的报文可以看出,微信返回的信息中,有提示文件使用了AEAD_AES_256_GCM算法进行加密,我们接下来看看先AES相关的知识。
AEAD_AES_256_GCM : AES算法的GCM认证加密模式,key的长度为256bit。
二、AES相关知识1、对称加密AES是对称加密的一种,即加密和解密使用相同的key。
2、对称加密的相关知识- 明文P(plainText):未经加密的数据
- 密钥K(key):用来加密明文的密码。在对称加密算法中,加密与解密的密钥是相同的,由双方协商产生,绝不可以泄漏
- 密文C(cipherText): 经过加密的数据
- 加密函数E(encrypt):C = E(K, P),即将明文和密钥作为参数,传入加密函数中,就可以获得密文
- 解密函数D(decrypt):P = D(K, C),即将密文和密钥作为参数,传入解密函数中,就可以获得明文
3、AES相关分组(或者叫块) :AES是一种分组加密技术,分组加密就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。。在AES标准规范中,分组长度只能是128 bits,也就是每个分组为16个bytes
初始向量(IV,Initialization Vector) :它的作用和MD5的“加盐”有些类似,目的是防止同样的明文块,始终加密成同样的密文块。
密钥长度:AES支持的密钥长度可以是128 bits或256 bits。
假如使用的AEAD_AES_256_GCM进行加密,则key的长度必须是32位 秘钥长度 = key.length * 8 256 = 32 * 8, key长度必须是 32 位 128 = 16 * 8, key长度必须是 16 位[color=rgba(140, 140, 140, 0.8)]复制代码4、AES-GCMGCM是认证加密模式中的一种,GCM中的G就是指GMAC,C就是指CTR,能同时确保数据的保密性、完整性及真实性。
运算标示运算过程
Ek使用秘钥k对输入做对称加密运算
XOR异或运算
Mh将输入与秘钥h在有限域GF(2^128)上做乘法大致流程可以见下方:
- 通过IV作为初始偏移量, 保证相同的明文和相同秘钥加密的结果是不一样的;(微信穿过来的IV)
- 最后对比发送端发过来的MAC值跟我们的解密过程生成的MAC值是否相关从而保证数据的完整性,不会被篡改。(微信传过来的tag)
三、加密和解密过程四、实操下面给出完整的过程,为了验证功能,把相关的内容都写在同一个类里面了。
ps:为了方便调试了先手动把加密文件下载到了本地。
package com.seewo.station.common.util;import java.io.*;import java.security.GeneralSecurityException;import java.security.InvalidAlgorithmParameterException;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.util.Base64;import javax.crypto.Cipher;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.GCMParameterSpec;import javax.crypto.spec.SecretKeySpec;public class AesUtil { static final int KEY_LENGTH_BYTE = 32; static final int TAG_LENGTH_BIT = 128; private final byte[] aesKey; public AesUtil(byte[] key) { if (key.length != KEY_LENGTH_BYTE) { throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); } this.aesKey = key; } /** * * @param associatedData 加密的字节流 * @param nonce 偏移量 * @param ciphertext 校验的tag * @return 解密后的文件流 */ public byte[] decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException { try { Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); SecretKeySpec key = new SecretKeySpec(aesKey, "AES"); GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce); //GCMParameterSpec spec = new GCMParameterSpec(ciphertext.length() * Byte.SIZE, nonce); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.update(associatedData); return cipher.doFinal(Base64.getDecoder().decode(ciphertext)); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); } } public static void main(String[] arg) throws IOException, GeneralSecurityException { String downloadUrl = "https://mmae.qpic.cn/204/20303/stodownload?filekey=30340201010420301e020200cc040253480410fdbdfa68107f86ef77746addde017c1e0202200c040d00000004627466730000000131&hy=SH&storeid=32303232303430323135343330363030306139376539306232356232373734313337623030623030303030306363&bizid=1023"; String tag_base64 = "KtCd2YFsoG92GcW5YT1utg=="; String iv_base64 = "e3fXzamUIuath/Hp"; String key_base64 = "58Doa0yn2xRiHiy5TFk4p42iR8JmYNCAZ8oxAqh8erE="; // 将key_base64 通过base64 解密 byte[] key = Base64.getDecoder().decode(key_base64); byte[] iv = Base64.getDecoder().decode(iv_base64); AesUtil a = new AesUtil(key); //将文件下载到本地, 我目前是直接手动将文件下载下来了,实际实现需要些下载文件相关的代码 //DownloadFileUtil.downloadFile(downloadUrl); byte[] fileData = a.getContent("/Users/Downloads/stodownload (12)"); save2File("/Users/Downloads/demo4.xlsx", a.decryptToString(fileData, iv , tag_base64 )); } /** * 读取每个路径上的文件 * @param filePath 文件路径 * @return 文件字节流 * @throws IOException */ public byte[] getContent(String filePath) throws IOException { File file = new File(filePath); long fileSize = file.length(); if (fileSize > Integer.MAX_VALUE) { System.out.println("file too big..."); return null; } FileInputStream fi = new FileInputStream(file); byte[] buffer = new byte[(int) fileSize]; int offset = 0; int numRead = 0; while (offset < buffer.length && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) { offset += numRead; } // 确保所有数据均被读取 if (offset != buffer.length) { throw new IOException("Could not completely read file " + file.getName()); } fi.close(); return buffer; } /** * 将字节流保存成文件 * @param filename 文件名 * @param msg 文件数据 * @return */ public static boolean save2File(String filename, byte[] msg){ OutputStream fos = null; try{ File file = new File(filename); File parent = file.getParentFile(); boolean bool; if ((!parent.exists()) && (!parent.mkdirs())) { return false; } fos = new FileOutputStream(file); fos.write(msg); fos.flush(); return true; }catch (FileNotFoundException e){ return false; }catch (IOException e){ File parent; return false; } finally{ if (fos != null) { try{ fos.close(); }catch (IOException e) {} } } }}[color=rgba(140, 140, 140, 0.8)]复制代码跑完main函数,会发现,本地多了一个demo4.xlsx文件,打开跟我们上传的一样。
五、后记从信息安全的角度,给第三方传输信息都需要进行加密,防止因为我们链接泄露,导致用户的私人数据被恶意盗用。 加密的返回不仅包括传输的信息,现有的加密算法也支持对文件进行加密。
https://juejin.cn/post/7084195149567229959
欢迎光临 firemail (http://firemail.wang:8088/) |
Powered by Discuz! X3 |