内容一样,gzip 文件的哈希为什么变了?检查时间戳和原始文件名
比较两份 gzip 产物时,先分清需要稳定的是压缩包字节还是解压内容。gzip 头部可以带时间戳和原始文件名,输入完全一致时,这些字段也会改变产物哈希。用 Python 生成可复现的 gzip 文件时,可明确指定 mtime=0 和 filename=""。
1. 每次只改变一个字段
将以下代码保存为 gzip_header_lab.py,用 Python 运行。样本固定输入和压缩级别,使用内存文件观察 gzip 字节。
from hashlib import sha256
import gzip
from io import BytesIO
payload = b"build artifact\n" * 20
def compress(filename, mtime):
output = BytesIO()
with gzip.GzipFile(filename=filename, fileobj=output, mode="wb",
compresslevel=6, mtime=mtime) as stream:
stream.write(payload)
return output.getvalue()
baseline = compress("", 0)
for label, data, same in [
("timestamp", compress("", 1700000000), False),
("filename", compress("report.txt", 0), False),
("fixed metadata", compress("", 0), True),
]:
mtime = int.from_bytes(data[4:8], "little")
has_filename = bool(data[3] & 0x08)
same_hash = sha256(baseline).digest() == sha256(data).digest()
assert gzip.decompress(data) == payload
assert same_hash == same
print(f"{label}: mtime={mtime} fname={has_filename} same_sha256={same_hash}")
timestamp: mtime=1700000000 fname=False same_sha256=False
filename: mtime=0 fname=True same_sha256=False
fixed metadata: mtime=0 fname=False same_sha256=True
2. 找出真正需要稳定的构建层
gzip 固定头部从偏移 4 开始存放四字节 MTIME,偏移 3 是标志字节。设置 FNAME 位表示头部随后带有以零字节结束的原始文件名。一般排查可以先对两份压缩包做解压并比较内容哈希,再检查头部。如果解压结果已经不同,应回到生成输入的阶段查换行、编码或记录顺序。
固定这两个字段以后,同一工具链内仍应在干净目录重复构建,比较整份产物的 SHA-256。跨环境发布时还应固定压缩器版本和压缩级别。若 gzip 包裹的是 tar,内部条目名、顺序、权限和时间也可能改变待压缩字节,需在打包阶段另行控制。
小结
gzip 有自己的元数据。固定头部字段后,再用产物哈希验证构建是否可复现。
数据来源与检索时间
| 来源 | URL | 检索日期 | 对应内容 |
|---|---|---|---|
| RFC 1952 §2.3.1 | https://www.rfc-editor.org/rfc/rfc1952.html#section-2.3.1 | 2026-11-13 | gzip 成员头部的 MTIME 和 FNAME |
| Python gzip.GzipFile | https://docs.python.org/3.11/library/gzip.html#gzip.GzipFile | 2026-11-13 | filename、mtime 与压缩级别参数 |
核验说明
- Windows、Python 3.11.9 对三个 gzip 样本分别核对头部、解压内容和 SHA-256。跨工具链字节一致性需在实际构建环境验证。