数据未变,检查却失败
AI 流水线收到两个配置文件,一个紧凑,另一个包含换行与空格,字段顺序也不同。解析后得到相同对象,哈希却不同。第二个文件算修改吗?取决于我们识别的是文件本身还是其表示的数据。混淆这两个问题,既可能产生无谓警报,也可能抹去重要差异。
如何选择符合检查目的的一致性定义?我们通过可复现案例研究 JSON 序列化,也就是把数据转为文本,并对字节计算 SHA-256。还会解释,与清单哈希一致为什么不能自动证明文件由谁生成。不攻击真实系统,不认证基础设施;文档均为合成,代码仅使用 Python 标准库。
需要区分的三个层次
字节层相等要求每个位置的字节一致。数据层中,本例解析后的 JSON 对象具有相同键和值即视为相同,不赋予属性顺序意义。应用层还可定义其他等价关系,但必须明确;本契约中整数版本 1 与字符串“1”不可互换。视觉相似不足以确定等价。
SHA-256 将字节序列映射为 256 位摘要,通常写作 64 个十六进制字符。它是确定性的,相同字节得到相同摘要。但输入空间大于摘要空间,所以哈希相等不是输入完全相同的数学证明;本实验也直接比较字节。哈希不同证明输入字节不同,却不证明应用含义不同。
两个文件,一个对象
示例对象的 name 为 café,version 为整数 1。第一种序列化先写 name,再写 version,无空格,é 直接编码为 UTF-8。第二种反转顺序、添加缩进,并用 JSON 转义表示非 ASCII 字符。解析后对象相同,文件却分别为 28、41 字节,摘要不同。结果保存文本、字节十六进制及完整 SHA-256,因此比较不依赖截断哈希。
在声明范围内选择稳定表示
若要比较数据而非格式,定义本地函数 C:排序键、使用紧凑分隔符、保留字符串并编码为 UTF-8。实验只接受 ASCII 键对象、可编码的 Unicode 字符串、列表及 −(2^53−1) 至 2^53−1 的整数,拒绝浮点数、布尔值等类型。范围刻意缩小以便理解结果,它不是通用 JSON 规范化库。
对两个示例对象,C 生成相同的 28 字节和摘要。在这个范围内,排版和属性顺序不再影响比较。但这不意味着哈希前的任意变换都合理。如果删除重要字段,或把版本统一变成字符串,就可能隐藏重要变化。稳定表示必须保留应用需要区分的内容。
看起来相同的字符串
现在用两种方式写 café:单个码点 é(U+00E9),或 e 后接组合重音 U+0301。显示可能相同,字符串却不同。本例组合文档为 28 字节,分解文档为 29;C 保留差异,摘要不一致。代码还展示显式 Unicode NFC 处理可使这两条字符串相同,但不把它悄悄加入 C。对标识符而言,改变字符可能改变需要保护的数据。
| 配对 | 对象相同 | 字节相同 | 摘要相同 |
|---|---|---|---|
| raw1 / raw2 | T | F | F |
| C(raw1) / C(raw2) | T | T | T |
| 组合 / 分解 | F | F | F |
T 表示真,F 表示假。C(raw1) 是先解析再应用 C 的简写,C 操作数据而非原始文本。各行是指定文件的实际比较,不是百分比或碰撞概率。图中将字节大小和逻辑比较结果分开展示。

JCS 参考与本代码边界
2020 年 6 月的信息性文档 RFC 8785 描述 JSON Canonicalization Scheme(JCS)。已阅读的技术规则涉及数字、属性和字符串:不应用 Unicode 归一化,键按 UTF-16 代码单元排序。Python sort_keys 不能证明普遍符合 JCS;代码中的范围外例子 U+E000 与 U+10000 在码点排序和 UTF-16 排序下顺序不同,也未实现 ECMAScript 数字序列化。因此 C 是本地约定,不是“一行实现 JCS”。
参考哈希需要可信来源
把 version 从 1 改为 2,摘要改变,与可信原始摘要比较会失败。但若替换文档的人也能把不可信清单替换为新摘要,比较就会通过,代码验证的正是这一点。这不是碰撞,也不是 SHA-256 的数学弱点,而是参考被替换了。哈希本身不能把内容绑定到身份或批准时间。
签名或认证码可在适当密钥与信任策略下回答其他问题,但本文不实现它们,也不把实验称为密码学审计。真实来源文件仍可能含错误数据。相对参考的完整性、来源和内容正确性需要不同证据。规范化让约定的比较可重复,却不决定哪个来源值得信任。
实际应用与限制
本次编辑流程确实记录了 SHA-256,并比较从网站下载的素材与准备文件的字节。这检查的是这些文件的传输,不是 EL-AI 安全产品,也不是人工科学审核证据。若要保留 PNG 或 ZIP 原件,就不应先转换它,因为字节一致正是目标。若要比较不受排版影响的 JSON 配置,则需定义并版本化表示契约。
实验未执行 JCS 符合性测试套件,不测量吞吐量,也不实现完整恶意输入协议。契约在序列化前拒绝多种类型,测试记录拒绝而不假装支持。排序 m 个键约需 m log m 次比较,另加读写内容的成本,实际成本还依赖字符串长度。不能从算法描述推断性能测量结果。
答案:先决定需要哪种相等
示例文件哈希不同,是因为字节不同,即使解析对象相同。稳定本地表示消除约定的格式差异,保留数据差异,却不创造来源保证,也不替代可信参考。检查校验和之前,要先决定什么必须一致,以及谁保证预期值。契约与计算结合,验证才有意义。
来源与已运行代码
RFC 8785 — JSON Canonicalization Scheme, §§3.1–3.2, 5.
import json,hashlib
a={'name':'café','version':1}
b={'version':1,'name':'café'}
x=json.dumps(a,ensure_ascii=False,separators=(',',':')).encode('utf8')
y=json.dumps(b,ensure_ascii=True,indent=2).encode('utf8')
assert json.loads(x)==json.loads(y)
assert x!=y and hashlib.sha256(x).digest()!=hashlib.sha256(y).digest()
print(len(x),len(y)) # 28 41
代码、数据与说明 · JSON. 教学计算使用 Python 3.14.0,图使用 Matplotlib 3.11.2。分析由 AI 辅助,不声称经过同行评审或人工审核。原创 ImageGen 封面仅作示意,不记录 EL-AI 人员、场所或实际安装。来源查阅于 2026 年 10 月 21 日。

