ZIP 也能边下载边解压?流式解压技术揭秘!

2020 年 9 月 8 日 阿里技术

阿里妹导读:对于个 ZIP 文件,由于标准的解压方式总是从读取文件的末尾开始的,因此必须下载完整个 ZIP 解压后才能访问。当用户通过网络访问 ZIP 文件时,下载解压所带来的耗时将大大降低用户体验。那么能不能边下载边解压呢?阿里巴巴文娱技术 喻远将介绍 ZIP 流式解压的原理和技术实现路径。


文末福利:宜搭低代码开发训练营来了!


打开网络上的 ZIP 文件需要几步?下载,解压,拿到所有文件。面对一个 ZIP,能不能「边下边播」、「按需下载」?

今年 6 月,优酷绘本技术团队开发出新的解压方式——ZIP 流式解压技术,并成功应用在优酷绘本秒开项目中,30M+ 绘本平均加载时长只需 0.91s,加载耗时比传统的解压方式降低了 88.3%,让用户的阅读体验直线提升。实际对比效果如下:

优化前


优化后


本文将介绍 ZIP 流式解压的原理和技术实现路径,希望为大家带来启发,将 ZIP 流式解压技术更多的应用到业务中。

一  什么是ZIP 

ZIP 是一种文件格式,定义了如何将多个文件、数据块组织在一起形成一个完整的文件。例如我们常见的 .apk,.ipa,.sketch,都是ZIP文件。通常程序是这样创建 ZIP 文件的:

  • 压缩单个文件形成单文件数据块;


  • 在数据块前后添加文件描述信息;


  • 对每个待压缩的文件重复以上步骤后,拼接所有数据形成更大的数据块;


  • 提取所有文件描述信息,生成一份「文件目录」,附在最后一个数据块的尾部。


我们将文件前部描述信息称为 Local File Header,文件后部描述信息称为 Data Descriptor, 被压缩的文件本身称为 File Data,将最后的文件目录称为 Central Directory。以上所有合在一起,就是一个标准的 ZIP 文件。如下图:


ZIP 文件格式

一个标准的解压方式总是从读取 ZIP 文件末尾开始的,我们以解压上图的 File Data 1 为例:



  • 首先在 ZIP 文件末尾找到 Central Directory 数据块;


  • 在 Central Directory 数据块中找到 File Header 1;


  • 从 File Header 1 中读取 Local File Header 1 的偏移量和 File Data 1 的相关信息;


  • 根据偏移量找到 Local File Header 1;


  • 读取 Local File Header 1;


  • 解密 File Data 1(如果需要);


  • 解压 File Data 1;


  • 读取 Data Descriptor 1;


  • 使用 File Header 1 中保存的 CRC-32 做校验步骤 7 中计算的 CRC-32,以确保解压后的数据完整性。


标准解压方式存在的不足

可以发现,标准的解压强依赖尾部的 Central Directory。当 ZIP 文件存储在 cdn 上时,哪怕我们只想访问其中的一个文件,也必须下载整个 ZIP 解压后才可访问。假如 ZIP 文件有 100 MB,但是我们只需要访问其中的某一个 10 KB 的文件,那么下载整个 ZIP 将是对流量的巨大浪费。

二  优酷技术方案:ZIP流式解压

我们的一个初步的想法是能不能边下载边解压?

要实现这点,首先需要改变解压方式,使其不能再依赖尾部的 Central Directory。

根据 ZIP 文件格式标准可知,除了 Central Directory,每个 File Data 头部的 Loca File Header 部分也包含了该文件的相关信息。

假如 Local File Header 中包含了充分的信息,我们也许可以基于 Local File Header 去解压文件数据,其解压流程就可以变为:


  • 从头开始,搜索到 Local File Header 1;


  • 读取 Local File Header 1;


  • 解密 File Data 1(如果需要);


  • 解压 File Data 1;


  • 读取 Data Descriptor 1;


  • CRC32 的校验。


那么 Local File Header 里到底存储了什么?是否满足解密解压所需?

了解 Local File Header

我们根据文档对 Local File Header 的描述,画出其二进制文件中的排列:

Local File Header 数据结构

其中的关键信息为:

Signature 元数据签名
Compress Method 压缩算法
Compressed Size 压缩后文件大小
Uncompressed Size 压缩前文件大小
CRC-32 文件的循环冗余校验值
File name 文件名

元数据签名是一个 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 文件。加密后的 ZIP 文件的 Local File Header 中的关键信息除了签名和文件名以外,其他信息都被隐去,需要去 Central Directory 中读取。

再一次,我们回到了依赖 Central Directory 的状态。

在失去如此多关键信息的情况下能否继续做到流式解压?我们需要先挖掘一下 ZIP 的加密方式。

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 字段被抹去,这样我们便无法知晓压缩算法,因此无法解压。

至此,问题集中为:

  • Local File Header 中是否有足够的加密所需信息。


  • 加密的 ZIP 文件,是否能在除 Central Directory 以外的位置找到 Compress Method 字段。


Local File Header 中加密相关的信息

ZIP 格式的设计者在设计 ZIP 文件格式的初期就提供了文件拓展能力,一些额外的拓展数据可以存放在 Local File Header 的 Extra Field 中。ZIP AES 加密说明书[2]告诉我们 AES 的相关信息就存放在这里。其关键信息如下:

Signature Extra Data 签名(0x9901)
AES Encryption strength AES 加密强度(128或192或256)
Actual compress Method 真正的压缩算法


原来压缩算法被藏到了 Extra Data 中。那么盐值被存放在哪里了?答案是存放在 File Data 的头尾。


综上,我们找到解密所需的所有关键信息,整个流式解密解压的所有技术点都被我们探索完。剩下的便是按原理实现,以及细节的打磨。

三  总结

说了那么多,流式解压究竟有什么价值呢?

由于流式解压实现了边下载边解压,将整个操作的时长从下载 + 解压缩变成了约等于纯下载的时长,直接抹掉了解压的耗时。在 39.1 MB 大小的 ZIP 包下载解压测试中,耗时从 9.08 秒降低至 4.17 秒,有将近 100% 的提速!同时,你可以不必等待整个 ZIP 下载解压完,而是在解压完一小部分数据的时候,就直接展示 UI。用户侧看起来就好像一瞬间就解压完了。

因此,流式解压可以应用在许多时间敏感的操作里,也可以用来优化基于 ZIP 文件的相关业务。例如基于 ZIP 的全局换肤加速、基于 ZIP 的 Web 资源缓存加载的加速等等。前言中的优酷绘本秒开就是基于这一技术实现。

参考

[1]https://houbb.github.io/2018/11/09/althgorim-compress-althgorim-12-zip-02
[2]AES Encryption Information: Encryption Specification AE-1 and AE-2
https://www.winzip.com/win/en/aes_info.html
[3]ZIP File Format Specification
https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.1.TXT
[4]AES Coding Tips for Developers
https://www.winzip.com/win/en/aes_tips.html



宜搭低代码开发训练营

实战课程


阿里低代码应用开发PaaS平台「宜搭」首次面向社会各界开发者的训练营即将开课。四位技术大咖独家授课,手把手教你使用宜搭,帮助你迅速掌握页面、流程及报表的搭建,从入门到掌握,只需要5天!


点击“阅读原文”,了解详情吧~


关注「阿里技术」
把握前沿技术脉搏


戳我,去训练营。
登录查看更多
0

相关内容

【2020新书】使用Kubernetes开发高级平台,519页pdf
专知会员服务
66+阅读 · 2020年9月19日
【2020新书】使用高级C# 提升你的编程技能,412页pdf
专知会员服务
57+阅读 · 2020年6月26日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
【实用书】流数据处理,Streaming Data,219页pdf
专知会员服务
76+阅读 · 2020年4月24日
【2020新书】如何认真写好的代码和软件,318页pdf
专知会员服务
63+阅读 · 2020年3月26日
【2020新书】Kafka实战:Kafka in Action,209页pdf
专知会员服务
67+阅读 · 2020年3月9日
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
3 行代码 5 秒抠图的 AI 神器,根本无需 PS
大数据技术
20+阅读 · 2019年7月24日
Rollup构建发布一个Vue组件
前端黑板报
5+阅读 · 2019年7月13日
MPEG-DASH在bilibili的应用与实践
前端之巅
5+阅读 · 2019年4月29日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
XXEinjector – XXE自动化测试工具
黑白之道
5+阅读 · 2018年8月20日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
Arxiv
0+阅读 · 2020年12月3日
Arxiv
0+阅读 · 2020年12月2日
Arxiv
0+阅读 · 2020年11月29日
Arxiv
0+阅读 · 2020年11月28日
Arxiv
0+阅读 · 2020年11月26日
VIP会员
相关VIP内容
相关资讯
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
3 行代码 5 秒抠图的 AI 神器,根本无需 PS
大数据技术
20+阅读 · 2019年7月24日
Rollup构建发布一个Vue组件
前端黑板报
5+阅读 · 2019年7月13日
MPEG-DASH在bilibili的应用与实践
前端之巅
5+阅读 · 2019年4月29日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
XXEinjector – XXE自动化测试工具
黑白之道
5+阅读 · 2018年8月20日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
Top
微信扫码咨询专知VIP会员