【zip】文件结构



2024年02月10日    Author:Guofei

文章归类: 0x58_密码学    文章编号: 59003

版权声明:本文作者是郭飞。转载随意,但需要标明原文链接,并通知本人
原文链接:https://www.guofei.site/2024/02/10/zip.html


zip 文件格式

1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节
本地文件头签名(Local File Header Signature)
值固定:[80, 75, 3, 4]
Version Needed to Extract General Purpose Bit Flag
是否用了加密、压缩
Compression Method
压缩算法
如:0代表无压缩,8代表使用Deflate算法
最后修改文件时间 最后修改文件日期 CRC-32校验
接上CRC-32校验 压缩后大小 压缩前大小 文件1文件名长度 文件1额外字段长度 文件1文件名(长度不固定)
文件1额外字段(长度不固定) 文件2文件名长度 文件2额外字段长度 文件2文件名(长度不固定) 文件2额外字段(长度不固定)
正文:文件本身内容…(大量数据)
【中央目录起始位置】
文件1文件名长度
文件1额外字段长度 文件1的文件注释长度 文件1的文件名
(长度不固定)
文件1的额外字段
(长度不固定)
结构为:2字节的keyId+2字节数据长度+2字节数据
文件1的注释(长度不固定)
文件2文件名长度 文件2额外字段长度 文件2的文件注释长度 文件2的文件名 文件2的额外字段 文件2的注释
文件3…
中央目录记录签名
值固定:[80, 75, 5, 6]
所在的磁盘编号
(用于跨磁盘zip文件)
单磁盘zip为0
中央目录起始部分的磁盘编号
单磁盘为0
本磁盘上中央目录文件数量
单磁盘zip值同后一个
中央目录中文件数量 中央目录总大小
中央目录起始位置 zip注释的长度 zip的注释
(长度不固定)

文件名、文件属性、文件注释在整个zip文件的头尾重复,原因是

  • 在文件头,就允许“流式”处理
  • 在文件尾放中央目录,使其可以对整个文件做完整性检查。

用 rust 把这些信息打印出来:

use std::io::{Cursor, Read, Seek, SeekFrom, Write};
let ori_filename = "./files/zip_file.zip";

let data_bytes = fs::read(ori_filename).unwrap();

// 确保文件至少有固定大小的文件头部分
if data_bytes.len() < 30 {
    return Err("文件太小,不是zip".to_string());
}

// 提取各个字段
let local_file_header_signature = &data_bytes[0..4];
let version_needed_to_extract = &data_bytes[4..6];
let general_purpose_bit_flag = &data_bytes[6..8];
let compression_method = &data_bytes[8..10];
let last_mod_file_time = &data_bytes[10..12];
let last_mod_file_date = &data_bytes[12..14];
let crc_32 = &data_bytes[14..18];
let compressed_size = &data_bytes[18..22];
let uncompressed_size = &data_bytes[22..26];
let file_name_length = &data_bytes[26..28];
let extra_field_length = &data_bytes[28..30];

// 打印各个字段
println!("本地文件头签名 (Local File Header Signature): {:?}", local_file_header_signature);
println!("版本需要 (Version Needed to Extract): {:?}", version_needed_to_extract);
println!("通用位标志 (General Purpose Bit Flag): {:?}", general_purpose_bit_flag);
println!("压缩方法 (Compression Method): {:?}", compression_method);
println!("最后修改文件时间 (Last Mod File Time): {:?}", last_mod_file_time);
println!("最后修改文件日期 (Last Mod File Date): {:?}", last_mod_file_date);
println!("CRC-32校验 (CRC-32): {:?}", crc_32);
println!("压缩后大小 (Compressed Size): {:?}", compressed_size);
println!("未压缩大小 (Uncompressed Size): {:?}", uncompressed_size);
println!("文件名长度 (File Name Length): {:?}", file_name_length);
println!("额外字段长度 (Extra Field Length): {:?}", extra_field_length);

// 根据文件名长度和额外字段长度提取并打印文件名和额外字段
let file_name_length = u16::from_le_bytes([file_name_length[0], file_name_length[1]]) as usize;
let extra_field_length = u16::from_le_bytes([extra_field_length[0], extra_field_length[1]]) as usize;

let file_name = &data_bytes[30..30 + file_name_length];
let extra_field = &data_bytes[30 + file_name_length..30 + file_name_length + extra_field_length];

println!("文件名 (File Name): {:?}", file_name);
println!("额外字段 (Extra Field): {:?}", extra_field);


println!("=========zip结尾存放中央目录============");


// 查找结束中央目录记录的签名
let eocd_signature = b"\x50\x4b\x05\x06";
if let Some(eocd_pos) = data_bytes.windows(4).position(|window| window == eocd_signature) {
    // 本磁盘上中央目录记录的文件数量
    let num_entries = u16::from_le_bytes([data_bytes[eocd_pos + 8], data_bytes[eocd_pos + 9]]);
    // 中央文件的起始位置
    let central_dir_offset = u32::from_le_bytes([
        data_bytes[eocd_pos + 16],
        data_bytes[eocd_pos + 17],
        data_bytes[eocd_pos + 18],
        data_bytes[eocd_pos + 19],
    ]) as usize;

    // 遍历中央目录记录
    let mut offset = central_dir_offset;
    for _ in 0..num_entries {
        let central_dir_record = &data_bytes[offset..];

        // 解析中央目录记录
        let file_name_length = u16::from_le_bytes([central_dir_record[28], central_dir_record[29]]) as usize;
        let extra_field_length = u16::from_le_bytes([central_dir_record[30], central_dir_record[31]]) as usize;
        let file_comment_length = u16::from_le_bytes([central_dir_record[32], central_dir_record[33]]) as usize;
        // extra_field:额外字段,根据需求放入信息,例如,文件的扩展属性,例如文件的创建时间、修改时间、访问时间。
        // extra_field:结构通常由两部分组成:一个两字节的标识符(Header ID)和一个两字节的数据长度字段(Data Size),后面跟随实际的数据(Data)。标识符用于标明extra_field的类型或用途,数据长度字段指示随后的数据部分的长度。
        // file_comment:文件的文本注释,


        // 提取文件名
        let file_name = &central_dir_record[46..46 + file_name_length];

        // 打印文件名
        println!("文件名: {}", String::from_utf8_lossy(file_name));

        // 更新offset以跳转到下一条目录记录
        offset += 46 + file_name_length + extra_field_length + file_comment_length;
    }

7z

7z介绍

  • 使用LZMA和LZMA2压缩算法,提供了高压缩比
  • 支持包括AES-256加密在内的强加密功能
  • 支持自解压档案,分卷压缩等特性。
  • 7-Zip软件是开源的

文件结构类似zip,但复杂很多

  1. header,包含一些元信息,例如文件列表、压缩方法、文件属性
  2. 压缩块,每个压缩块可以有自己的压缩算法,
    • 每个压缩块可以有多个 stream,stream 存放了文件信息,也因此支持多线程压缩和解压缩
  3. end header,提供了压缩包整体的附加信息,有时 header 是加密的,就需要用 end header 来解密
1字节 1字节 1字节 1字节 1字节 1字节 1字节 1字节
文件头签名
值为[55, 122, 188, 175, 39, 28]
7z版本
2字节
Start header的CRC
4字节
下一个头的偏移量
8字节
下一个头的长度
8字节
下一个头的 CRC
4字节
次头结构,包含ID和多个属性
压缩数据
结尾签名

png

png 的结尾是:

let png_end = [0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82]
  • 长度:前4个字节表示数据长度,对于IEND块,这四个字节的值都是0(即十六进制的 00 00 00 00),因为IEND块不包含任何数据。
  • 类型:接下来的4个字节指示块的类型。对于IEND块,这四个字节为 49 45 4E 44(即ASCII码中的IEND)。
  • CRC:最后是4个字节的CRC(循环冗余检验)检查值,用于检测在传输过程中块是否有损坏。IEND块的CRC值是固定的,十六进制表示为 AE 42 60 82。

JPG

开头是 0xFFD8,结尾是 0xFFD9

  • APP0,开头是 FFE0 。可以含有以下信息:
    • 标识字符串(如 “JFIF” 表示文件遵循 JFIF 约定)
    • 版本号
    • 像素密度设置
    • 缩略图信息
  • APP1,开头是 FFE1,存储 EXIF,包括但不限于:
    • 拍照时间和日期
    • 相机制造商和型号
    • 图像方向
    • 拍摄参数(如快门速度、焦距、光圈大小等)
    • GPS 信息(纬度、经度和其他相关参数)
  • APP2,开头是 FFE2 存储其他元信息,例如 ICC 颜色档案
  • APPn,开头是 FFEn

您的支持将鼓励我继续创作!