我需要签署一个JSON,但我注意到解编/编组可能会改变JSON的顺序,这可能会使签名无效。
无论如何,尽管它的顺序是从JSON字符串生成相同的哈希?
我已经看过JOSE但是找不到真正散列JSON的函数。
JOSE JWS绝对会以管理签名和验证密钥为代价完成您的工作。
但是我们假设您并不真正需要JOSE中的整个密钥管理和一般加密功能,并且您并不是SUPER关注性能(因此在此过程中稍微纠结一点就可以了)。
你可以愚蠢地解组你的JSON并重新编组,然后哈希:
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
json "encoding/json"
)
// NB These docs are strictly-speaking the same.
const DOCA = "{ \"foo\": 1.23e1, \"bar\": { \"baz\": true, \"abc\": 12 } }"
const DOCB = "{ \"bar\": { \"abc\": 12, \"baz\": true }, \"foo\": 12.3 }"
func hash(doc string) string {
// Dumb af, but it's a cheap way to specific the most generic thing
// you can :-/
var v interface{}
json.Unmarshal([]byte(doc), &v) // NB: You should handle errors :-/
cdoc, _ := json.Marshal(v)
sum := sha256.Sum256(cdoc)
return hex.EncodeToString(sum[0:])
}
func main() {
fmt.Println(DOCA)
fmt.Printf("Hash: %s\n", hash(DOCA))
fmt.Println(DOCB)
fmt.Printf("Hash: %s\n", hash(DOCB))
}
该程序的输出(至少在golang docker容器中)是:
{ "foo": 1.23e1, "bar": { "baz": true, "abc": 12 } }
Hash: d50756fbb830f8335187a3f427603944c566772365d8d8e6f6760cd2868c8a73
{ "bar": { "abc": 12, "baz": true }, "foo": 12.3 }
Hash: d50756fbb830f8335187a3f427603944c566772365d8d8e6f6760cd2868c8a73
关于这种方法的好处在于,对于某些性能的成本,你可以避免在编写JSON时所做的任何愚蠢的垃圾(因此,与其他建议不同,你不必考虑你的内容)可能正在使用自定义Marshallers和诸如此类的东西)。当你在一年后的代码版本3.8中忘记这是一个问题时,这尤其是一个大问题,实现与编组顺序混淆的东西,并开始破坏事物。
当然,您始终可以将哈希添加到结果结构中,并使用地图中的额外项目再次进行编组。显然你想要优化一点性能,如果你完全担心它并正确处理错误,但无论如何这是一个很好的原型:-)
哦,如果你非常担心咬你的边缘情况,你也可以使用canonical JSON来编组,因为它是专门为这种类型的使用而设计的(但老实说,我在测试时无法想出一个例子) c-json工作的地方,但是默认的json没有)。