Comment hacher cryptographiquement un object JSON?

La question suivante est plus complexe qu’il n’y paraît.

Supposons que je dispose d’un object JSON arbitraire, pouvant contenir n’importe quelle quantité de données, y compris d’autres objects JSON nesteds. Ce que je veux, c’est un hachage cryptographique des données JSON, sans tenir compte du formatage JSON proprement dit (par exemple, en ignorant les nouvelles lignes et les différences d’espacement entre les jetons JSON).

La dernière partie est une exigence, car le JSON sera généré / lu par une variété de (de) sérialiseurs sur plusieurs plates-formes différentes. Je connais au moins une bibliothèque JSON pour Java qui supprime complètement le formatage lors de la lecture des données lors de la désérialisation. En tant que tel, il va briser le hash.

La clause de données arbitraires ci-dessus complique également les choses, car cela m’empêche de prendre des champs connus dans un ordre donné et de les concaténer avant de les avoir (pensez approximativement au fonctionnement de la méthode hashCode () non cryptographique de Java).

Enfin, le hachage de la chaîne JSON entière en tant que bloc d’octets (avant la désérialisation) n’est pas souhaitable non plus, car il existe des champs dans le JSON qui doivent être ignorés lors du calcul du hachage.

Je ne suis pas sûr qu’il y ait une bonne solution à ce problème, mais j’apprécie toute approche ou pensée =)

Le problème est courant lors du calcul des hachages pour tout format de données où la flexibilité est autorisée. Pour résoudre ce problème, vous devez canoniser la représentation.

Par exemple, le protocole OAuth1.0a, utilisé par Twitter et d’autres services pour l’authentification, nécessite un hachage sécurisé du message de demande. Pour calculer le hachage, OAuth1.0a dit que vous devez d’abord classer les champs par ordre alphabétique, les séparer par des nouvelles lignes, supprimer les noms de champs (qui sont bien connus) et utiliser des lignes vides pour les valeurs vides. La signature ou le hachage est calculé sur le résultat de cette canonisation.

XML DSIG fonctionne de la même manière: vous devez canoniser le code XML avant de le signer. Il existe une norme W3 proposée à ce sujet , car il s’agit d’une exigence fondamentale pour la signature. Certaines personnes l’appellent c14n.

Je ne connais pas de norme de canonisation pour json. Cela vaut la peine de faire des recherches.

S’il n’y en a pas, vous pouvez certainement établir une convention pour l’utilisation de votre application particulière. Un début raisonnable pourrait être:

  • sortinger lexicographiquement les propriétés par nom
  • guillemets doubles utilisés sur tous les noms
  • doubles guillemets utilisés sur toutes les valeurs de chaîne
  • pas d’espace, ou un espace, entre les noms et les deux points, et entre les deux points et la valeur
  • pas d’espaces entre les valeurs et la virgule suivante
  • tout autre espace blanc s’est réduit à un seul espace ou à rien – choisissez-en un
  • exclure les propriétés que vous ne voulez pas signer (un exemple est la propriété qui contient la signature elle-même)
  • signer le résultat avec l’algorithme choisi

Vous pouvez également réfléchir à la manière de passer cette signature dans l’object JSON – éventuellement établir un nom de propriété bien connu, comme “nichols-hmac” ou quelque chose, qui récupère la version codée en base64 du hachage. Cette propriété devrait être explicitement exclue par l’algorithme de hachage. Ensuite, tout récepteur du JSON pourrait vérifier le hash.

La représentation canonique ne doit pas nécessairement être la représentation que vous transmettez dans l’application. Il suffit de produire facilement un object JSON arbitraire.

Au lieu d’inventer votre propre normalisation / canonisation JSON, vous pouvez utiliser bencode . Sémantiquement, c’est le même que JSON (composition de nombres, de chaînes, de listes et de dicts), mais avec la propriété de coder sans ambiguïté nécessaire pour le hachage cryptographique.

bencode est utilisé comme un format de fichier torrent, chaque client bittorrent contient une implémentation.

Ce problème est identique à celui qui pose des problèmes avec les signatures S / MIME et XML. Autrement dit, il existe plusieurs représentations équivalentes des données à signer.

Par exemple dans JSON:

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

contre.

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

Ou selon votre application, cela peut même être équivalent:

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

La canonisation pourrait résoudre ce problème, mais c’est un problème inutile.

La solution simple si vous avez le contrôle de la spécification est d’emballer l’object dans une sorte de conteneur pour le protéger de la transformer en une représentation «équivalente» mais différente.

Ie éviter le problème en ne signant pas l’object “logique” mais en signant une représentation sérialisée particulière à la place.

Par exemple, Objets JSON -> Texte UTF-8 -> Octets. Signez les octets en octets , puis transmettez-les en octets, par exemple, en codant en base64. Comme vous signez les octets, les différences telles que les espaces font partie de ce qui est signé.

Au lieu d’essayer de faire cela:

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

Faites ceci:

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

Ie ne signe pas le JSON, signe les octets du JSON codé .

Oui, cela signifie que la signature n’est plus transparente.

JSON-LD peut faire la normalisation.

Vous devrez définir votre contexte.

Je ferais tous les champs dans un ordre donné (alphabétiquement par exemple). Pourquoi les données arbitraires font-elles une différence? Vous pouvez simplement parcourir les propriétés (ala reflection).

Sinon, je chercherais à convertir la chaîne de caractères json brute en une forme canonique bien définie (supprimer toutes les mises en forme superflues) – et à hacher cela.

Nous avons rencontré un problème simple avec le hachage de charges utiles codées JSON. Dans notre cas, nous utilisons la méthodologie suivante:

  1. Convertir les données en object JSON;
  2. Encoder la charge utile JSON en base64
  3. Message digest (HMAC) la charge utile base64 générée.
  4. Transmettre la charge utile base64.

Avantages de l’utilisation de cette solution:

  1. Base64 produira la même sortie pour une charge utile donnée.
  2. Comme la signature résultante sera directement dérivée de la charge utile encodée en base64 et que base64-payload sera échangée entre les points de terminaison, nous serons certains que la signature et la charge utile seront conservées.
  3. Cette solution résout les problèmes liés à la différence de codage des caractères spéciaux.

Désavantages

  1. Le codage / décodage de la charge utile peut append des frais généraux
  2. Les données encodées en base64 sont généralement de 15 à 20% plus grandes que les données utiles d’origine.