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.
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
En décrypté on obtient 3 parties : le header, la payload et la signature
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"
}
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
}
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
)
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.
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 |
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>"
}
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
Indice: Comparez le header et la signature et vous aurez la réponse.
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
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é |
---|---|
À 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.
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)
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)