Base64 et encodage d'URL : but, pièges, usage correct

Publié le 2026-04-13 7 min de lecture

Résumé (TL;DR)

Exécutez echo -n "Hi" | base64 et vous obtenez exactement quatre caractères en retour : SGk=. Deux octets d’entrée (16 bits) sont découpés en morceaux de 6 bits, complétés pour une sortie de 4 caractères, et voilà toute l’arithmétique. Traitez cette règle simple comme une valeur par défaut « on enveloppe tout pour être sûr » et le coût s’accumule vite — trois octets d’entrée deviennent toujours quatre caractères de sortie, ce qui représente environ 33 % d’inflation, et un JPEG de 1,2 Mo que j’ai vu un service Base64-envelopper dans un champ JSON s’est retrouvé à environ 1,64 Mo sur le fil. L’encodage n’est ni du chiffrement, ni de la compression. C’est un ensemble de règles pour exprimer un alphabet à l’intérieur d’un autre pour que les données survivent à un canal qui sinon les mutilerait. Base64 (RFC 4648 §4) existe pour faire voyager des octets arbitraires à travers des systèmes qui ne comprennent que du texte — corps d’e-mail, champs de chaîne JSON, données d’image en ligne. Base64URL (même RFC, §5) échange deux caractères (+-, /_) pour que le résultat soit sûr à glisser dans un chemin d’URL, un nom de fichier ou un JWT. Le percent-encoding (RFC 3986) est un outil séparé : il convertit les caractères qui signifient quelque chose pour les URL (?, &, #, espace, Unicode) en séquences %XX qui ne le font pas. Choisissez l’encodage qui correspond au canal : binaire si le chemin le prend en charge, Base64 pour les canaux texte, Base64URL pour les données intégrées dans une URL, et percent-encoding pour tout ce qui est déjà dans un contexte d’URL. Les chaînes Base64 révélées restent lisibles pour quiconque a un décodeur, alors ne traitez jamais l’encodage comme un mécanisme de secret.

Contexte et concepts

Les canaux uniquement texte existaient bien avant qu’on leur demande de transporter des images. L’e-mail supposait historiquement de l’ASCII 7 bits et dépouillait ou corrompait les octets dont le bit de poids fort était à 1 ; les en-têtes HTTP restreignent encore certains caractères ; les champs de chaîne JSON sont en UTF-8 mais ne peuvent pas contenir d’octets de contrôle bruts (0x00–0x1F) en toute sécurité. Base64, normalisé dans la RFC 4648, résout le problème en mappant chaque 3 octets d’entrée (24 bits) sur 4 caractères de sortie de 6 bits chacun. L’alphabet compte 64 caractères : A-Z (indices 0–25), a-z (26–51), 0-9 (52–61), + (62), / (63), avec = comme padding pour maintenir la longueur de sortie multiple de quatre. Un octet d’entrée produit XX== (8 bits → 6+2 padés), deux octets produisent XXX= (16 bits → 6+6+4 padés), et trois octets produisent XXXX sans padding.

La variante URL-safe de Base64 ne change que les indices d’alphabet 62 et 63 — + devient -, / devient _ — parce que + se décode comme espace dans les soumissions de formulaires historiques et / est un séparateur de chemin. Dans de nombreux contextes URL-safe, le padding = est aussi supprimé ; c’est la forme utilisée dans les JSON Web Tokens (RFC 7519), où un triplet compact header.payload.signature doit tenir dans un en-tête Authorization: Bearer ... sans ré-échappement.

Le percent-encoding (parfois appelé URL-encoding) est séparé. La RFC 3986 définit deux catégories d’ASCII : les caractères non réservés (A-Z, a-z, 0-9, -, ., _, ~) qui peuvent apparaître tels quels, et les caractères réservés (:, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =) qui signifient structurellement quelque chose et doivent être encodés quand ils apparaissent comme données plutôt que comme syntaxe. Tout octet hors de cet ensemble — y compris les octets d’un caractère non-ASCII encodé en UTF-8 — est écrit comme %XX en hexadécimal. encodeURIComponent en JavaScript encode agressivement les caractères réservés et non réservés pour usage dans un composant ; encodeURI est plus permissif parce qu’il suppose que vous passez une URL entière.

Une conséquence subtile de ces trois encodages est qu’ils ne sont pas interchangeables, même quand les octets qu’ils produisent se chevauchent. Un paramètre de requête percent-encodé reste des caractères ASCII encodant d’autres caractères ASCII ; le passer à travers un décodeur Base64 produit des ordures. Une chaîne Base64 standard déposée dans un chemin d’URL casse silencieusement parce que le / à l’intérieur est lu comme un séparateur de chemin — j’ai vu un collègue perdre une demi-journée à cause d’un seul caractère de ce genre. Ces confusions sont la source de la moitié des bugs d’encodage en production.

Comparaison et données

PropriétéBase64 (standard)Base64URLPercent-encoding
Quand l’utiliserCanal texte qui rejette le binaire brut (MIME d’e-mail, chaîne JSON d’octets)Idem, mais intégré dans une URL, un nom de fichier ou un JWTUn seul composant d’URL contenant des caractères réservés ou non-ASCII
AlphabetA–Z a–z 0–9 + / =A–Z a–z 0–9 - _ (padding optionnel)Ensemble non réservé ; tout le reste devient %XX
SurcoûtEnviron 33 % (4 caractères pour 3 octets), plus retour à la ligne à 76 caractères en MIMEEnviron 33 %, pas de padding en usage typiqueVariable — 1 octet d’UTF-8 devient 3 octets ASCII (%XX)
Casse sur+ et / bruts dans les URL, longueur non padée dans les décodeurs strictsDécodeurs Base64 standard qui exigent +/=Double encodage (données déjà percent-encodées réencodées)
Usages courantsPièces jointes MIME, data URI, binaires en ligne dans JSONJWT, IDs URL-safe, liens courtsParamètres de requête, segments de chemin, corps de formulaire

Les chiffres comptent pour le budget. Une image de 1 Mo encodée en Base64 devient environ 1,37 Mo ; avec le retour à la ligne MIME à 76 caractères, ajoutez environ 2 % de plus. Dans une réponse HTTP, cette inflation de taille frappe à la fois le serveur et le parser du client. Le percent-encoding est généralement un plus petit problème au niveau de la chaîne mais peut multiplier les octets pour le texte CJK : un caractère coréen en UTF-8 prend 3 octets, qui deviennent 9 caractères ASCII après encodage. Le mot à deux caractères « 안녕 » devient la séquence de 18 octets %EC%95%88%EB%85%95 à l’intérieur d’une URL.

Scénarios concrets

Scénario 1 — Pièces jointes d’e-mail. Un PDF voyage à l’intérieur d’une partie MIME avec Content-Transfer-Encoding: base64. Le client mail encode le fichier en Base64, retourne à la ligne à 76 caractères (la limite MIME classique), et l’envoie. Le destinataire inverse le processus. L’hypothèse texte-seulement de SMTP a fait de Base64 la valeur par défaut ici bien avant que des extensions modernes comme 8BITMIME ou BINARYMIME n’existent, et la plupart des serveurs mail émettent encore du Base64 par sûreté. Le surcoût de 33 % est accepté comme le coût d’une livraison fiable.

Scénario 2 — JSON Web Tokens. Un JWT (RFC 7519) est header.payload.signature, où chaque segment est du JSON ou des octets encodés en Base64URL, sans padding. L’alphabet URL-safe signifie que les tokens peuvent apparaître dans des en-têtes Authorization, des paramètres de requête access_token et des lignes de log sans ré-échappement. L’absence de padding les garde courts. Un avertissement : quiconque décode un JWT peut lire les claims — l’encodage n’est pas de la sécurité ; la signature HMAC ou RSA l’est. Ne mettez pas de secrets dans le payload.

Scénario 3 — Data URI pour petites images. Un background-image: url("data:image/png;base64,iVBORw0K...") met un PNG directement en ligne dans le CSS. Cela évite un aller-retour pour les petits actifs comme les icônes. Mais le surcoût de 33 % de Base64 et la perte du cache navigateur et des requêtes parallèles signifient que c’est seulement gagnant pour des actifs assez petits pour que l’aller-retour HTTP supplémentaire ait coûté plus. D’après mon expérience, le point d’équilibre est environ 1–2 Ko ; au-dessus, un fichier mis en cache séparé ou un SVG est généralement plus rapide.

Scénario 4 — Upload d’avatar utilisateur. Un anti-motif courant consiste à lire un fichier avec FileReader.readAsDataURL, qui renvoie une chaîne data:image/png;base64,..., puis à POSTer cette chaîne comme champ JSON. Ça fonctionne, mais c’est presque jamais la bonne forme : le payload est maintenant 33 % plus gros, le serveur doit le décoder avant d’écrire sur le disque, et les deux côtés brûlent de la mémoire sur la forme chaîne. Dans un cas que j’ai observé, une image de 5 Mo a enflé à environ 6,7 Mo de JSON et a causé des timeouts sur les réseaux mobiles ; passer à multipart/form-data a livré le même fichier à 5 Mo. Recourez à Base64 dans ce flux seulement quand la couche de transport exige réellement une chaîne JSON, par exemple quand une API tierce n’accepte pas multipart.

Idées fausses courantes

« Base64 est du chiffrement. » Non. Le mapping est publié dans la RFC 4648, l’alphabet est fixe, et tout décodeur renvoie les octets originaux. L’encodage protège le transport, pas le contenu. Si le payload est sensible, chiffrez d’abord et encodez le texte chiffré en Base64 pour le transport.

« L’URL encoding n’est nécessaire que pour les caractères non-ASCII. » Beaucoup de caractères ASCII réservés doivent aussi être encodés quand ils apparaissent comme données. Une valeur de requête contenant & doit devenir %26, ou le serveur la parsera comme un nouveau paramètre. # doit devenir %23, ou tout ce qui suit est traité comme le fragment. La règle est structurelle, pas basée sur un jeu de caractères.

« Tout ce qui passe par HTTP doit être encodé en Base64 par sûreté. » HTTP porte joyeusement des corps binaires — Content-Type: application/octet-stream, transfert par morceaux, toute valeur d’octet. Base64 est un contournement pour les canaux qui ne le font pas, et payer 33 % de surcoût quand le canal gère déjà les octets est pure taxe. Utilisez multipart/form-data ou un corps brut pour les uploads de fichiers, et réservez Base64 aux cas où le canal a réellement besoin de texte (un champ JSON, un paramètre d’URL, un corps MIME).

« Base64 et Base64URL sont interchangeables. » Non. Passer une chaîne URL-safe à un décodeur Base64 strict qui attend +/ échouera ou produira des ordures. Les bibliothèques fournissent généralement les deux ; faites correspondre l’encodeur au décodeur de bout en bout. Buffer.from(s, 'base64') de Node accepte l’un ou l’autre alphabet, mais toutes les bibliothèques standard ne sont pas aussi indulgentes.

Liste de vérification

  1. Le canal est-il uniquement 7 bits ou du texte structuré ? Corps d’e-mail, chaîne JSON d’octets, URI data: CSS → Base64.
  2. La valeur encodée va-t-elle dans une URL, un nom de fichier ou un JWT ? Utilisez Base64URL et décidez si le padding est autorisé.
  3. La valeur est-elle déjà un composant d’URL ? Percent-encodez, et seulement les parties qui en ont besoin.
  4. Les données sont-elles volumineuses ? Considérez si le canal exige vraiment l’encodage — un corps binaire brut évite 33 % de surcoût.
  5. Le décodage est-il strict ou permissif ? Faites correspondre les alphabets standard/URL-safe de bout en bout ; ne les mélangez pas.
  6. La confidentialité est-elle un objectif ? Chiffrez avant d’encoder. L’encodage seul ne rend jamais les données secrètes.

Outil associé

L’encodeur/décodeur Base64 Patrache Studio gère les variantes standard et URL-safe localement, donc les octets d’entrée ne quittent pas votre navigateur — utile quand les données sont un token ou une petite clé. Si le payload Base64 se trouve être un JWT, associez-le à Formatage, validation et schéma JSON en pratique pour inspecter les claims décodés proprement. Et si le blob encodé est un ID compact dérivé d’un UUID, UUID v1 vs v4 vs v7 : choisir une clé primaire de BD explique pourquoi les octets originaux que vous avez encodés en Base64URL importent encore pour l’ordre de tri et l’indexation.

Références