Discuz! Board

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 550|回复: 0
打印 上一主题 下一主题

微信AEAD_AES_256_GCM加密文件解密

[复制链接]

1228

主题

1997

帖子

7582

积分

认证用户组

Rank: 5Rank: 5

积分
7582
跳转到指定楼层
楼主
发表于 2023-4-22 16:15:59 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
谁适合看这篇文章
  • 找不到AEAD_AES_256_GCM 解密过程或者算法的小伙伴
  • 想了解一下AES加密算法的人
  • 其他感兴趣的小伙伴
  • 了解文件加解密的大概过程
一、前言
目前在对接微信的微信硬件平台( iot.weixin.qq.com ),可以从微信发送一个文件给到绑定的设备上。
大致流程: 微信绑定设备 →  在微信中发送文件到设备 → 微信会回调用户配置的回调地址 →  接收端将文件下载发给用户
我已经跑完绑定流程,现在想试试把文件发给用户试试看,回调我们服务器了,结果文件无法打开。 发送内容为一个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-GCM
GCM是认证加密模式中的一种,GCM中的G就是指GMAC,C就是指CTR,能同时确保数据的保密性、完整性及真实性。
运算标示运算过程
Ek使用秘钥k对输入做对称加密运算
XOR异或运算
Mh将输入与秘钥h在有限域GF(2^128)上做乘法
大致流程可以见下方:
  • 通过IV作为初始偏移量, 保证相同的明文和相同秘钥加密的结果是不一样的;(微信穿过来的IV)
  • 最后对比发送端发过来的MAC值跟我们的解密过程生成的MAC值是否相关从而保证数据的完整性,不会被篡改。(微信传过来的tag)
三、加密和解密过程
四、实操
微信提供的参考文档:pay.weixin.qq.com/wiki/doc/ap… ,里面有demo,但是只有解密的部分。
下面给出完整的过程,为了验证功能,把相关的内容都写在同一个类里面了。
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



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|firemail ( 粤ICP备15085507号-1 )

GMT+8, 2024-5-4 07:36 , Processed in 0.056787 second(s), 19 queries .

Powered by Discuz! X3

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表