SHA-256校验不一致:先检查末尾换行和Unicode字节
排查文本摘要不一致时,先检查输入的原始字节,再怀疑算法。肉眼相似的内容,可能因为换行、前导空格或 Unicode 表示方式不同,得到不同的 SHA-256。下面用六组可复现输入说明。
1. 六组输入的实际结果
本次使用 Node.js 内置 crypto 模块,以 UTF-8 编码计算 SHA-256,并用 Buffer.byteLength 检查字节数。表中摘要只显示前24位,便于对照;完整摘要是64位十六进制字符。
| 输入(JSON字符串表示) | UTF-8字节数 | SHA-256前24位 |
|---|---|---|
"hello" |
5 | 2cf24dba5fb0a30e26e83b2a |
"hello\n" |
6 | 5891b5b522d5df086d0ff0b1 |
" hello" |
6 | 1c9c535c80b3298829957b9f |
"你好" |
6 | 670d9743542cae3ea7ebe36a |
"é" |
2 | 4a99557e4033c3539de2eb65 |
"é" |
3 | bf12767b0f2a56b2190075ba |
其中,"hello" 与 "hello\n" 只差一个末尾换行,字节数由5变为6,摘要也随之改变。前导空格同样属于数据,不能在校验前随意调用 trim()。
两种看起来都是 é 的写法也不同:U+00E9 的 UTF-8 编码是2字节;字母 e 加组合重音 U+0301 是3字节。摘要不会因为它们显示相似就自动将二者视为相同。
2. 在浏览器中复现
以下代码应在支持 Web Crypto 的安全上下文中运行,例如 HTTPS 页面。它保留传入字符串中的空格和换行,再将文本转换为 UTF-8 字节。
async function sha256Text(text) {
const bytes = new TextEncoder().encode(text);
const result = await crypto.subtle.digest("SHA-256", bytes);
const hex = Array.from(new Uint8Array(result), b =>
b.toString(16).padStart(2, "0")
).join("");
return { bytes: bytes.length, hex };
}
const samples = ["hello", "hello\n", " hello", "你好", "\u00e9", "e\u0301"];
for (const text of samples) {
console.log(JSON.stringify(text), await sha256Text(text));
}
复制样例时请保留代码中的反斜杠;JavaScript运行后,\n表示一个换行字符,\u00e9和e\u0301分别产生上述两种Unicode表示。
3. 文件校验要直接读取文件字节
文本摘要与文件摘要的输入边界不同。文件可能包含 BOM、CRLF 或其他不能仅凭显示效果确认的字节。校验原文件时,可以直接读取 ArrayBuffer:
async function sha256File(file) {
const result = await crypto.subtle.digest("SHA-256", await file.arrayBuffer());
return Array.from(new Uint8Array(result), b =>
b.toString(16).padStart(2, "0")
).join("");
}
不要先把文件显示到文本框、复制出来,再把结果当成原始文件摘要。读取为文本以及编辑、保存等环节,都可能改变换行或编码。文件模式直接处理文件字节,更适合与下载页提供的校验值对照。
4. 排查时固定这三个条件
- 输入边界:是否多了末尾换行或首尾空格?日志中的显示文本不一定就是参与计算的字节。
- 编码规则:双方是否使用同一种编码?本例统一使用UTF-8。
- 规范化规则:仅在协议明确要求时统一Unicode表示、换行或空白;不要为了让摘要一致而临时修改原始数据。
普通SHA-256摘要用于检查字节一致性,不应直接作为密码存储方案。若业务需要对消息做带密钥的完整性校验,应按具体协议选用HMAC等机制。
实现依据:WHATWG Encoding Standard中的TextEncoder接口,以及W3C Web Cryptography规范中的SubtleCrypto.digest方法。本文代码与表格由上述六组输入实际运行获得,正文为原创整理。
- 点赞
- 收藏
- 关注作者
评论(0)