Я должен обмениваться объектами 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, а затем попытайтесь проверить подпись, возможно, я получу другой порядок атрибутов объекта.
Так что мне нужен надежный способ создания String из JSONObject, независимо от используемого языка или платформы.
Приведенный выше пример объекта должен всегда выводиться {"a":1, "b":2}
и никогда {"b":2, "a":1}
, К сожалению, это обычный случай, например на Яве.
Существуют ли «лучшие практики» для безопасной подписи объектов JSON?
Но позвольте мне описать проблему по-другому:
Допустим, я хочу сделать это на Java (или любом другом языке):
JSONObject j = new JSONObject();
j.put("a", 1);
j.put("b", 2);
Теперь мне нужна функция сериализации, которая выводит всегда одно и то же строковое представление для этого объекта, независимо от того, как и на каком языке этот объект создается.
Подписание и шифрование объектов JSON определено в наборе спецификаций JOSE, где JOSE означает Подписание и шифрование объектов Javascript, см. http://jose.readthedocs.org/en/latest/ JOSE использует отдельную сигнатуру, вычисленную по представлению кодирования base64url объекта JSON. Подпись не является частью самого объекта JSON, поэтому для ее проверки не требуется переупорядочение.
Поскольку у AFAIK еще нет официального (или неофициального) стандарта на подпись JSON, я бы, вероятно, сделал пользовательскую реализацию. Я бы определил новый объект JSON, например
{
"original": "..." // original JSON as a Base64 encoded string
"signature": "..." // the signature
}
и внедрить слой подписи / проверки подписи на обеих сторонах моей системы.
Вот как я решил это сейчас. Это похоже на то, что делает 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);
}
}
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);
Мы столкнулись с аналогичной проблемой с хэшированием JSON-кодированных полезных нагрузок. В нашем случае мы используем следующую методологию:
Подробности смотрите в ссылке ниже
связанный ответ здесь:
Как криптографически хешировать объект JSON?