本文将介绍 ZIP 流式解压的原理和技术实现路径,希望为大家带来启发,将 ZIP 流式解压技术更多的应用到业务中。
ZIP 是一种文件格式,定义了如何将多个文件、数据块组织在一起形成一个完整的文件。例如我们常见的 .apk,.ipa,.sketch,都是ZIP文件。通常程序是这样创建 ZIP 文件的:
提取所有文件描述信息,生成一份「文件目录」,附在最后一个数据块的尾部。
我们将文件前部描述信息称为 Local File Header,文件后部描述信息称为 Data Descriptor, 被压缩的文件本身称为 File Data,将最后的文件目录称为 Central Directory。以上所有合在一起,就是一个标准的 ZIP 文件。如下图:
一个标准的解压方式总是从读取 ZIP 文件末尾开始的,我们以解压上图的 File Data 1 为例:
可以发现,标准的解压强依赖尾部的 Central Directory。当 ZIP 文件存储在 cdn 上时,哪怕我们只想访问其中的一个文件,也必须下载整个 ZIP 解压后才可访问。假如 ZIP 文件有 100 MB,但是我们只需要访问其中的某一个 10 KB 的文件,那么下载整个 ZIP 将是对流量的巨大浪费。
我们的一个初步的想法是能不能边下载边解压?
要实现这点,首先需要改变解压方式,使其不能再依赖尾部的 Central Directory。
根据 ZIP 文件格式标准可知,除了 Central Directory,每个 File Data 头部的 Loca File Header 部分也包含了该文件的相关信息。
假如 Local File Header 中包含了充分的信息,我们也许可以基于 Local File Header 去解压文件数据,其解压流程就可以变为:
我们根据文档对 Local File Header 的描述,画出其二进制文件中的排列:
其中的关键信息为:
元数据签名是一个 Magic Number,用来标记接下来数据是什么内容。例如 Local File Header 的签名是 0x04034b50,用 char 表示也就是 { 'P', 'K', '3', '4' }。当读取到对应数据签名时,则意味着接下来的数据结构符合对应元数据的定义,需要使用对应规则解析。
Compress Method 指明数据块用何种算法压缩,解压需要使用对应的算法。
Compressed Size 和 UnCompressed Size可以帮助确定文件的结尾地址和 Data Descriptor 的偏移量。这两个 Size 也是文件解密时 HMAC 计算的关键。
有了 Magic Number 作为元数据签名,我们只需要逐字节遍历去匹配这个 Number,就可以找到 Loca File Header,而不再需要依赖尾部的定位信息。而且 Local File Header 中存储的元数据足够我们决定解压算法、计算大小、校验 CRC-32 了。
还有一个问题是,解压缩算法是否支持流式解压缩?是否有特定的上下文依赖?通过了解压缩算法的原理[1],我们知道,所有的压缩算法都是支持从头部开始流式解压的。
而下载方面,文件是以从头到尾连续的方式下载,这又天然地和和从头解压的方式配合,便可以初步实现边下边解!
一切都相当顺利,直到遇到了加密后的 ZIP 文件。加密后的 ZIP 文件的 Local File Header 中的关键信息除了签名和文件名以外,其他信息都被隐去,需要去 Central Directory 中读取。
再一次,我们回到了依赖 Central Directory 的状态。
在失去如此多关键信息的情况下能否继续做到流式解压?我们需要先挖掘一下 ZIP 的加密方式。
ZIP 文件支持多种加密方式,最常见的是 Traditional PKWARE Encryption 和 AES Encryption 。
Traditional PKWARE Encryption 是 ZIP 自定义的一种基于密码的对称加密方式,每个字节的加密仅和密码有关,加密前后的数据长度不变。这种不依赖上下文的加密方式可以实现我们需要的流式解密。
AES 加密采用的是 CTR 模式。CTR 模式将明文分组,并生成一个计数器。使用密钥对计数器进行加密生成二进制字节流。利用这个字节流和明文进行 XOR 操作进行加密。其解密方式也是一样的。
这种方式也支持流式解密。
两种常用的加密方式都支持流式解密,那么加解密需要的关键信息,在 Local File Header 中是否有存储就成了能否流式解密的关键。
无论是 Traditional PKWARE Encryption 还是 AES Encryption,在解密时都需要一些除密码之外的关键信息,例如盐值,加密算法的强度等。此外,在 AES 加密的 ZIP 文件中, Local File Header 中的 Compress Method 字段被抹去,这样我们便无法知晓压缩算法,因此无法解压。
至此,问题集中为:
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。