签署 JSON 对象

Posted

技术标签:

【中文标题】签署 JSON 对象【英文标题】:Signing JSON objects 【发布时间】:2015-10-25 18:24:58 【问题描述】:

我必须在不同平台和服务实现之间交换 JSON 对象,并通过数字签名验证其完整性。所以平台 A 会创建这样一个对象并创建一个数字签名。然后将所述签名包含在对象中并发送到平台 B。JSON 对象可以包含任意属性和数据。

例如在 php 中:

function signObject($jsonObjectToSign, $privateKey) 
    $jsonObjectToSign->signature = "";
    $msgToSign = json_encode($jsonObjectToSign);

    openssl_sign($msgToSign, $jsonObjectToSign->signature, $privateKey, OPENSSL_SLGO_SHA1);

    return $jsonObjectToSign;

问题是,例如在 Java 中,无法判断 JSON 对象的属性是否与您添加它们的顺序相同(通过 JSONObject.put())。所以,如果我做一个

$json = json_encode('"a":1, "b":2');

在 PHP 中,如上所述对该对象进行签名,将其传输到基于 java 的服务器,解码 json 对象,然后尝试验证签名,我可能会得到对象属性的不同顺序。

所以我需要的是一种从 JSONObject 创建字符串的可靠方法,与所使用的语言或平台无关。

上面的示例对象需要始终输出"a":1, "b":2 和NEVER "b":2, "a":1。不幸的是,这是通常的情况,例如在 Java 中。

是否有任何“最佳实践”以安全的方式?

但是让我用另一种方式来描述这个问题:

假设我想用 Java(或任何其他语言)执行此操作:

JSONObject j = new JSONObject();
j.put("a", 1);
j.put("b", 2);

现在,我需要一个序列化函数,它始终为该对象输出相同的字符串表示形式,无论该对象是如何以及使用何种语言创建的。

【问题讨论】:

不确定this 是否重复,但绝对值得一读。提出了一些用于规范化 JSON 以保证可重现哈希的想法。 我在问题中看到了OPENSSL_SLGO_SHA1,但不应该是OPENSSL_ALGO_SHA1 带有A 而不是S?跨度> 【参考方案1】:

JSON 对象的签名和加密在 JOSE 规范套件中指定,其中 JOSE 代表 javascript 对象签名和加密,请参阅 http://jose.readthedocs.org/en/latest/ JOSE 使用基于 JSON 对象的 base64url 编码表示计算的分离签名。签名不是 JSON 对象本身的一部分,因此无需重新排序即可对其进行验证。

【讨论】:

【参考方案2】:

由于 AFAIK 还没有关于 JSON 签名的官方(也不是非官方)标准,我可能会做一个自定义实现。我会定义一个新的 JSON 对象,例如


  "original": "..." // original JSON as a Base64 encoded string
  "signature": "..." // the signature

并在我的系统的两侧/所有方面实施签名/签名验证层。

【讨论】:

这就是我现在正在做的事情。无论如何,当使用对象然后创建所说的 base64 表示时,我可能会在不同的系统上得到不同的 base64 表示。因此,检查签名可能会失败。 不,Base64 应该相同(恕我直言)。或者你的意思是,例如原始 JSON 字符串中的字段顺序可能不同,因此 Base64 字符串会有所不同?但这应该没问题,您将签署 Base64 字符串,而不是原始字符串。而且您不必关心字段顺序,因为在目标系统上您首先验证 Base64 字符串的签名,然后对其进行解码以获取原始文件,最后加载它。但是通过解码和加载,您不再关心签名了。 是的,这就是我要说的问题。不幸的是,我没有在我的框架中“得到”这个字符串表示,我只是得到了 JSON 对象。所以我需要一种方法来创建始终相同的 base64 表示。【参考方案3】:

这就是我现在解决的方法。它在某种程度上类似于 JOSE 所做的,除了标题。但是 JOSE 似乎带来了很多我不需要的开销(和功能)。所以我决定采用以下方法:

class Signature

    private static $algorithm = OPENSSL_ALGO_SHA512;
    private static $signaturePrefix = '-----BEGIN SIGNATURE-----';
    private static $signaturePostfix = '-----END SIGNATURE-----';

    public static function createSignature($message, $privateKey)
    
        $signature = null;

        openssl_sign($message, $signature, $privateKey, self::$algorithm);

        return self::$signaturePrefix . base64_encode($signature) . self::$signaturePostfix;
    

    public static function verifySignature($message, $publicKey, $signature)
    
        $signature = str_replace(self::$signaturePrefix, '', $signature);
        $signature = str_replace(self::$signaturePostfix, '', $signature);

        return openssl_verify($message, base64_decode($signature), $publicKey, self::$algorithm);
    

    public static function signJSON($jsonToSign, $privateKey)
    
        if(gettype($jsonToSign) != 'string')
            $jsonToSign = json_encode($jsonToSign);

        $signedJSON = json_decode('');
        $sigedJSON->signature = self::createSignature($message, $privateKey);
        $signedJSON->object = $jsonToSign;

        return $signedJSON;
    

    public static function verifyJSONSignature($jsonObject, $publicKey)
    
        if(gettype($jsonObject->object) == 'string')
            throw new Exception('Value $jsonObject->object must be a String, is a ' . gettype($jsonObject->object));

        return self::verifySignature($jsonObject->object, $publicKey, $jsonObject->signature);
    

【讨论】:

您是如何验证 JSON 在其他平台和语言中的元素顺序的? “jsonToSign”被序列化为字符串,然后签名。这允许任何 JSON 实现来验证签名,但需要额外的编码/解码步骤来获取实际数据。 等一下,@Xenonite。 if (gettype(...) == 'string') throw new Exception("not a string"); 这似乎与预期相反。【参考方案4】:

我们在散列 JSON 编码的负载时遇到了类似的问题。在我们的例子中,我们使用以下方法:

    将数据转换为 JSON 对象 用 base64 编码 JSON 负载 消息摘要 (HMAC) 生成的 base64 有效负载 传输 base64 有效负载(带有生成的签名)。

查看以下链接中的详细信息

链接的答案在这里: How to cryptographically hash a JSON object?

【讨论】:

【参考方案5】:
JsonObject js = new JsonObject();
js.addProperty("c", "123");
js.addProperty("t", "yyyy-MM-dd'T'HH:mm:ss.SSSZ");
System.out.println("Json object == " + js);
GenerateSignature util = new GenerateSignature();
String ecodedToken = util.signJSONObject(js);

【讨论】:

欢迎来到堆栈溢出 :-) 请看How to Answer。您应该提供一些信息为什么您的代码可以解决问题。纯代码答案对社区没有用处。

以上是关于签署 JSON 对象的主要内容,如果未能解决你的问题,请参考以下文章

如何使用返回的 json 对象中的特定项目设置反应组件的状态?

在签署 jwt 时,只保留 user_id 或整个用户对象是不是更好?

gpg 未能签署数据致命:未能写入提交对象 [Git 2.10.0]

gpg无法签署致命的数据:无法写入提交对象[Git 2.10.0]

typeScript面对对象篇二

生成和签署 JSON Web 令牌时出错