相同字符串的 SHA256 不同值

Ans*_*mar 3 javascript cryptography sha256

我正在生成以下字符串的 SHA256

{
    "billerid": "MAHA00000MUM01",
    "authenticators": 
    [
        {
            "parameter_name": "CA Number",
            "value": "210000336768"
        }
    ],
    "customer": 
    {
        "firstname": "ABC",
        "lastname": "XYZ",
        "mobile": "9344895862",
        "mobile_alt": "9859585525",
        "email": "abc@billdesk.com",
        "email_alt": "abc2@billdesk.com",
        "pan": "BZABC1234L",
        "aadhaar": "123123123123"
    },
    "metadata": 
    {
        "agent": 
        {
            "agentid": "DC01DC31MOB528199558"
        },
        "device": 
        {
            "init_channel": "Mobile",
            "ip": "124.124.1.1",
            "imei": "490154203237518",
            "os": "Android",
            "app": "AGENTAPP"
        }
    },
    "risk":
    [
        {
          "score_provider": "DC31",
          "score_value": "030",
          "score_type": "TXNRISK"
        },
        {
          "score_provider": "BBPS",
          "score_value": "030",
          "score_type": "TXNRISK"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

我从不同来源获得不同的 SHA256 输出。本网站:https ://www.freeformatter.com/sha256-generator.html#ad-output 计算上述字符串的SHA256:053353867b8171a8949065500d7313c69fe7517c9d69eaff11164c35f7b144

这个网站(https://emn178.github.io/online-tools/sha256.html)给出的SHA256为eae5c26759881d48a194a6b82a9d542485d6b6ce96297275c136b1fa6712f25

我在 Javascript 中使用 CryptoJs 库来计算 SHA256,这也给出了 eae5c26759881d48a194a6b82a9d542485d6b6ce96297275c136b1fa6712f253 这个结果。

我希望 SHA256 计算为:053353867b8171a8949065500d7313c69fe7517c9d69eaff11164c35fcb14457

为什么这些在不同地方的 SHA256 计算有所不同?

Maa*_*wes 5

您遇到的问题是由于编码差异造成的。相同字符串的编码可能产生不同结果的原因有多种:

  • 不同的行尾(Windows 为 CR/LF,Linux 为 LF,经典 MacOS 为 CR);
  • 空格的其他差异(制表符或空格、行尾中的空格);
  • 不同的字符编码(Windows-1252、UTF-8 和 UTF-16 或语言实现中的内部字符表示);
  • 元信息的存在(字节顺序标记的存在);
  • 在编码中处理特殊字符的不同方法(一个字符后跟一个组合波浪号和带有组合波浪号的字符,请参阅Unicode 等价);

也有可能产生不同结果的不可见错误:

  • 存在不可打印的字符/控制代码(0x00字符串末尾的空值 ,可能是最好的例子);

除了任何(结构化)文本可能存在的所有这些差异之外,JSON 数据结构也可能具有等效值。最好的例子可能是+数字前的前导字符。这完全是虚假的,但仍会导致不同的文本表示,但数字的值相同。


如果字符串的编码不同,则散列算法的二进制输入不同,您将得到与普通加密散列的比特位相差约 50% 的结果。产生相同输入的方法称为规范化(或 C14N,因为规范化的 C 和 N 之间有 14 个字符)。

很久以前就为 XML定义了规范形式。对于 JSON,情况并非如此,即使 JSON 的规范化会容易得多。毕竟,JSON 的规则集要少得多。有尝试规范化 JSON,参见例如这个草案 RFC明确提到加密哈希:

例如,当加密散列应用于 JSON 文档时,单个物理表示允许散列通过删除内容在 JSON 中编码方式的变化来表示文档的逻辑内容。

顺便说一下,这个 RFC 草案看起来更彻底一些。


现在,您可以继续使用 RFC 草案之一。如果您想保留换行符,那么您可以使用这些定义明确的规则序列化 JSON,并将其用作哈希函数的输入,同时保持 JSON 本身不变。这样不同格式的JSON 仍会生成相同的哈希值。

[Input JSON] -> (parse) -> (canonicalize & serialize) -> (hash) -> [hash value]
[Input JSON'] -> (parse) -> (canonicalize & serialize) -> (hash) -> [hash value']
Run Code Online (Sandbox Code Playgroud)

如果Input JSONInput JSON'在结构/语义上相同,则哈希输出将相同,因为规范化将消除差异。


请注意,JSON Web 签名 (JWS) 方面解决了这个问题。毕竟,签名在内部使用散列。签名在包含的有效载荷上,并且简单地使用该有效载荷的编码。只要中间系统不重新编码 JSON,这就没问题。签名不必完全相同,他们只需要验证数据即可。

不幸的是,哈希不是这种情况。但是,在实践中,您可以将 JSON 定义为文件并使用相同的推理。缺点当然是,如果您得到差异,则必须执行二进制比较以找到差异,然后追溯引入更改的位置。工作系统可能会在语义仍然相同的情况下破坏散列(例如,在替换或更新 JSON 库时)。