【问题标题】:How to cryptographically hash a JSON object?如何加密散列 JSON 对象?
【发布时间】:2011-06-07 21:58:47
【问题描述】:

以下问题比最初看起来要复杂。

假设我有一个任意 JSON 对象,它可能包含任意数量的数据,包括其他嵌套的 JSON 对象。我想要的是 JSON 数据的加密哈希/摘要,而不考虑实际的 JSON 格式本身(例如:忽略 JSON 令牌之间的换行符和间距差异)。

最后一部分是一项要求,因为 JSON 将由许多不同平台上的各种(反)序列化程序生成/读取。我知道至少有一个用于 Java 的 JSON 库可以在反序列化期间读取数据时完全删除格式。因此它会破坏散列。

上面的任意数据子句也使事情复杂化,因为它阻止我以给定的顺序获取已知字段并在拥有之前将它们连接起来(大致想想 Java 的非加密 hashCode() 方法是如何工作的)。

最后,将整个 JSON 字符串散列为一个字节块(在反序列化之前)也是不可取的,因为在计算散列时应该忽略 JSON 中的某些字段。

我不确定这个问题是否有好的解决方案,但我欢迎任何方法或想法 =)

【问题讨论】:

标签: json cryptography canonicalization


【解决方案1】:

在为任何允许灵活性的数据格式计算哈希时,这个问题很常见。为了解决这个问题,您需要规范化表示。

例如,Twitter 和其他服务用于身份验证的 OAuth1.0a 协议需要请求消息的安全哈希。要计算散列,OAuth1.0a 说您需要首先按字母顺序排列字段,用换行符分隔它们,删除字段名称(众所周知),并为空值使用空行。签名或散列是根据该规范化的结果计算的。

XML DSIG 的工作方式相同 - 您需要在签署 XML 之前对其进行规范化。有一个proposed W3 standard covering this,因为它是签名的基本要求。有些人称之为c14n。

我不知道 json 的规范化标准。值得研究。

如果没有,您当然可以为您的特定应用程序使用建立一个约定。一个合理的开始可能是:

  • 按名称按字典顺序对属性进行排序
  • 所有名称都使用双引号
  • 所有字符串值都使用双引号
  • 名称和冒号之间以及冒号和值之间没有空格或一个空格
  • 值和以下逗号之间没有空格
  • 所有其他空白都折叠成一个空格或什么都没有 - 选择一个
  • 排除您不想签名的任何属性(例如,拥有签名的属性)
  • 使用您选择的算法对结果进行签名

您可能还想考虑如何在 JSON 对象中传递该签名 - 可能建立一个众所周知的属性名称,例如“nichols-hmac”或其他东西,它可以获取 base64 编码版本的哈希。散列算法必须明确排除此属性。然后,JSON 的任何接收者都可以检查散列。

规范化的表示不必是您在应用程序中传递的表示。它只需要在给定任意 JSON 对象的情况下轻松生成。

【讨论】:

  • 规范化还必须考虑字符的表示:"A" vs "\u0041""é" vs "\u00e9" vs "\u00E9"。同样的数字问题:1 vs 0.1e1
  • 规范化还必须考虑数字。 ECMAscript 定义了 JSON.stringify,它表示如果数字在 [1e-6, 1e21) 范围内,则格式化没有指数的数字;否则用小数点前1位格式化。
【解决方案2】:

您可能想要使用bencode,而不是发明自己的 JSON 规范化/规范化。从语义上讲,它与 JSON(数字、字符串、列表和字典的组合)相同,但具有加密哈希所必需的明确编码属性。

bencode用作torrent文件格式,每个bittorrent客户端都包含一个实现。

【讨论】:

  • JSON 是首选,因为几乎每种语言都有可用于进行对象(反)序列化的库。
  • 我的意思是仅使用 bencode 作为散列之前的规范化步骤。在您的哈希例程之外,所有内容都保持为 JSON。
  • bencode 非常棒,而且超级容易实现。规范 JSON 也不会使用标准 JSON 解析器进行解析。对于这个只需要哈希函数输入的应用程序,两者都不需要解析。
  • +1 这个答案 - 我在一个名为 Learning Registry 的 OSS 项目上工作,它是一个分布式 JSON 数据库。我们必须在每个 JSON 文档进入数据库之前对其进行签名。为了实现这一点,我们(除其他外)在签署 b/c 之前将 JSON 转换为 Bencode。Bencode 是一种可靠的语义表示,而 JSON 不是(根据我们的经验)。
  • bencoding 仅编码字节字符串,而 JSON 编码 Unicode 字符串。所以你必须在 bencode 之上设计一个 JSON 字符串规范化。而且 bencode 不编码 JSON 具有的浮点值。
【解决方案3】:

这与导致 S/MIME 签名和 XML 签名出现问题的问题相同。也就是说,要签名的数据有多种等效表示。

以 JSON 为例:

{  "Name1": "Value1", "Name2": "Value2" }

对比

{
    "Name1": "Value\u0031",
    "Name2": "Value\u0032"
}

或者根据您的应用程序,这甚至可能是等效的:

{
    "Name1": "Value\u0031",
    "Name2": "Value\u0032",
    "Optional": null
}

规范化可以解决这个问题,但这是你根本不需要的问题。

如果您可以控制规范,那么简单的解决方案是将对象包装在某种容器中,以防止其被转换为“等效”但不同的表示形式。

即通过不签署“逻辑”对象而是签署它的特定序列化表示来避免问题。

例如,JSON 对象 -> UTF-8 文本 -> 字节。将字节作为字节签名,然后将它们作为字节传输,例如通过base64编码。由于您正在对字节进行签名,因此空格等差异是签名内容的一部分。

而不是尝试这样做:

{  
   "JSONContent": {  "Name1": "Value1", "Name2": "Value2" },
   "Signature": "asdflkajsdrliuejadceaageaetge="
}

这样做:

{
   "Base64JSONContent": "eyAgIk5hbWUxIjogIlZhbHVlMSIsICJOYW1lMiI6ICJWYWx1ZTIiIH0s",
   "Signature": "asdflkajsdrliuejadceaageaetge="

}

即不要对 JSON 签名,对 已编码的 JSON 字节进行签名

是的,这意味着签名不再透明。

【讨论】:

  • Pro:这会放松属性的耦合,如“可选”对象所示。次要缺点:标准 API 工具不理解这种包装。话又说回来,为这些生成哈希并非易事。
  • 我已经多年没有关注这个问题了,但是如果我今天必须实现散列,这就是我会采取的方法。
【解决方案4】:

JSON-LD 可以进行标准化。

你必须定义你的上下文。

【讨论】:

    【解决方案5】:

    RFC 7638:JSON Web 密钥 (JWK) 指纹包括一种规范化。尽管 RFC7638 期望的成员集是有限的,但我们可以对任何成员应用相同的计算。

    https://www.rfc-editor.org/rfc/rfc7638#section-3

    【讨论】:

      【解决方案6】:

      我会按照给定的顺序(例如按字母顺序)处理所有字段。为什么任意数据会产生影响?您可以只迭代属性(ala 反射)。

      或者,我会考虑将原始 json 字符串转换为一些定义明确的规范形式(删除所有多余的格式) - 并对其进行哈希处理。

      【讨论】:

        【解决方案7】:

        我们遇到了一个关于散列 JSON 编码负载的简单问题。 在我们的案例中,我们使用以下方法:

        1. 将数据转换为 JSON 对象;
        2. 用 base64 编码 JSON 负载
        3. 消息摘要 (HMAC) 生成的 base64 有效负载。
        4. 传输 base64 有效载荷。

        使用此解决方案的优势:

        1. Base64 将为给定的负载产生相同的输出。
        2. 由于生成的签名将直接从 base64 编码的有效负载派生,并且由于 base64 有效负载将在端点之间交换,我们将确定签名和有效负载将保持不变。
        3. 此解决方案解决了由于特殊字符编码不同而出现的问题。

        缺点

        1. 有效载荷的编码/解码可能会增加开销
        2. Base64 编码的数据通常比原始有效负载大 30% 以上。

        【讨论】:

        • Base64 编码的数据通常比原始有效负载大 30%
        • 您不需要先对对象键进行排序(例如按字母顺序),因为 JSON 不保证对象键的顺序,因此相同的数据可能具有不同的键顺序,从而给出不同的哈希值?
        • 不需要对原始字符串进行排序,因为您将散列 b64 有效负载而不是原始 JSON 字符串本身。
        • @DeezzleLuBimkii “有效负载”是什么意思?具体来说,如果我有对象{"a":1,"b":2},那么当您说有效负载时,您是指值12,还是指整个序列化字符串'{"a":1,"b":2}'?如果是后者,那么对要排序的键绝对确实很重要。排序的更改将更改 base64 编码。如果你指的是前者,那么这并不能完全解决整个问题。
        • 有效载荷可能是序列化数据本身。将您的序列化数据转换为 base64 将保留结构。然后您可以对 b64 编码的字符串进行哈希/签名以获得签名。在接收端,您可以通过应用之前使用的相同哈希/签名算法来验证 b64 有效负载的签名。如果签名匹配,则 b64 有效负载有效,您可以对有效负载进行解码。如果 sig 不匹配,则可以假设 b64 已被篡改。
        猜你喜欢
        • 2020-07-08
        • 2011-02-18
        • 2011-06-02
        • 1970-01-01
        • 2011-06-23
        • 2018-01-20
        • 2018-07-17
        • 1970-01-01
        • 2013-01-08
        相关资源
        最近更新 更多