【问题标题】:How to reliably hash JavaScript objects?如何可靠地散列 JavaScript 对象?
【发布时间】:2011-07-30 09:42:19
【问题描述】:

在 JavaScript 对象相同的情况下,是否有可靠的方法来JSON.stringify JavaScript 对象,以保证所有浏览器、Node.js 等中的 ceated JSON 字符串相同?

我想像这样散列 JavaScript 对象

{
  signed_data: object_to_sign,
  signature:   md5(JSON.stringify(object_to_sign) + secret_code)
}

并在 Web 应用程序(例如 Python 和 Node.js)和用户之间传递它们,以便用户可以针对一个服务进行身份验证并显示下一个服务的“签名数据”以检查数据是否真实。

但是,我遇到了JSON.stringify 在实现中并不是真正独一无二的问题:

  • 在 Node.js / V8 中,JSON.stringify 返回一个不含不必要空格的 JSON 字符串,例如 '{"user_id":3}。
  • Python 的simplejson.dumps 留下一些空白,例如'{"user_id": 3}'
  • 可能其他字符串化实现可能会以不同方式处理空格、属性顺序等。

有可靠的跨平台字符串化方法吗?是否有“规范化的 JSON”?

你会推荐其他方法来散列这样的对象吗?

更新:

这是我用作解决方法的方法:

normalised_json_data = JSON.stringify(object_to_sign)
{
  signed_data: normalised_json_data,
  signature:   md5(normalised_json_data + secret_code)
}

所以在这种方法中,不是对象本身,而是它的 JSON 表示(特定于签名平台)被签名。这很好用,因为我现在签名的是一个明确的字符串,我可以在检查签名哈希后轻松JSON.parse 数据。

这里的缺点是,如果我将整个 {signed_data, signature} 对象也作为 JSON 发送,我必须调用 JSON.parse 两次,它看起来不太好,因为内部的对象被转义了:

{"signature": "1c3763890298f5711c8b2ea4eb4c8833", "signed_data": "{\"user_id\":5}"}

【问题讨论】:

  • 您正在使用 json stringify - 作为序列化机制 - 进行散列。我不确定这是一个好主意 - 正是由于您遇到的原因。无论如何,JSON.stringify 非常有限,我不相信它会散列或序列化我的信息。例如,尝试 JSON.stringify(new Error('not going to work'))
  • 我还想自我评论说 MD5 不是在这里使用的最佳哈希函数。
  • JSON.stringify 不是准备散列的好方法,即 JSON.stringify({b:2,a:1}) => '{"b":2,"a":1 }' , 而 JSON.stringify({a:1,b:2}) => '{"a":1,"b":2}'

标签: javascript json node.js hash


【解决方案1】:

您可能对 npm 包object-hash 感兴趣,它似乎具有相当好的活动和可靠性水平。

var hash = require('object-hash');

var testobj1 = {a: 1, b: 2};
var testobj2 = {b: 2, a: 1};
var testobj3 = {b: 2, a: "1"};

console.log(hash(testobj1)); // 214e9967a58b9eb94f4348d001233ab1b8b67a17
console.log(hash(testobj2)); // 214e9967a58b9eb94f4348d001233ab1b8b67a17
console.log(hash(testobj3)); // 4a575d3a96675c37ddcebabd8a1fea40bc19e862

【讨论】:

  • 或者在浏览器中使用,console.log(objectHash(testobj1))
  • 不过,这不是跨平台的,这正是 OP 所要求的。还有一些围绕 JSON 的确定性版本包装器,其运行速度比对象散列快得多。看看我做的这个基准:jsperf.com/stringify-vs-object-hash
  • @oligofren npmjs.com/package/json-stringify-deterministic 看起来很有趣。我建议将您的评论作为另一个答案发表。关于跨平台支持,我同意 Mark Kahn 的回答... + 或许将 JS 散列函数设为 Web 服务,然后从不同平台调用它就可以解决问题,而无需太多开发成本。
  • 我测试了 json-stringify-deterministic,但发现它很慢。我自己的一个简单函数将散列的总时间从 4 毫秒减少到 2 毫秒:function ObjectToUniqueStringNoWhiteSpace(obj : any){ let SortedObject : any = sortObjectKeys(obj); let jsonstring = JSON.stringify(SortedObject, function(k, v) { return v === undefined ? "undef" : v; }); // Remove all whitespace let jsonstringNoWhitespace :string = jsonstring.replace(/\s+/g, ''); return jsonstringNoWhitespace; }
【解决方案2】:

这是一个老问题,但我想我会为任何谷歌裁判添加一个当前的解决方案。

现在对 JSON 对象进行签名和散列的最佳方式是使用 JSON Web Tokens。这允许对象被签名、散列,然后由其他人根据签名进行验证。它提供了一系列不同的技术,并拥有一个活跃的开发团队。

【讨论】:

  • 尝试生成 jwt 签名,但看起来它不会忽略有效负载 json 中的字段顺序。所以我不明白它有什么帮助,因为 OP 想在哈希计算期间忽略字段顺序。你能解释一下你的想法吗?
  • @derp Javascript 从来没有保证对象中键的顺序——这不是你在使用对象时应该考虑的事情。我不认为 OP 明确表示他想忽略字段顺序,只是他认为不同的实现会以不同的方式处理它们。他的工作有效地处理了这个问题,因为他正在签署最终结果,而不是另一个 stringify 调用。
  • 他在stringify 的第三项中提到了结果字符串可以有不同的字段顺序的缺点。我仍然不明白 JWT 如何为 {"foo":"bar", "bar":"foo"}{"bar":"foo","foo":"bar"} 生成相同的哈希/签名,除了它们的字段顺序之外基本相同。如果 JWT 没有这样的答案,那么您的答案并不是 OP(和我)正在寻找的确切答案。
  • 不要混淆签名和哈希,它们解决不同的问题。签名就像类固醇上的哈希,重点是安全性。哈希与安全无关。 JWT 可能是解决 OPs 问题的最佳解决方案,因为您可以自动获得安全的哈希、验证、到期和信任。您每次都会为同一个对象获得不同的签名,因为时间戳会自动包含在散列数据中。听起来@Derp 只想要逻辑上相同的对象表示之间的相同哈希值,在这种情况下请参阅object-hash
【解决方案3】:

您要求跨多种语言实现相同的东西......您几乎可以肯定不走运。你有两个选择:

  • 检查 www.json.org 的实现,看看它们是否更标准化
  • 在每种语言中使用自己的语言(使用 json.org 实现作为基础,应该做的工作很少)

【讨论】:

  • 好的,对于像签名认证这样的关键事情,看起来我确实对这种方法不走运。我更新了我的帖子以显示我如何签名的不是对象,而是哈希创建平台的特定 JSON 字符串。
【解决方案4】:

您可以通过应用以下规则来规范化stringify() 的结果:

  • 删除不必要的空格
  • 对哈希中的属性名称进行排序
  • 定义明确的一致引用样式
  • 规范化字符串内容(因此“\u0041”和“A”变得相同)

这将为您提供对象的规范 JSON 表示,然后您可以可靠地对其进行哈希处理。

【讨论】:

  • 引用样式已标准化(它是 JSON 规范的一部分)。空格和属性顺序不是。
  • @cwolves:确实如此,但我见过声称是 JSON 的单引号字符串。
  • 也许,但这些都坏了,我真的怀疑你会在任何一种语言原生的 JSON 实现中看到这一点。
  • 你想如何对 JS 对象中的键进行排序?
  • @YanFoto 您需要自己定义。例如,可以是字母数字。或者你可以依赖一个包,比如 json-stringify-deterministic。基准测试:jsperf.com/stringify-vs-object-hash
【解决方案5】:

在尝试了一些哈希算法和 JSON-to-string 方法后,我发现这是最好的(抱歉,它是 typescript,当然可以重写为 javascript):

// From: https://stackoverflow.com/questions/5467129/sort-javascript-object-by-key
function sortObjectKeys(obj){
    if(obj == null || obj == undefined){
        return obj;
    }
    if(typeof obj != 'object'){ // it is a primitive: number/string (in an array)
        return obj;
    }
    return Object.keys(obj).sort().reduce((acc,key)=>{
        if (Array.isArray(obj[key])){
            acc[key]=obj[key].map(sortObjectKeys);
        }
        else if (typeof obj[key] === 'object'){
            acc[key]=sortObjectKeys(obj[key]);
        }
        else{
            acc[key]=obj[key];
        }
        return acc;
    },{});
}
let xxhash64_ObjectToUniqueStringNoWhiteSpace = function(Obj : any)
{
    let SortedObject : any = sortObjectKeys(Obj);
    let jsonstring = JSON.stringify(SortedObject, function(k, v) { return v === undefined ? "undef" : v; });

    // Remove all whitespace
    let jsonstringNoWhitespace :string = jsonstring.replace(/\s+/g, '');

    let JSONBuffer: Buffer = Buffer.from(jsonstringNoWhitespace,'binary');   // encoding: encoding to use, optional.  Default is 'utf8'
    return xxhash.hash64(JSONBuffer, 0xCAFEBABE, "hex");
}

它使用了 npm 模块:https://cyan4973.github.io/xxHash/https://www.npmjs.com/package/xxhash

好处:

  • 这是确定性的
  • 忽略键顺序(保留数组顺序)
  • 跨平台(如果你能找到 JSON-stringify 的等价物) JSON-stringify 希望不会得到不同的实现,并且空格的删除有望使其独立于 JSON 格式。
  • 64 位
  • 十六进制字符串结果
  • 最快(2177 B JSON 为 0.021 毫秒,150 kB JSON 为 2.64 毫秒)

【讨论】:

    【解决方案6】:

    您可能会找到适合您需求的bencode。它是跨平台的,并且保证每个实现的编码都是相同的。

    缺点是它不支持空值或布尔值。但是,如果您在编码之前进行诸如 bools -> 0|1 和 nulls -> "null" 之类的转换,这对您来说可能没问题。

    【讨论】:

    • 也不支持浮动
    猜你喜欢
    • 1970-01-01
    • 2021-12-19
    • 2014-02-16
    • 1970-01-01
    • 2012-10-27
    • 2018-12-31
    • 2023-03-08
    • 2015-07-07
    相关资源
    最近更新 更多