JWT, What else ?

illustration de l'article

Qui connait les RFC 7515, RFC 7516, RFC 7517, et RFC 7519 ?

Bon la question manque un peu de contexte. En fait elles sont plus connues sous les noms de JWS (Json Web Signature), JWE (Json Web Encryption), JWK (Json Web Key) mais la plus connue reste certainement JWT (Json Web Token).

Elles existent depuis 2010 et permettent principalement de normer les échanges de données entre 2 parties (En général un client et un serveur) Il existe bien des façons d’adresser ce point. Je pense notamment aux cookies, token SWT ou SAML. L’expérience a montré les limites à l’utilisation de ces solutions et nous ne les aborderons pas ici.

Un des pains-point connus pour un backend consiste à s’assurer de l’identité du client avec lequel il communique et c’est là que JWT intervient. En effet, il permet d’identifier et de vérifier l’identité d’un client. Son format correspondant à du JSON il est “facile” d’en lire le contenu pour en déduire les operations adéquates pour le backend.

Le format : À quoi ça peut bien ressembler cette affaire ?

Elles se décomposent en 3 parties séparées par des '.' :

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRBTEFOIExhYnMiLCJpYXQiOjE2NDQ4NTU0MDR9.
LFPlnuy_RGaBcFRCsR130lAu0ilOxsMff0OqvUlgtmU

Note : Il est possible de le décoder en utilisant jwt.io

Bon c’est coloré tout ça, mais ça veut dire quoi ce charabia ?

En décrypté on obtient 3 parties : le header, la payload et la signature

Header

C’est le json d’en-tête du token. Il est composé au minimum de la claim alg correspondant au JWA utilisé et de la claim typ indiquant le type du token. L’en-tête est amené à changer selon que l’on fasse du JWS ou JWE pour présenter d’autres informations obligatoires ou non.

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

Concrètement, il s’agit là des données à envoyer dans le token. La payload contient un ensemble de claims, c’est-à-dire un ensemble de paire <clé, valeur> indiqué en JSON.

{
  "sub": "1234567890",
  "name": "Talan Labs",
  "iat": 1644855404
}

Signature

Comme son nom l’indique, elle permet de signer le token afin de vérifier l’intégrité du token ainsi que l’identité de l’expéditeur du token.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

C’est bien de le savoir, mais existe-t-il une différence entre tout ça ?

A priori oui sinon il n’y aurait pas autant de RFC 😄

En réalité on emploie souvent JWT par abus de langage, mais dans la pratique on utilise plus facilement du JWS. En réalité JWT peut aussi bien correspondre à un objet JWE ou JWS selon le cas

Il faut donc voir JWT comme une abstraction a JWS et JWE. Il permet d’encoder des données à échanger, JWS permet de signer les données à échanger, JWE quant à lui, il permet d’encrypter les données à échanger. Notez qu’il y a une différence entre encrypter et encoder (en savoir plus)

On a un JWT, du coup, à la fin, il n’y a pas vraiment de différence ?

Sur le format non, sinon ce ne serait pas du JWT.

En revanche le contenu oui. Ça ne devrait pas sauter aux yeux du premier coup d’œil, mais essayons de voir à quoi cela peut-il bien ressembler.

JWA

This specification registers cryptographic algorithms and identifiers to be used with the JSON Web Signature (JWS) [JWS], JSON Web Encryption (JWE) [JWE], and JSON Web Key (JWK) [JWK] specifications. It defines several IANA registries for these identifiers. All these specifications utilize JSON-based [RFC7159] data structures. This specification also describes the semantics and operations that are specific to these algorithms and key types.

Registering the algorithms and identifiers here, rather than in the JWS, JWE, and JWK specifications, is intended to allow them to remain unchanged in the face of changes in the set of Required, Recommended, Optional, and Deprecated algorithms over time. This also allows changes to the JWS, JWE, and JWK specifications without changing this document.

Tout est dans le nom. JWA regroupe l’ensemble des algorithmes utilisables dans le cadre des spécifications JWK, JWS, et JWE.

Il correspond donc à l’indication placée dans la propriété alg du header. On y retrouve HS256 (exemple par defaut sur jwit.io ). Ceux qui sont désomais deprecated sont retirés de la RFC 7518 et c’est la raison pour laquelle est recommandé d’utiliser ES256 à date.

source : RFC 7518

Paramètre alg Description Requirements
HS256 HMAC using SHA-256 Required
HS384 HMAC using SHA-384 Optional
HS512 HMAC using SHA-512 Optional
RS256 RSASSA-PKCS1-v1_5 using SHA-256 Recommended
RS384 RSASSA-PKCS1-v1_5 using SHA-384 Optional
RS512 RSASSA-PKCS1-v1_5 using SHA-512 Optional
ES256 ECDSA using P-256 and SHA-256 Recommended+
ES384 ECDSA using P-384 and SHA-384 Optional
ES512 ECDSA using P-521 and SHA-512 Optional
PS256 RSASSA-PSS using SHA-256 and MGF1 with SHA-256 Optional
PS384 RSASSA-PSS using SHA-384 and MGF1 with SHA-384 Optional
PS512 RSASSA-PSS using SHA-512 and MGF1 with SHA-512 Optional
none No digital signature or MAC performed Optional

JWK

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key. This specification also defines a JWK Set JSON data structure that represents a set of JWKs. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification and IANA registries established by that specification.

Contrairement aux autres RFC, JWK ne correspond pas directement à un token JWT. C’est plutôt un objet JSON décrivant un lien entre une clé publique et un token JWT. Cet objet est stocké dans une map et est utilisé dans le cadre des RFC JWS et JWE. À noter d’ailleurs que le token JWT stocké est issu d’un serveur d’authentification et signé avec l’algorithme JWA

Il est possible de le générer en utilisant la librairie nimbus (source: medium)

import java.util.*;

import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.*;

class ExempleBlog {
  public static void main(String[] args) {
    // Generate 2048-bit RSA key pair in JWK format, attach some metadata
    RSAKey jwk = new RSAKeyGenerator(2048)
            .keyUse(KeyUse.SIGNATURE) // indicate the intended use of the key        
            .keyID(UUID.randomUUID().toString()) // give the key a unique ID
            .generate();
    // Output the private and public RSA JWK parameters
    System.out.println(jwk);
    // Output the public RSA JWK parameters only
    System.out.println(jwk.toPublicJWK());
  }
}

Exemple de JWK

Chacun des champs présents à un rôle spécifique que nous ne détaillerons pas ici. (en savoir plus)

 {
    "kty":"EC",
    "crv":"P-256",
    "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
    "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0",
    "kid":"<Public key used>"
}

JWS

Les payloads du JWS sont signées avec une signature (clé publique) qui peut être vérifiée uniquement par le serveur avec une clé de signature secrète (clé privée). Cela garantit que les payloads n’ont pas été falsifiées lorsqu’elles sont transmises entre le client et le serveur.

Le contenu du token JWS est encodé en Base64 mais non chiffré. En conséquence JWS est à utiliser uniquement lorsque vous souhaitez échanger des données non sensibles dans la payload du token.

public class ExempleBlog {
    ...
    /**
     *
     * @param DATA_TO_ENCRYPT The JWE content
     * @param keyId Key ID that help the recipient determine the valid certificate to use to decrypt the JWE
     * @param rsaPublicKey Public Key of the certificate to use
     * @return Serialized JWS
     * @throws JOSEException
     */
    public static String encryptJWS(
            String DATA_TO_ENCRYPT,
            String keyId,
            RSAKey rsaPublicKey
    ) throws JOSEException {

        // RSA signatures require a public and private RSA key pair,
        // the public key must be made known to the JWS recipient to
        // allow the signatures to be verified
        RSAKey rsaJWK = new RSAKeyGenerator(2048)
                .keyID(keyId)
                .generate();
        RSAKey rsaPublicJWK = rsaJWK.toPublicJWK();

        Payload payload = new Payload(DATA_TO_ENCRYPT);

        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
        JWSObject jws = new JWSObject(header, payload);
        JWSSigner signature = new RSASSASigner(rsaPublicJWK);
        jws.sign(signature);
        return jws.serialize();
    }
}

Exemple de JWS

eyJpYXQiOjE2NDY0MTU4NDksImFsZyI6IlJTMjU2Iiwia2lkIjoiYWI3Njk1ZjAtODk0Yi00MGZjLTlmNTctNjNkZTFiMTQ4ZWFiIn0.
eyJzdWIiOiJKV0UtRXhhbXBsZS1CbG9nIiwicm9sZSI6IkF1dGhvciIsIm5iZiI6MTY0NjQxNTg0OSwiaXNzIjoibmljb2xhcy5ldGllbm5lIiwidXNlcklkIjoiZjlmNDNlOTI
tOTdkNy00ZTE3LWIwZmQtZmY5ZTcwYzI0ZTk5IiwiZW1haWwiOiJuaWNvbGFzLmV0aWVubmVAdGFsYW5iYXMuZnIiLCJqdGkiOiIwOTI4NmZmMy0zOGVlLTRhZTQtYTQwYy1hOGEwNTY0MDMyM2IifQ.
iQxW7J4BHS5hbj9SZzb0_HfO7VvdScfutlvadO2VBV0EHNPHllyB_K5XS3dyEMkKHMFIrcBl4J3oOqTcKBU6GOtCslVo6kb1t_MERwCFDx7uzVQs027AUJRDCrDhFdyLF_
bbGoZ2jVvWIwEAWm5Bc6KNkFpJBoOZPqluBSs2DAUUAW24vL1E8ar5saCl4tYNLejJz1mLesXip5Fve4QElAw0VhAYqh9JUGamNjpqei36kI6JQj1Fe8FnyP00eFYAZYJMSmrg0SI2jC2IK3-mcRhKj-8-inz3O_gke51UKB-LZ4anDSH3_MguNgrv-II9-Wi44VNLYOnQoubGopv91g

Mais alors comment distingue-t-on la différence entre JWS et JWT ?

Indice: Comparez le header et la signature et vous aurez la réponse.

JWE

Le schéma JWE crypte le contenu au lieu de le signer. Le contenu chiffré ici correspond à la payload du token JWT. JWE, apporte ainsi la confidentialité, mais peut également être signé. Ainsi, on obtient à la fois le cryptage et la signature assurant la confidentialité, l’intégrité et l’authentification.

Ça à l’air bien ton truc, mais comment fait-on en pratique ?

Pas de problème, voila un exemple de génération de JWE avec la librairie nimbus

import com.nimbusds.jose.*;
...

public class ExempleBlog {
    ...
    /**
     * 
     * @param DATA_TO_ENCRYPT The JWE content
     * @param keyId Key ID that help the recipient determine the valid certificate to use to decrypt the JWE
     * @param rsaPublicKey Public Key of the certificate to use
     * @return Serialized JWE
     * @throws NoSuchAlgorithmException
     * @throws JOSEException
     */
    public static String encryptJWE(
            String DATA_TO_ENCRYPT,
            String keyId,
            RSAPublicKey rsaPublicKey
    ) throws NoSuchAlgorithmException, JOSEException {

        // Create the JWE header and specify:
        // {"enc":"A128CBC-HS256","iat":1489420432,"alg":"RSA-OAEP-256","kid":"54831214775126"}
        JWEHeader header = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128CBC_HS256)
                .keyID(keyId)
                .customParam(JWTClaimNames.ISSUED_AT, Instant.now().getEpochSecond())
                .build();

        // Initialized the EncryptedJWT object
        JWEObject jweEncrypted = new JWEObject(header, new Payload(DATA_TO_ENCRYPT));

        // Generate the Content Encryption Key (CEK)
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(EncryptionMethod.A128CBC_HS256.cekBitLength());

        // Create an RSA encrypted with the specified public RSA key
        RSAEncrypter encrypter = new RSAEncrypter(rsaPublicKey);

        // Doing the actual encryption
        jweEncrypted.encrypt(encrypter);

        return jweEncrypted.serialize();
    }
}

Exemple de JWE

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiaWF0IjoxNjQ2NDE1MDc1LCJhbGciOiJSU0EtT0FFUC0yNTYiLCJraWQiOiI1ZTBlZDQ3Ny0xMmU2LTQzMjEtODliNS0wNWMwMWU1ZTQyNDAifQ.
AB_5L82Qg-RKRRl1CfaNmUV_rHcriOL5Ab09VJT2oKRm9SnZoPlL_sI2fgyrjgjX01ZtgsChgzG2T-F9w6TsFssGFpCXQqHIpdW-jz6oT31hEb3KizWzyWf4SFkzUJGfSn2rqdV4XUqjcwqXjo_
-lzY1qnrB_zCUamULlHE4YFjMydmWU5k5IHSZDvwN8wU1OTQllM_clw1WHaueEePtrbr6_b5it4RRQo-jPfTVbPEzgy0zxu7o2Tx1FsKYPJ2zjR2oUGex-q8H3g5S_0hiVjgeTy6RPY8EImV-Rs302RKQM6RXpAcCKC2ERBRoC-bYUHNVebThIgOom6h-a5O74A.WhAVg-7l4OdQYDUizKTMag.
tr4Wif4GKes2U-jjzfiFYMdRaHiQIWmy0VMiH5i1QaRWkbUvVC6mK_Lpj0ICSKU7Mq8sLIPkHlCB0KDDxfXlG2u3YCyTlJFJDJ71y9T0xYEr2Sm_HOgZEcso849rVbNxp-CB_1JK-e5JQ6vPdGVV4R3D6aSrNK9cvGbuDrvY8pSR3j36kKCBCnagGL3loTRLl4Xpro3J8w4lJWRDg7g-TV10fvEFT4GT8bwbEskVeIMXf94SrQQhPAic7enmaw_Y_p-8Anby0CG7dfVQt9KbTzxkINxVXn-SBzF57h0wHPA.qkEI79YgMrjOvp0z8ivUsg

Euh, c’est quasiment le même code et le même rendu. Tu n’essaierais pas de m’enfumer par hasard !?

La différence notable se situe au niveau du header et de la signature. Comme indiqué précédemment on remarquera que seul le JWE garantie la sécurité et confidentialité des données (claims) contenu dans le token.

JWS Décrypté JWE Décrypté
image jwt.io image jwt.io

Mais comment faire pour lire le contenu de mon token depuis mon client ?

À partir du moment où on veut lire le contenu d’un token JWT ou JWS côté client, cela pose des problèmes de sécurité. Dans le cas d’un JWE il s’agit de stocker la clé publique (ou le secret partagé) sur le client. En revanche, pour JWS, et JWT “pas de problème” car rappelez-vous que le token est simplement encodé et non encrypté 😉 (en savoir plus)

En conséquence, on comprendra aisément une des contraintes de l’utilisation du JWE à savoir, l’utilisation d’une clé pour le décodage. L’utilisation de ce type de token sera donc plutôt réservée dans le cadre d’échange serveur → serveur.

Un cas d’utilisation intéressant pourrait être l’échange de données entre de 2 serveurs qui ne se connaissent pas. Un token JWE emit par un serveur peut alors transiter par client puis vers le second serveur sans que le client(ou tout autre intermédiaire) soit en capacité de lire le contenu de ce token.

Ohh attend 1 minute, dans ce cas JWT c’est sécure ou pas !!!?

Si on ne fait pas attention aux claims contenues dans le token les conséquences en cas d’interception peuvent être grave. Il est préconisé de ne jamais y faire figurer de donnée sensible aussi bien dans un jwt qu’un jws. En revanche, si ce point pose problème il faut alors utiliser un token JWE.

Nota

Le header d’un token JWT contient plusieurs informations dont une intitulée alg. Celle-ci indique le JWA utilisé pour la payload. Donc, dans le cas du JWT non sécurisé, cette propriété est simplement laissée à la valeur ‘none’. En d’autres mots les JWT non sécurisés, correspondent à un JWS sans signature (étrange de faire cela, mais bon, c’est possible)

Des alternatives : Pff j’connais JWT c’est pas terrible, il y a mieux

Il est possible de comparer JWT à d’autres solutions telles que les cookies, SWT (simple Web Token), SAML (Security Assertion Markup Language Tokens) mais nous ne rentrerons pas dans le détail de chaque.

Ce qu’il faut savoir :

Cookies

Le plus souvent un cookie est une chaîne de caractère random stockée coté serveur et coté client. Bien qu’elle ne contienne aucune donnée client, elle permet d’identifier une session utilisateur auprès du serveur. Elle ne permet pas de vérifier l’expéditeur de la requête du client vers le serveur, ni l’intégrité des données contenues dans ladite requête. Par ailleurs, son utilisation nécessite des actions coté serveur afin de stocker/invalider les sessions utilisateurs au fil du temps.

Ce type d’approche convient à une utilisation stateful d’une application. En revanche, pour une approche stateless, passez votre chemin.

SWT (Simple Web Token)

En fait, ce qui se trouve à l’intérieur d’un SWT est visible de tous. C’est un simple token, après tout contenant un ensemble de paire clé-valeur encodées.

Cet encodage, repose sur une clé partagée entre le client et le serveur. Elle permet de générer un hash des claims transmises dans le token assurant ainsi son intégrité lors des échanges.

SAML (Security Assertion Markup Language)

Si sur le papier, le principe de JWT et de SAML est de concourir à l’identification d’un utilisateur, SAML va plus loin dans ce principe avec la mise en place d’un IdP (Identity Provider) et d’un Service Provider. En très simple, on ne joue pas dans la même cour ! (En savoir plus)

En résumé / Bilan des courses

  • JWA : c’est juste, l’ensemble des algorithmes à utiliser pour générer un token
  • JWK : Ensemble de clés permettant de lier un token JWT à une clé publique
  • JWT : La base à condition de ne pas transmettre d’info sensible hein !
  • JWS : Juste pour l’intégrité du token
  • JWE : top du top d’un point de vue sécurité pour les tokens JSON. Il garantit l’intégrité, la sécurité et confidentialité des données contenues dans le token. À utiliser pour les échanges serveur → serveur.

Sources

Date

Auteur

Avatar Nicolas ETIENNE

Nicolas ETIENNE

Lead Developer

Catégories

architecture back

Tags

#JWT #JWE #JWS #JWA #JWK