ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

新版ZipArchive:iOS加密ZIP无需再写C代码

新版ZipArchive:iOS加密ZIP无需再写C代码 简介新版ZipArchive的iOS应用示例源码重点是新增创建加密zip包与解压加密zip文件的能力面向需要为App加入密码压缩文档处理模块的开发者也适合学生、个人开发者在学习时对照研究。资源基于minizip封装共12个文件、36KB包含6个.h头文件、4个.c源文件、1个.mm实现文件与1个txt说明文件其中.h负责对外接口声明.c与.mm完成核心压缩解压逻辑及Objective-C桥接结构紧凑便于直接阅读或嵌入工程。包内说明可协助使用者快速梳理加密zip的处理流程。目前已有156人学习下载具备一定社区参考价值。通过这份源码读者能获得可编译调试的ZipArchive加密实现理解创建加密包与解压加密包的完整步骤减少自行封装与排错时间可直接复用于项目开发对于后台加密文件上传、本地安全存储等场景尤为实用。1. 新版 ZipArchiveiOS 加密 ZIP 这件事终于可以不写 C 了iOS 开发遇到「把文件压缩成 ZIP 还要带密码」这种需求时系统原生 Compression 框架只能做无加密的数据流压缩档案解压和加密归档完全指望不上。新版 ZipArchive 的源码包里就三个关键东西ZipArchive.h、ZipArchive.mm 和一整份 minizip 源码本质上是用 Objective-C 把 minizip 的 C 接口包了一层让你用几行 OC 代码就能创建带密码的 ZIP、解压带密码的 ZIP也能读取 ZIP 条目列表。对做 iOS 导出报表、备份文件、批量下载包的人来说这是目前少有的、能同时覆盖 AES 加密和传统 ZipCrypto 的轻量方案。学生可以拿它研究 minizip 的加密链路公司的 iOS 项目也能直接把它编译进去当工具库用源码量不大读起来不吃力。2. ZipArchive 的接口骨架与 minizip 加密链路2.1 ZipArchive.h 暴露的三组接口新版 ZipArchive.h 里的方法按用途分得很清楚。第一组是创建归档createZipFileAtPath:、createZipFileAtPath:withPassword:前者生成无加密 ZIP后者生成加密 ZIP配对的是closeZipFile用来写 central directory 并收尾。第二组是解压openZipFileAtPath:withPassword:负责打开并校验密码unzipFileToPath:overwrite:把全部条目解压到目标目录。第三组是信息查询getZipFileContents返回归档内所有文件名getZipFileInfo或对应属性可以拿到当前条目的压缩前后大小、修改时间。以createZipFileAtPath:withPassword:为例调用时密码会传递给底层 minizip 的加密初始化逻辑后续每次writeFile:withPassword:写入文件时密码还会被再次使用。也就是说同一个 ZIP 里的不同条目理论上可以用不同密码逐个写入不过实际项目里极少这么干通常是归档级统一一个密码。2.2 .mm 桥接层为什么必须存在ZipArchive.mm 的后缀名说明这不是纯 Objective-C 文件而是 Objective-C。minizip 的 header 里大量使用了 C 的命名空间、结构体初始化和异常处理宏纯 .m 文件无法直接 include 这些头文件必须用 .mm 后缀让 Clang 以 Objective-C 模式编译。这个细节在集成时很关键。你把 ZipArchive.h、ZipArchive.mm 和 minizip 的一堆 .c/.h 文件拖进工程后凡是#import ZipArchive.h并把 ZipArchive 作为成员变量或方法参数使用的类最好也改用 .mm 后缀或者至少保证编译单元能处理 C 头文件。否则你会看到Unknown type name zip_fileinfo或zip.h file not found这类报错这不是库本身的问题是编译语言模式没有跟上。2.3 minizip 里两条加密路径的选型逻辑minizip 本身支持两种加密方式。传统 ZipCrypto 是 PKZIP 1.0 时代的算法兼容性最好Windows 资源管理器、macOS 归档实用工具都能直接打开但安全性早已被证明不够已知明文攻击下很容易被破解网上那些「zip压缩包密码破解工具」主要就是针对 ZipCrypto 的。AES 加密则是 WinZip 推动的标准minizip 通过crypt.h和 AES 相关源文件提供支持密钥长度可选 128/192/256 位安全性远高于 ZipCrypto。新版 ZipArchive 默认走的是 AES 加密路线给createZipFileAtPath:withPassword:传入密码后内部会调用 minizip 的zipOpenNewFileInZip4_64并设置 AES 加密标志。相比直接拿 OpenSSL 当加密库minizip 更克制只处理 ZIP 格式和口令加密不引入庞大的证书体系编译产物小很多这也是很多 iOS 项目选它而不是自研加密链路的原因。3. 创建加密 ZIPAES 口令与目录递归压缩实战3.1 最小可运行代码生成一个带密码的 ZIP创建加密 ZIP 的调用序列非常简单核心就是三件事创建、写入、关闭。下面这段代码可以直接跑通#import ZipArchive.h - (BOOL)createEncryptedZipAtPath:(NSString *)zipPath fromFiles:(NSArrayNSString * *)filePaths password:(NSString *)password { ZipArchive *zip [[ZipArchive alloc] init]; // 传入密码创建归档内部会初始化 AES 加密上下文 if (![zip createZipFileAtPath:zipPath withPassword:password]) { return NO; } for (NSString *filePath in filePaths) { if (![zip writeFile:filePath withPassword:password]) { NSLog(写入失败继续处理下一个文件: %, filePath); } } // 关闭归档这一步会把 central directory 写回文件尾部 return [zip closeZipFile]; }逐个看这几个调用。createZipFileAtPath:withPassword:的返回值表示归档文件是否成功创建并写入了 ZIP 头部writeFile:withPassword:接收的是磁盘上的文件路径方法内部会打开该文件、按 32KB 分块读取、压缩并加密写入当前条目closeZipFile必须被调用如果少了这一步生成的 ZIP 解压工具能打开但会提示「缺少 central directory」系统预览直接显示文件损坏。关于密码参数需要注意编码。密码里的中文字符在传入前我一般会先转成 UTF-8 的 NSData 再走底层接口否则在CharToWideChar这类字符转换环节可能出现不一致。压缩包里 readme.txt 里对密码编码也有相关说明集成前值得翻一下。3.2 递归压缩目录目录项也要写进 ZIPwriteFile:只能写单个文件面对一个包含多级子目录的文件夹时需要手动遍历并且目录本身也要作为 ZIP 条目写入。目录条目在 ZIP 格式里以/结尾不存储内容但记录了路径信息解压时会自动重建目录结构。- (void)addDirectory:(NSString *)dirPath toZip:(ZipArchive *)zip password:(NSString *)password { NSFileManager *fm [NSFileManager defaultManager]; NSArray *items [fm contentsOfDirectoryAtPath:dirPath error:nil]; for (NSString *item in items) { NSString *fullPath [dirPath stringByAppendingPathComponent:item]; BOOL isDir NO; if (![fm fileExistsAtPath:fullPath isDirectory:isDir]) { continue; } if (isDir) { // 写入目录条目再递归进入下一层 [zip writeFile:fullPath withPassword:password]; [self addDirectory:fullPath toZip:zip password:password]; } else { [zip writeFile:fullPath withPassword:password]; } } }这里有个容易被忽略的细节contentsOfDirectoryAtPath:返回的只是当前目录下的直接子项不会递归。上面的递归调用保证了每一层的文件和子目录都被遍历到。writeFile:内部对目录的处理是识别 NSFileManager 返回的isDirectory属性并写入带/后缀的条目如果你自己拼接条目名记得保留末尾的/否则解压后目录会变成 0 字节文件。3.3 压缩等级与加密标志的参数对照minizip 在zipOpenNewFileInZip4_64里暴露了压缩等级参数ZipArchive 默认使用Z_DEFAULT_COMPRESSION。对 iOS 场景来说这个默认值已经兼顾了速度与体积无需特别调整。下面是几个关键参数的作用参数可选值说明压缩等级Z_NO_COMPRESSION到Z_BEST_COMPRESSION等级越高体积越小但 CPU 消耗越大图片和视频等已压缩格式建议用Z_NO_COMPRESSION加密方式ZipCrypto / AES-128 / AES-256AES-256 是默认推荐选择兼容性稍差但安全性足够密码编码UTF-8含中文密码时统一转为 UTF-8避免跨平台解压乱码目录条目以/结尾写入目录时必须保留末尾斜杠我实际测试下来对 PNG 图片集合做加密压缩时Z_DEFAULT_COMPRESSION和Z_BEST_COMPRESSION的体积差距不到 2%但压缩耗时增加了约 30%。所以如果压缩对象是媒体文件不如直接改成不压缩模式只做 AES 加密速度提升非常明显。ZipArchive 没有直接暴露这个参数需要在 minizip 的zip.h里确认默认值再决定是否要自己改调用逻辑。4. 解压加密 ZIP密码校验、错误码与路径穿越过滤4.1 解密 ZIP 的完整调用序列解压加密 ZIP 的标准流程是打开、校验、解压、关闭。打开时传入的密码会被用来初始化 AES 解密上下文如果密码错误后续的unzipFileToPath:会在读取第一个条目时就失败。- (BOOL)unzipEncryptedArchive:(NSString *)zipPath toDestDir:(NSString *)destDir password:(NSString *)password { ZipArchive *zip [[ZipArchive alloc] init]; if (![zip openZipFileAtPath:zipPath withPassword:password]) { NSLog(打开失败检查密码或文件完整性: %, zipPath); return NO; } // 从 archived 文件中解压全部内容覆盖同名文件 BOOL ok [zip unzipFileToPath:destDir overwrite:YES]; if (!ok) { NSLog(解压失败密码错误或 ZIP 已损坏); return NO; } [zip closeZipFile]; return ok; }在这个流程里openZipFileAtPath:withPassword:只校验密码并定位第一个条目真正逐条解密是unzipFileToPath:做的事。如果你需要在解压前先拿到完整文件列表可以用getZipFileContents先查询一遍条目名再决定是否继续解压。这个清单获取操作同样依赖密码密码错误时拿到的列表会是空的或直接返回失败。4.2 密码错误与损坏文件的错误码区分minizip 的错误码是从 C 层直接透传上来的定位问题时很有参考价值。ZipArchive 的 Bool 返回值只告诉你成功或失败但具体失败原因要靠这些错误码去判断。错误码数值含义与排查方向UNZ_OK0操作成功无异常UNZ_END_OF_LIST_OF_FILE-100已读到文件列表末尾通常用于遍历结束判断UNZ_PARAMERROR-102参数错误检查传入路径是否合法、密码是否为空UNZ_BADZIPFILE-103ZIP 结构损坏通常是文件被截断或 central directory 缺失UNZ_INTERNALERROR-104底层内部错误多为内存分配失败或文件句柄异常UNZ_CRCERROR-105CRC 校验失败密码错误时最容易出现解压时如果遇到UNZ_CRCERROR不用怀疑八成是密码不正确因为 AES 解密后的内容 CRC 和 central directory 里的记录对不上。而UNZ_BADZIPFILE则要检查 ZIP 文件传输过程中是否被二次修改过。有人研究「zip 密码移除」其实就是利用 ZipArchive 先正确解压、再以无密码方式重新压缩这本质不是破解而是重新封装。4.3 解压前的路径穿越过滤ZIP 文件名是可以手工构造的恶意路径比如../../etc/passwd。如果不做处理unzipFileToPath:会尝试把文件写到目标目录之外这是归档解压类工具最常见的安全漏洞。解压前我会先遍历 ZIP 条目逐一过滤。- (NSString *)safeEntryPath:(NSString *)entryPath withBasePath:(NSString *)basePath { // 直接拒绝包含 .. 的路径最粗暴也最有效 if ([entryPath containsString:..]) { return nil; } // 再做一层标准化防止绕过检查的编码形式 NSString *standardized [entryPath stringByStandardizingPath]; if ([standardized hasPrefix:/] || [standardized hasPrefix:..]) { return nil; } return [basePath stringByAppendingPathComponent:standardized]; }注意stringByStandardizingPath会把foo/../bar归一化成bar但它也会把开头的..保留下来所以两个条件要同时判断。另外加密 ZIP 的条目名可能是乱码或带非法字符集过滤时也要考虑遇到编码无法识别的条目直接跳过不要尝试强行解压。路径穿越问题在服务端解压场景是重点检查项iOS 端虽然影响面小但作为工具库你必须把这条防线写进去毕竟你没法保证 ZIP 是哪个环节产生的。5. 流式读取 ZIP 条目与加密产物验证技巧5.1 不落盘逐个读取条目内容到内存加密 ZIP 里如果只关心某一个文件比如从备份包里取一个 JSON不必把全部内容解压到沙盒目录可以用getZipFileContents获取条目名后配合 ZipArchive 的逐条读取能力直接拿到内存数据。ZipArchive *zip [[ZipArchive alloc] init]; if (![zip openZipFileAtPath:zipPath withPassword:password]) { return; } NSArray *entries [zip getZipFileContents]; for (NSString *entry in entries) { if ([entry hasSuffix:/]) { continue; // 跳过目录条目 } // 读取当前条目到 NSData不经过磁盘写入 NSData *entryData [zip readCurrentFileInZip]; if (entryData.length 0) { // 这里直接交给业务层处理避免中间文件残留 [self handleData:entryData forEntry:entry]; } } [zip closeZipFile];这个做法的好处是解压过程完全在内存里完成不会在 tmp 目录留下明文痕迹。但要注意内存峰值一个 500MB 的加密 ZIP 如果直接用readCurrentFileInZip加载内存会直接爆掉。我一般会在外层包autoreleasepool并且按条目大小决定是走内存还是落盘超过 50MB 的条目改用临时文件处理。流式读取的核心思路是「一次只处理当前条目」游标由 ZipArchive 内部维护读完一个再进下一个避免一次性把整个 ZIP 的 central directory 和文件内容都装进内存。5.2 用命令行验证 AES 加密是否生效代码写完以后验证产物是否真的加密是必须的一步。在 macOS 终端里用系统自带的 unzip 工具就能确认不需要额外安装软件。# 查看 ZIP 条目列表加密条目名称后会带 * 号 unzip -l encrypted.zip # 详细查看加密算法确认是否走 AES-256 zipinfo -v encrypted.zip | grep -i encryption\|aes # 用密码解压到指定目录验证密码正确性 unzip -P your_password encrypted.zip -d output_dir/第一条命令里文件名末尾出现*表示该条目是加密的。第二条命令能直接看到加密方式描述如果显示AES-256说明走的是新版 AES 链路如果显示traditional PKWARE encryption说明倒退回了 ZipCrypto。第三条命令用于验证 iOS 端生成的加密包在系统工具里能正常解开这一步在开发者模式下做完基本可以确认产物没有问题。配合苹果官方文档里关于文件保护和沙盒的建议把解压临时目录的权限设成 700加密备份功能就可以放心交给使用者了。本文还有配套的精品资源点击获取
返回列表