Cómo criptográficamente hash un objeto JSON?

La siguiente pregunta es más compleja de lo que parece en un principio.

Supongamos que tengo un objeto JSON arbitrario, uno que puede contener cualquier cantidad de datos, incluidos otros objetos JSON nesteds. Lo que quiero es un hash criptográfico / resumen de los datos JSON, sin tener en cuenta el formato JSON real (por ejemplo, ignorar las nuevas líneas y las diferencias de espaciado entre los tokens JSON).

La última parte es un requisito, ya que el JSON será generado / leído por una variedad de (de) serializadores en varias plataformas diferentes. Conozco al menos una biblioteca JSON para Java que elimina por completo el formateo al leer datos durante la deserialización. Como tal, romperá el hash.

La cláusula de datos arbitrarios anterior también complica las cosas, ya que me impide tomar campos conocidos en un orden determinado y concatenarlos antes de hasing (piense más o menos cómo funciona el método hashCode () no criptográfico de Java).

Por último, no es deseable mezclar todo el String JSON como un trozo de bytes (antes de la deserialización), ya que hay campos en el JSON que se deben ignorar al calcular el hash.

No estoy seguro de que haya una buena solución a este problema, pero doy la bienvenida a cualquier enfoque o pensamiento =)

El problema es común cuando se computan hashes para cualquier formato de datos donde se permite flexibilidad. Para resolver esto, necesita canonicalizar la representación.

Por ejemplo, el protocolo OAuth1.0a, que es utilizado por Twitter y otros servicios para la autenticación, requiere un hash seguro del mensaje de solicitud. Para calcular el hash, OAuth1.0a dice que primero necesita alfabetizar los campos, separarlos por líneas nuevas, eliminar los nombres de campo (que son bien conocidos) y usar líneas en blanco para los valores vacíos. La firma o hash se calcula sobre el resultado de esa canonicalización.

XML DSIG funciona de la misma manera: necesita canonicalizar el XML antes de firmarlo. Existe un estándar W3 propuesto que lo cubre , porque es un requisito fundamental para la firma. Algunas personas lo llaman c14n.

No sé de un estándar de canonicalización para json. Vale la pena investigar.

Si no hay uno, ciertamente puede establecer una convención para su uso particular de la aplicación. Un comienzo razonable podría ser:

  • clasificar lexicográficamente las propiedades por nombre
  • comillas dobles usadas en todos los nombres
  • comillas dobles usadas en todos los valores de cadena
  • no hay espacio, o un espacio, entre los nombres y los dos puntos, y entre los dos puntos y el valor
  • sin espacios entre los valores y la siguiente coma
  • todos los demás espacios en blanco colapsaron en un solo espacio o nada: elija uno
  • excluya cualquier propiedad que no desee firmar (un ejemplo es la propiedad que contiene la firma)
  • Firme el resultado, con su algoritmo elegido

También puede pensar en cómo pasar esa firma en el objeto JSON; posiblemente establezca un nombre de propiedad conocido, como “nichols-hmac” o algo así, que obtenga la versión codificada en base64 del hash. Esta propiedad debería excluirse explícitamente mediante el algoritmo hash. Entonces, cualquier receptor del JSON podría verificar el hash.

La representación canonicalizada no necesita ser la representación que se pasa en la aplicación. Solo necesita ser producido fácilmente dado un objeto JSON arbitrario.

En lugar de inventar su propia normalización JSON / canonicalización es posible que desee utilizar bencode . Semánticamente es lo mismo que JSON (composición de números, cadenas, listas y dictados), pero con la propiedad de encoding inequívoca que es necesaria para el hash criptográfico.

bencode se usa como un formato de archivo torrent, cada cliente bittorrent contiene una implementación.

Este es el mismo problema que causa problemas con las firmas S / MIME y las firmas XML. Es decir, hay múltiples representaciones equivalentes de los datos que se deben firmar.

Por ejemplo en JSON:

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

vs.

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

O dependiendo de su aplicación, esto puede ser equivalente:

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

La canonicalización podría resolver ese problema, pero es un problema que no necesita en absoluto.

La solución fácil si tiene control sobre la especificación es envolver el objeto en algún tipo de contenedor para evitar que se transforme en una representación “equivalente” pero diferente.

Es decir, evite el problema al no firmar el objeto “lógico”, sino que firma una representación serializada particular del mismo.

Por ejemplo, Objetos JSON -> Texto UTF-8 -> Bytes. Firme los bytes como bytes , luego los transmite como bytes, por ejemplo, mediante encoding base64. Como está firmando los bytes, las diferencias como el espacio en blanco son parte de lo que se firma.

En lugar de tratar de hacer esto:

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

Solo haz esto:

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

Es decir, no firme el JSON, firme los bytes del JSON codificado .

Sí, significa que la firma ya no es transparente.

JSON-LD puede hacer la normalización.

Deberá definir su contexto.

Haría todos los campos en un orden dado (alfabéticamente por ejemplo). ¿Por qué los datos arbitrarios hacen una diferencia? Puede simplemente iterar sobre las propiedades (reflection del ala).

Alternativamente, buscaría convertir la cadena de json sin procesar en una forma canónica bien definida (eliminar todo el formato superfino) – y hashing eso.

Nos encontramos con un problema simple con las cargas útiles hash codificadas en JSON. En nuestro caso utilizamos la siguiente metodología:

  1. Convierte datos en un objeto JSON;
  2. Codificar carga útil JSON en base64
  3. Mensaje resumen (HMAC) la carga útil base64 generada.
  4. Transmita la carga útil de base64.

Ventajas de usar esta solución:

  1. Base64 producirá la misma salida para una carga útil determinada.
  2. Como la firma resultante se derivará directamente de la carga útil codificada en base64 y como la carga base64 se intercambiará entre los puntos finales, estaremos seguros de que se mantendrá la firma y la carga útil.
  3. Esta solución resuelve problemas que surgen debido a la diferencia en la encoding de caracteres especiales.

Desventajas

  1. La encoding / desencoding de la carga útil puede agregar gastos generales
  2. Los datos codificados en Base64 suelen ser un 15-20% más grandes que la carga original.
Intereting Posts