开发者工具 · SHA 哈希计算器
相同文本,不同 SHA-256:换行符、编码和隐藏字节
· 工作原理
sha-256 编码 文本处理 调试
对于看起来相同的文本,命令行说的是一件事,浏览器说的是另一件事。尾随换行符、UTF-16 和 CRLF 解释了几乎所有情况;这篇文章展示了如何找到隐藏的字节。
echo 说一个哈希值,该工具说另一个哈希值 - 日常不匹配以及为什么两者都没有错
终端报告一个 SHA-256 摘要,浏览器报告另一个看似相同的文本。命令行工具没有损坏,浏览器也没有。尽管可见字符看起来相同,但被散列的字节并不相同。这篇文章追踪了这些隐藏字节的最常见来源,并展示了如何使用文本字段和十六进制查看器找到它们。
问题几乎从来都不是 SHA-256 算法本身。 SHA-256 是确定性的:相同的字节总是产生相同的摘要,并且摘要是正确的。当输出不同时,字节也不同。之所以会出现这种混乱,是因为“相同的文本”是不明确的:一个人看到的是字符,但哈希函数看到的是字节,而它们之间的翻译就是隐藏差异的地方。
尾随换行符 — echo 如何附加字节而 printf 不附加字节,以及这对摘要有何作用
shell 中的 echo 命令将换行符(U+000A,字节 0x0A)附加到其输出。这是设计使然:Unix 中文本文件以换行符结尾的惯例使 echo 成为一项简单的工作。当您在终端中输入 echo abc 并将其通过管道传输到 sha256sum 时,摘要的字节是 61 62 63 0A(a、b、c 的 ASCII 代码和换行符的字节),而不是 61 62 63。 ToolAcre 用于计算哈希值的工具对字节 61 62 63 进行哈希计算并产生不同的结果。
printf 命令不会添加换行符,除非您将换行符写入格式字符串。打印 abc | sha256sum 单独计算字节 61 62 63 的摘要,与浏览器工具匹配。这就是为什么比较哈希值通常意味着运行 printf 而不是 echo,或者使用 -z 标志通过管道传输到 sha256sum,或者以工具提供的任何形式指定原始输入。隐藏的换行符是浏览器工具和命令行工具不一致的最常见原因。
UTF-8 与 UTF-16 — 为什么相同的字符在某些 shell 和编辑器中是不同的字节
UTF-8 和 UTF-16 将相同的字符编码为不同的字节序列。字符 é(U+00E9,带有锐音符的 e)编码为两个 UTF-8 字节:0xC3 0xA9。在 UTF-16(JavaScript 内部表示字符串的方式)中,相同的字符以不同的顺序(取决于字节顺序)占据两个字节,或者如果由基本字符和组合标记组成,则完全不同的形式。当您从 Windows 应用程序复制咖啡馆并将其粘贴到浏览器哈希工具中时,该工具哈希的字节可能与 Mac 终端哈希的字节不匹配,因为系统默认采用不同的编码或规范化形式。
ToolAcre 哈希工具在通过 TextEncoder 进行哈希处理之前将文本显式转换为 UTF-8。这与 Unix 命令行默认使用的编码相同。 apps/dev/src/lib/base64.js 中的源代码显示函数 textToBytes 调用 new TextEncoder().encode(),这保证了 UTF-8。如果另一个系统使用 UTF-16 或 Latin-1 或任何其他编码,则生成的字节将有所不同。该工具在摘要旁边显示字节计数,这就是为什么如果编码不同,粘贴咖啡馆并与命令行哈希进行比较将显示不同的字节计数。
CRLF、BOM 和标准化 — 行结尾、字节顺序标记以及组合重音与分解重音作为不可见的输入差异
CRLF(回车+换行,字节0x0D 0x0A)是Windows上的行结束约定; LF(单独换行,字节 0x0A)是 Unix 约定。在文本编辑器中打开时看起来相同的文本文件可以带有不同的行结尾,并且这些字节是哈希输入的一部分。如果一个系统已转换行结尾而另一个系统未转换,则在 Windows 上编辑并对照在 Unix 系统上计算的 SHA-256 检查的文件将不匹配。
字节顺序标记(BOM,UTF-8 的字节 0xEF 0xBB 0xBF)是位于文件开头的可选序列,用于表示编码。一些编辑添加了它;有些工具会剥离它;有些人忽视它。如果文件带有 BOM,并且您逐字节对其进行哈希处理,则 BOM 字节就是摘要的一部分。如果您随后将可见文本(查看者隐藏了 BOM)复制到不添加 BOM 的工具中,则摘要将不匹配。文本规范化形式(用于组合和分解重音的 NFD 与 NFC)添加了另一层:相同的重音字母可以表示为单个预组合字符或表示为后跟组合重音的基本字符,并且字节序列不同。
工作示例 - 一个字符串使用或不使用换行符进行哈希处理,然后采用两种编码,显示每个字节的差异
诊断方法一:使用十六进制转储工具或在线转换器来准确查看工具正在操作的字节。将文本粘贴到 Base64 编码器中,对其进行编码,然后您就拥有了字节的文本记录。然后在命令行上使用 base64 -d 解码 Base64 并将其通过管道传输到 od -A x -t x1z 以查看十六进制字节序列。如果字节匹配,则算法正确;如果不这样做,差异就会显而易见。
诊断方法二:使用 ToolAcre SHA 哈希计算器对逐渐变长的输入进行哈希处理,从单个字符开始。添加换行符(这意味着在文本框中键入 Enter),添加空格,如果输入来自非 ASCII 源,则添加带有 UTF-16 转义序列的相同文本。观察每次添加后摘要的变化。摘要旁边显示的字节计数告诉您该工具正在散列多少字节,这极大地缩小了搜索范围。
输出中的十六进制大小写和空格 - 纯粹是装饰性的差异
摘要的十六进制表示形式不区分大小写。大写和小写字母都代表相同的字节:A = 10,a = 10。有些工具发出大写字母,有些工具发出小写字母,有些则允许两者。如果一个摘要是小写,另一个摘要是大写,则它们是相同的摘要。摘要显示中的空白纯粹是装饰性的。显示为 ba78 16bf 与 ba7816bf 的摘要是相同的;空格只是一种格式选择。由大小写或空格引起的不匹配并不是真正的不匹配。
固定宽度格式差异在字节级别也是不可见的。用连字符、空格或冒号显示的摘要(如 ba-78-16-bf)是一种格式约定,使人们更容易阅读,而不是更改实际字节。 ToolAcre 工具始终发出不带分隔符的小写字母,这是大多数命令行工具打印的格式。如果您要与发出不同信号的工具进行比较,请先转换为相同的表示形式。
这不包括什么 - 散列文件,其中应用相同的原则,但字节来自磁盘而不是文本字段
ToolAcre SHA 哈希计算器在哈希之前执行 UTF-8 转换,显示输入字节计数,并提供 base64 和十六进制输出形式。源文件apps/dev/src/lib/hash.js显示了调用digestBytes的hashText函数,该函数将bytes.slice().buffer传递给crypto.subtle.digest。该文件中的注释明确记录了 UTF-8 步骤是有意为之,并注意不同编码之间的差异。根据已知向量(十六进制的 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad)测试您的输入,确定浏览器工具正常工作;任何偏差都表明输入中存在字节差异。
文件散列遵循相同的原则。文件中的字节至关重要:从一个系统导出并导入到另一个系统时的行结束差异可能会更改每个摘要。有些工具提供了在比较期间处理行结尾的选项;其他人按原样散列文件。了解您的工具是将文件散列为二进制还是首先执行文本规范化对于再现性至关重要。
要点:哈希字节,而不是文本 — ToolAcre SHA 哈希计算器会对您粘贴的字节进行哈希处理,因此请先检查您粘贴的内容
仅当散列相同字节时比较和验证才有效。首先确认您正在散列完全相同的输入:运行 echo -n (或 printf)而不是 echo 以避免换行符,如果您的工具允许,则显式指定 UTF-8 编码,检查 CRLF 是否未被编辑器或系统实用程序插入。然后并排使用 ToolAcre 计算器和命令行工具进行哈希处理。如果摘要匹配,则字节相同。如果不存在,请使用字节计数显示和十六进制转储方法来查找隐藏的差异。
了解字节分歧的位置后,您可以选择是否对它们进行标准化以进行比较。某些校验和旨在验证文件的完整性是否与磁盘上存在的文件完全一致,在这种情况下,目标是逐字节散列。其他的目的是验证可见内容是否相同,在这种情况下规范化行结尾和编码是正确的。两者都没有错;他们回答不同的问题。 SHA-256 算法总是正确的;问题只是你是否要求它在两种情况下对相同的输入进行哈希处理。