Как криптографически хэшировать объект JSON?

следующий вопрос более сложный, чем может показаться на первый взгляд.

предположим, что у меня есть произвольный объект JSON, который может содержать любой объем данных, включая другие вложенные объекты JSON. Я хочу криптографический хэш / дайджест данных JSON, независимо от фактического форматирования JSON (например: игнорирование новых строк и различий между токенами JSON).

последняя часть является требованием, так как JSON будет сгенерирован / прочитан разнообразие (de)сериализаторов на нескольких различных платформах. Я знаю по крайней мере одну библиотеку JSON для Java, которая полностью удаляет форматирование при чтении данных во время десериализации. Таким образом, он сломает хэш.

произвольные пункте данных и усложняет, так как он предотвращает меня от известных месторождений, расположенных в определенном порядке объединить их до hasing (думаю, примерно как в Java не криптографический хэш-код() метод работает).

наконец, хэширования вся строка JSON в виде куска байтов (до десериализации) также нежелательна, поскольку в JSON есть поля, которые следует игнорировать при вычислении хэша.

Я не уверен, что есть хорошее решение этой проблемы, но я приветствую любые подходы или мысли =)

6 ответов


проблема является распространенной при вычислении хэшей для любого формата данных, где допускается гибкость. Чтобы решить эту проблему, вам нужно канонизируют представление.

например, OAuth1.Протокол 0a, который используется Twitter и другими службами для аутентификации, требует безопасного хэша сообщения запроса. Для вычисления хэша, OAuth1.0а говорит, что нужно сначала по алфавиту поля, разделить их на строки, удалить имена полей (которые хорошо known) и использовать пустые строки для пустых значений. Подпись или хэш вычисляется в результате этой канонизации.

XML DSIG работает так же - вам нужно канонизировать XML перед подписанием. Есть предлагаемый стандарт W3, охватывающий это, потому что это такое фундаментальное требование для подписания. Некоторые называют его c14n.

Я не знаю стандарта канонизации для json. Это стоит исследовать.

Если нет во-первых, вы можете установить Соглашение для вашего конкретного использования приложения. Разумным началом может быть:

  • лексикографически сортировать свойства по имени
  • двойные кавычки, используемые на всех именах
  • двойные кавычки, используемые для всех строковых значений
  • нет пробелов или одно пробел, между именами и двоеточием, а также между двоеточием и значением
  • нет пробелов между значениями и следующей запятой
  • все другое белое пространство рухнуло либо в одно пространство , либо в ничто-выберите один
  • исключить любые свойства, которые вы не хотите подписывать (например, свойство, которое содержит саму подпись)
  • подпишите результат, с выбранным алгоритмом

вы также можете подумать о том, как передать эту подпись в объекте JSON-возможно, установить известное имя свойства, например "Николс-hmac" или что-то еще, что получает кодированную версию base64 гашиш. Это свойство должно быть явно исключено алгоритмом хэширования. Тогда любой получатель JSON сможет проверить хэш.

канонизированное представление не должно быть представлением, которое вы передаете в приложении. Его нужно только легко произвести, учитывая произвольный объект JSON.


вместо изобретения собственной нормализации / канонизации JSON вы можете использовать bencode. Семантически это то же самое, что и JSON (состав чисел, строк, списков и диктов), но со свойством однозначного кодирования, которое необходимо для криптографического хеширования.

bencode используется как формат торрент-файла, каждый клиент bittorrent содержит реализацию.


Это та же проблема, что и проблемы с сигнатурами S/MIME и XML-сигнатурами. То есть существует несколько эквивалентных представлений подписываемых данных.

например в JSON:

{  "Name1": "Value1", "Name2": "Value2" }

и

{
    "Name1": "Value\u0031",
    "Name2": "Value\u0032"
}

или в зависимости от вашего приложения, это может даже быть эквивалентны:

{
    "Name1": "Value\u0031",
    "Name2": "Value\u0032",
    "Optional": null
}

канонизация может решить эту проблему, но это проблема, которая вам вообще не нужна.

самое простое решение, если у вас есть управление спецификацией заключается в том, чтобы обернуть объект в какой-то контейнер, чтобы защитить его от преобразования в "эквивалентное", но другое представление.

т. е. избегайте проблемы, не подписывая "логический" объект, а подписывая вместо него определенное сериализованное представление.

например, объекты JSON - > UTF - 8 Text - > Bytes. Знак байт как байт, а затем передать их как байт например, по кодировке base64. С вы подписываете байты, различия, такие как пробелы, являются частью того, что подписано.

вместо того, чтобы пытаться сделать это:

{  
   "JSONContent": {  "Name1": "Value1", "Name2": "Value2" },
   "Signature": "asdflkajsdrliuejadceaageaetge="
}

просто сделать это:

{
   "Base64JSONContent": "eyAgIk5hbWUxIjogIlZhbHVlMSIsICJOYW1lMiI6ICJWYWx1ZTIiIH0s",
   "Signature": "asdflkajsdrliuejadceaageaetge="

}

т. е. не подписывайте JSON, подписывайте байты кодируются в формате JSON.

Да, это означает, что подпись не прозрачный.


JSON-LD могу сделать normalitzation.

вам нужно будет определить свой контекст.


Я бы сделал все поля в заданном порядке (в алфавитном порядке, например). Почему произвольные данные имеют значение? Вы можете просто перебирать свойства (отражение ala).

альтернативно, я бы посмотрел на преобразование необработанной строки json в некоторую четко определенную каноническую форму (удалите все сверхтонкое форматирование) - и хэширование этого.


мы столкнулись с простой проблемой с хэшированием полезных нагрузок, закодированных в JSON. В нашем случае мы используем следующую методику:

  1. преобразование данных в объект JSON;
  2. кодировать полезную нагрузку JSON в base64
  3. дайджест сообщений (HMAC) сгенерированная полезная нагрузка base64 .
  4. передайте полезную нагрузку base64 .

преимущества использования этого решения:

  1. Base64 произведет такой же выход для данного полезная нагрузка.
  2. поскольку результирующая подпись будет получена непосредственно из полезной нагрузки, закодированной в base64, и поскольку полезная нагрузка base64 будет обмениваться между конечными точками, мы будем уверены, что подпись и полезная нагрузка будут поддерживаться.
  3. Это решение решает проблемы, которые возникают из-за разницы в кодировке специальных символов.

недостатки

  1. кодирование/декодирование полезной нагрузки может добавить накладные
  2. данные, закодированные в Base64, обычно на 15-20% больше исходной полезной нагрузки.