在Python和JavaScript中生成JSON的md5哈希并进行比较

28

我有一个使用场景,需要生成JSON对象的md5哈希值,并比较服务器和浏览器中的哈希值。

浏览器客户端生成哈希值,然后请求相同资源(即一个JSON对象)在服务器上的哈希值,并比较这两个哈希值以决定下一步做什么。

我在服务器上使用Python,而浏览器客户端则使用Javascript

但是在我的代码中,两种情况下生成的哈希值不匹配。这里是我的代码:

Python:

>>> import hashlib
>>> import json

>>> a = {"candidate" : 5, "data": 1}
>>> a = json.dumps(a, sort_keys = True).encode("utf-8")
>>> hashlib.md5(a).hexdigest()
>>> 12db79ee4a76db2f4fc48624140adc7e

JS:我正在浏览器中使用md5进行哈希处理。

> var hash = require("md5")
> var data = {"candidate":5, "data":1}
> data = JSON.stringify(data)
> md5(data)
> 92e99f0a99ad2a3b5e02f717a2fb83c2

我做错了什么?


1
这个库提供了正确的哈希值(Python的)。 - user5734311
请注意,将JS转换为字符串并不是最佳方法:空格、格式甚至2个键的顺序可能不同,但语义上文档是相同的。我还没有找到Python的库,但对于JavaScript,可以使用https://github.com/fraunhoferfokus/JSum。对于Python,可以使用https://github.com/schollii/sandals/blob/master/json_sem_hash.py。 - Oliver
md5不安全。最好使用一些现代的东西,比如sha384sha256 - Zaz
md5不安全。最好使用一些现代的东西,比如sha384sha256 - undefined
3个回答

44

你假设两种语言生成的JSON看起来是完全一样的。

>>> json.dumps({"candidate" : 5, "data": 1}, sort_keys=True)
'{"candidate": 5, "data": 1}'

js> JSON.stringify({"candidate" : 5, "data": 1})
"{\"candidate\":5,\"data\":1}"

幸运的是,他们可以。

>>> a = json.dumps({"candidate" : 5, "data": 1}, sort_keys=True, indent=2)
'{\n  "candidate": 5,\n  "data": 1\n}'

js> var a = JSON.stringify({"candidate" : 5, "data": 1}, null, 2)
"{\n  \"candidate\": 5,\n  \"data\": 1\n}"

现在哈希值也将相同。

Python:

>>> hashlib.md5(a.encode("utf-8")).hexdigest()
>>> d77982d217ec5a9bcbad5be9bee93027

JS:

>>> md5(a)
>>> d77982d217ec5a9bcbad5be9bee93027

我无法弄清楚如何在我的 JS REPL 中进行哈希处理。 - Ignacio Vazquez-Abrams
我认为这个答案是无效的,因为在 Python 版本中强制执行排序,而在 JavaScript 版本中它是任意的,事实上,根据文档,其不保证遵循任何特定的顺序:注意:非数组对象的属性不能保证以任何特定顺序进行字符串化。不要依赖于同一对象内部属性的顺序进行字符串化。相同的示例将失败: {"data" : 5, "candidate": 1} - Vektrat

1

两者的区别在于json.dumps默认会进行一些微小的漂亮打印,但JSON.stringify不会,这就是哈希值不同的原因。
Python:

 >>> import json
 >>> json.dumps({"candidate" : 5, "data": 1})
     '{"candidate": 5, "data": 1}'

JavaScript:

 > JSON.stringify({"candidate" : 5, "data": 1})
   '{"candidate":5,"data":1}'

但是经过一些修改,我们可以生成相同的哈希值。有两种方法可以实现:-
  1. Modifying javascript JSON string to make it equivalent to a python JSON string.
    Python:
    >>> import json,hashlib
    >>> a = json.dumps({"candidate" : 5, "data": 1}, sort_keys=True)
    >>> hashlib.md5(a.encode("utf-8")).hexdigest()
        '12db79ee4a76db2f4fc48624140adc7e'
    
    Javacript:
    > const Crypto = require("crypto-js")
      undefined
    > const a = JSON.stringify({"candidate" : 5, "data": 1}).replaceAll(":", ": ").replaceAll(",", ", ")
      undefined
    > Crypto.MD5(a).toString(Crypto.enc.Hex)
      '12db79ee4a76db2f4fc48624140adc7e'
    
  2. Modifying python JSON string to make it equivalent to a javascript JSON string.
    Python:
    >>> import json,hashlib
    >>> a = json.dumps({"candidate" : 5, "data": 1}, separators=(',', ':'))
    >>> hashlib.md5(a.encode("utf-8")).hexdigest()
        '92e99f0a99ad2a3b5e02f717a2fb83c2'
    
    Javacript:
    > const Crypto = require("crypto-js")
      undefined
    > const a = JSON.stringify({"candidate" : 5, "data": 1})
      undefined
    > Crypto.MD5(a).toString(Crypto.enc.Hex)
      '92e99f0a99ad2a3b5e02f717a2fb83c2'
    

    Note:- To run javascript code, crypto-js npm pkg should be installed as same location where you started the node shell.


非常有帮助,我的问题的关键在于separators选项。谢谢! - elPastor

1
我创建了这个 Python 模块 merkle-json,它可以生成一个唯一的哈希值,无论列表中的顺序或字典或 JSON 对象中的键是什么。 它还提供了一些灵活和额外的配置选项,您可以根据需要忽略键或空值,请查看文档以获取更多信息。
像这样使用它:
from merkle_json import MerkleJson

mj = MerkleJson()

obj = {
    'keyC': [3,4],
    'keyA': 2,
    'keyB': 4,
    'keyD': 1,
}
mjHash = mj.hash(obj)
print(mjHash) # '7001bd2b415e6a624a23d7bc7c249b21'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接