文件结构与协议
🗓 2024年02月10日 📁 文章归类: 0x58_密码学
版权声明:本文作者是郭飞。转载随意,标明原文链接即可。
原文链接:https://www.guofei.site/2024/02/10/file.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 = ¢ral_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,但复杂很多
- header,包含一些元信息,例如文件列表、压缩方法、文件属性
- 压缩块,每个压缩块可以有自己的压缩算法,
- 每个压缩块可以有多个 stream,stream 存放了文件信息,也因此支持多线程压缩和解压缩
- 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和多个属性 | |||||||
| 压缩数据 | |||||||
| 结尾签名 | |||||||
图片类
关于多媒体各种压缩算法、协议:https://www.guofei.site/2024/03/02/digital_image_spatial2.html
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
模型文件
知识
- PyTorch 中,
.ckpt、.pt、.pth本质上都是用 Python 的pickle序列化出来的 - TensorFlow 中,是 protobuf+tensor 数据,而不是 pickle
- 通过前几个字节可以区分其为 PyTorch 还是 TensorFlow
torck>=1.6 其默认保存格式为 zip,就是用 zip 对 pickle 进行二次封装
.safetensors
整体结构:
[ u32 : header_len ]
[ header (JSON, UTF-8, 长度 = header_len) ]
[ data : 一串二进制 tensor 数据 ]
其中:
- u32:4 字节无符号整型,小端序,表示 header 的长度。
- header:JSON 格式的字符串,包含所有张量的元信息(名字、dtype、shape、data 在文件中的偏移量/长度等)。
- data:紧接在 header 后面,存放实际的张量二进制内容,按 header 中的 offset/length 对齐。
header 典型示例:
{
"weight1": {
"dtype": "F32",
"shape": [2, 2],
"data_offsets": [0, 16]
},
"embedding": {
"dtype": "I64",
"shape": [10000, 512],
"data_offsets": [16, 4096016]
},
"__metadata__": {
"format": "pt",
"arch": "TinyCNN"
}
}
特点:
- 跨语言、跨框架:任何能解析 JSON 和二进制的语言都能读。
- 零执行风险:没有 Python 对象序列化,只是结构化的字节流。
- 内存映射 (mmap):因为 header 里有偏移量,所以可以直接对文件做 mmap,按需读,不必一次性加载。
- 原子性:写文件时,先写临时文件,再 rename 替换,避免部分写入导致损坏。
- 对齐:通常按 64 字节对齐,保证高效读取。
字体文件
| 字体 | TTF | OTF |
|---|---|---|
| 字形 | 二次贝赛尔 | 三次贝赛尔 |
| 特点 | 更平滑,描述能力更强 | |
| 文件大小 | 较大,因为细节多 | 更紧凑 |
| 兼容性 | 更好 |
WOFF/WOFF2 是对 TTF/OTF 的压缩,其中 WOFF2 更现代
常见的文件头(转载)
文件格式 文件头(十六进制)
JPEG (jpg) FFD8FF
PNG (png) 89504E47
GIF (gif) 47494638
TIFF (tif) 49492A00
Windows Bitmap (bmp) 424D
CAD (dwg) 41433130
Adobe Photoshop (psd) 38425053
Rich Text Format (rtf) 7B5C727466
XML (xml) 3C3F786D6C
HTML (html) 68746D6C3E
Email [thorough only] (eml) 44656C69766572792D646174653A
Outlook Express (dbx) CFAD12FEC5FD746F
Outlook (pst) 2142444E
MS Word/Excel (xls.or.doc) D0CF11E0
MS Access (mdb) 5374616E64617264204A
您的支持将鼓励我继续创作!