Base64, encodage d’URL et JWT expliqués
Ces trois-là apparaissent dès que des données doivent circuler sous forme de texte : dans les URL, les en-têtes HTTP, le JSON et les jetons de connexion. Aucun ne cache quoi que ce soit. Ce guide explique le fonctionnement de chacun, où vous le rencontrerez et les erreurs à l’origine de la plupart des problèmes.
Encoder n’est pas chiffrer
Encoder change la façon d’écrire les données pour qu’elles traversent un canal qui n’accepte que certains caractères. N’importe qui peut l’annuler, car aucune clé n’intervient. Le chiffrement transforme aussi les données, mais seul le détenteur de la clé peut revenir en arrière. Un hachage est encore autre chose : une empreinte à sens unique qu’on ne peut pas inverser, comme l’explique Les fonctions de hachage expliquées.
Base64 et l’encodage d’URL sont des encodages. Un JWT est généralement signé, ce qui prouve qui l’a émis et qu’il n’a pas été modifié, mais son contenu n’est qu’encodé. Si une valeur doit rester secrète, il lui faut un chiffrement, pas l’un de ces formats.
Comment fonctionne Base64
Base64 lit les données trois octets (24 bits) à la fois et les découpe en quatre groupes de six bits. Chaque groupe est un nombre de 0 à 63, écrit sous la forme d’un caractère d’un alphabet de 64 : A–Z pour 0–25, a–z pour 26–51, 0–9 pour 52–61, puis + et /. Le mot « Man » montre les étapes :
| Étape | M | a | n |
|---|---|---|---|
| Valeurs des octets | 77 | 97 | 110 |
| Bits | 01001101 | 01100001 | 01101110 |
| Quatre groupes de 6 bits | 010011 · 010110 · 000101 · 101110 = 19 · 22 · 5 · 46 | ||
| Base64 | T W F u → TWFu | ||
Quand la longueur de l’entrée n’est pas un multiple de trois, le dernier groupe est complété et signalé par un remplissage : un octet restant donne deux caractères et ==, deux octets restants donnent trois caractères et =. « Ma » devient TWE= et « M » devient TQ==.
Comme trois octets deviennent quatre caractères, Base64 alourdit les données d’environ un tiers, plus les retours à la ligne qu’ajoutent certains formats. Une photo de 3 Mo envoyée en pièce jointe voyage sous forme d’environ 4 Mo de texte, et une image intégrée à une feuille de style en URL de données pèse environ un tiers de plus que le fichier.
Où l’on rencontre Base64
- Les pièces jointes d’e-mail (MIME) sont du texte Base64, coupé à 76 caractères par ligne.
- Les URL de données comme
data:image/png;base64,iVBORw0…placent un petit fichier directement dans le HTML ou le CSS. - L’authentification HTTP Basic envoie
user:passworden Base64 dans l’en-tête Authorization. Cela ne cache le mot de passe qu’aux regards distraits ; ce n’est donc sûr qu’en HTTPS. - Les certificats et les clés des fichiers PEM sont du Base64 entre les lignes
-----BEGIN …-----et-----END …-----, coupé à 64 caractères. - JSON et XML transportent des valeurs binaires, comme une signature ou une miniature, sous forme de chaînes Base64, faute de type binaire.
Base64URL et le remplissage
Le + et le / du Base64 standard ont un sens dans les URL et les chemins de fichiers ; un second alphabet, Base64URL, utilise donc - et _ à la place. Il omet généralement aussi le remplissage =, puisque le décodeur peut calculer la longueur. Les JWT, de nombreux jetons d’API et certains noms de fichiers utilisent Base64URL. Pour passer du Base64 standard au Base64URL, remplacez + par -, / par _ et supprimez les = finaux ; pour revenir en arrière, inversez les remplacements et ajoutez des = jusqu’à ce que la longueur soit un multiple de quatre.
L’encodage-pourcent dans les URL
Une URL ne peut contenir qu’un ensemble limité de caractères, et certains structurent l’adresse : / et ? séparent le chemin et la requête, & et = séparent les paramètres, # ouvre le fragment. Quand une valeur contient l’un de ces caractères, ou quoi que ce soit hors ASCII, elle est encodée en pourcent : chaque octet de sa forme UTF-8 s’écrit % suivi de deux chiffres hexadécimaux. « café » devient caf%C3%A9, car é correspond aux deux octets C3 A9.
Les lettres, les chiffres et - . _ ~ n’ont jamais besoin d’être encodés. Voici les caractères qui posent le plus souvent problème :
| Caractère | Encodé | Pourquoi c’est important |
|---|---|---|
| espace | %20 | Interdit dans une URL |
| & | %26 | Commencerait un nouveau paramètre |
| = | %3D | Terminerait le nom du paramètre |
| ? | %3F | Commencerait la requête |
| # | %23 | Commencerait le fragment, qui n’est pas envoyé au serveur |
| / | %2F | Ajouterait un segment de chemin |
| + | %2B | Lu comme un espace dans les données de formulaire |
| % | %25 | Commencerait une séquence d’échappement |
Encodez chaque valeur séparément, puis assemblez l’URL. En JavaScript, encodeURIComponent le fait pour une valeur et encode aussi / ? & = #, tandis que encodeURI est destiné à une adresse entière et laisse ces caractères intacts. Utiliser encodeURI sur une seule valeur est une cause fréquente de chaînes de requête cassées.
Signes plus, espaces et double encodage
Les formulaires HTML envoient leurs champs en application/x-www-form-urlencoded, où un espace devient +. Dans la syntaxe générale des URL, + n’est qu’un signe plus. Une recherche de « C++ » envoyée sous la forme q=C++ arrive donc comme « C » suivi de deux espaces sur un serveur qui décode les données de formulaire ; il faut l’envoyer sous la forme q=C%2B%2B. Quand vous décodez une chaîne de requête à la main, vérifiez d’abord quelle règle l’a produite.
Le double encodage se produit quand une valeur déjà encodée est encodée à nouveau : %20 devient %2520, car le % lui-même devient %25. Si vous voyez %25 suivi de deux chiffres hexadécimaux dans une URL, un maillon de la chaîne a encodé deux fois ; décodez une fois et cherchez où se trouve l’étape en trop.
Ce que contient un JWT
Un JSON Web Token (JWT) transporte des déclarations (claims), comme l’identité de l’utilisateur et la date limite de validité du jeton, du système qui l’émet vers les systèmes qui l’acceptent. La forme signée courante comporte trois parties Base64URL séparées par des points : un en-tête, une charge utile et une signature. Décodées, les deux premières sont du JSON ordinaire :
Header: {"alg": "HS256", "typ": "JWT"}
Payload: {"sub": "user-42", "role": "editor", "iat": 1767225600, "exp": 1767229200}
La norme (RFC 7519) définit quelques déclarations enregistrées ; les applications ajoutent les leurs, comme role ci-dessus.
| Déclaration | Signification |
|---|---|
| iss | Émetteur : qui a créé le jeton |
| sub | Sujet : généralement l’ID de l’utilisateur ou du compte |
| aud | Audience : le service auquel le jeton est destiné |
| exp, nbf, iat | Expiration, pas avant, émis le : temps Unix en secondes |
| jti | ID du jeton, pour détecter une réutilisation ou révoquer un jeton précis |
Comme la charge utile n’est qu’encodée, quiconque détient le jeton peut la lire. N’y mettez ni données personnelles ni secrets, et gardez les jetons légers : ils accompagnent chaque requête, et de nombreux serveurs limitent la taille des en-têtes.
Signé ne veut pas dire secret : comment un JWT est vérifié
La signature est calculée sur l’en-tête et la charge utile. Avec HS256, l’émetteur et le destinataire partagent un secret et utilisent HMAC avec SHA-256. Avec RS256 ou ES256, l’émetteur signe avec une clé privée et les destinataires vérifient avec la clé publique correspondante ; ils ne peuvent donc pas créer de jetons eux-mêmes.
Un serveur qui reçoit un jeton doit vérifier la signature avec l’algorithme et la clé attendus, et contrôler exp, nbf, iss et aud, avant de se fier à la moindre déclaration. Le simple décodage, comme le fait l’outil LeedyBox, saute tout cela : il montre ce qu’affirme un jeton, pas s’il est authentique. Les bibliothèques doivent aussi refuser l’algorithme « none », qui désigne un jeton non signé, sauf si des jetons non signés sont prévus. Quand le contenu lui-même doit rester caché, le jeton est chiffré (JWE) ; il comporte alors cinq parties illisibles sans la clé.
Problèmes courants et solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| Le Base64 se décode en texte illisible | Les données sont binaires, ou du texte dans un autre encodage | Vérifiez ce qui a été encodé ; enregistrez les données binaires dans un fichier |
| « Caractère invalide » au décodage | Un préfixe data:, des guillemets ou l’alphabet Base64URL | Supprimez tout ce qui précède la virgule ; utilisez un décodeur qui accepte - et _ |
| Un paramètre arrive tronqué | Un & ou un # non encodé dans la valeur | Encodez chaque valeur avec encodeURIComponent |
| %2520 dans une URL | La valeur a été encodée deux fois | Encodez une seule fois, là où l’URL est construite |
| Jeton refusé alors que exp est dans le futur | Audience ou émetteur incorrects, décalage d’horloge ou clé différente | Comparez aud, iss et la clé de signature avec ce qu’attend le serveur |
| Date d’expiration dans des milliers d’années | exp écrit en millisecondes au lieu de secondes | Stockez exp, nbf et iat en secondes |
Questions fréquentes
Base64 permet-il de protéger un mot de passe ou une clé d’API ?
Non. N’importe qui peut inverser du Base64 en une seconde. Un identifiant encodé en Base64, par exemple dans un fichier de configuration ou une URL, est aussi exposé que du texte en clair.
Puis-je mettre des données personnelles dans la charge utile d’un JWT ?
Évitez-le. Quiconque détient le jeton peut lire la charge utile, y compris des extensions de navigateur, des journaux et des proxys qui voient la requête. Placez un identifiant dans le jeton et récupérez les détails sur le serveur.
L’encodage d’URL protège-t-il contre les attaques par injection ?
Seulement dans le sens où une valeur ne peut pas casser la structure de l’URL. Le serveur doit toujours valider et échapper la valeur décodée selon l’endroit où il l’utilise, comme du SQL, du HTML ou une commande shell.
Pourquoi ma chaîne Base64 est-elle plus longue que le fichier ?
Trois octets deviennent quatre caractères, le texte est donc environ 33 % plus grand, et les retours à la ligne ajoutent un peu plus. Pour les gros fichiers, envoyer les octets directement est plus efficace que Base64.