Base64 ou Base64URL : ce qui change et quand utiliser chacun

Base64URL, c'est le Base64 standard avec deux caractères remplacés, - pour + et _ pour /, et en général sans le padding =.

Les deux encodages sont définis dans la RFC 4648 : le Base64 standard dans la section 4, et la variante « URL and filename safe », base64url, dans la section 5. Ils découpent les données en groupes de 6 bits exactement de la même manière et partagent 62 de leurs 64 caractères. Seuls diffèrent les caractères des valeurs 62 et 63, ainsi que la convention de padding (remplissage). Passer de l'un à l'autre revient donc à échanger des caractères, sans réencoder les données, et les confondre est un bug facile à commettre qui passe souvent inaperçu.

Les deux alphabets côte à côte

Base64 (RFC 4648, section 4)Base64URL (RFC 4648, section 5)
Valeurs 0 à 61A-Z a-z 0-9A-Z a-z 0-9
Valeur 62+-
Valeur 63/_
Padding avec =Obligatoire, sauf si le format qui l'utilise en dispose autrementGénéralement omis
Utilisable tel quel dans les URL et les noms de fichiersNonOui

Les mêmes octets sous les deux formes :

Bytes       fb ff bf
Base64      +/+/
Base64URL   -_-_

Text        <<???>>
Base64      PDw/Pz8+Pg==
Base64URL   PDw_Pz8-Pg

Les données binaires encodées, comme les hashs, les clés et les tokens aléatoires, contiennent très souvent un + ou un / quelque part : la différence n'a rien d'un cas marginal. La RFC 4648 précise elle-même que base64url ne doit pas être considéré comme le même encodage que base64, et que le code doit nommer la variante qu'il attend.

Pourquoi le Base64 standard casse dans les URL et les noms de fichiers

Chacun des trois caractères supplémentaires du Base64 standard a un sens ailleurs :

  • + est lu comme une espace lorsqu'une chaîne de requête est décodée comme des données de formulaire, ce que font URLSearchParams, $_GET en PHP et la plupart des frameworks côté serveur. Un token comme ab+c arrive sous la forme ab c.
  • / sépare les segments de chemin : une valeur Base64 placée dans un chemin crée des segments supplémentaires, et certains serveurs web refusent la forme encodée %2F dans les chemins. Ce caractère est aussi interdit dans les noms de fichiers sous Windows, macOS et Linux.
  • = sépare le nom d'un paramètre de sa valeur.

Encoder la valeur en pourcent (%2B, %2F, %3D) fonctionne si chaque morceau de code concerné le fait exactement une fois. En pratique, une couche oublie de le faire ou le fait deux fois : c'est pourquoi les formats conçus pour les URL utilisent Base64URL et évitent tout échappement.

Les règles de padding

Base64 encode 3 octets en 4 caractères. Quand la longueur des données n'est pas un multiple de 3, le dernier groupe est plus court, et le Base64 standard le complète jusqu'à 4 caractères avec = :

Octets dans le dernier groupeBase64Base64URL sans padding
1, par exemple ff/w==_w
2, par exemple fb ff+/8=-_8
3, par exemple fb ff bf+/+/-_-_

La section 3.2 de la RFC 4648 impose le padding, sauf si la spécification qui s'y réfère indique explicitement le contraire. La section 5 ajoute que = devrait normalement être encodé en pourcent dans une URL, et que le padding peut être omis lorsque la longueur des données est de toute façon connue. Les formats fondés sur Base64URL profitent de cette possibilité : les JSON Web Signatures (RFC 7515), et donc les JWT, suppriment tous les = finaux, tout comme les code challenges PKCE (RFC 7636).

Sans padding, n octets produisent ⌈4n/3⌉ caractères : 16 octets donnent 22 caractères au lieu de 24, et 32 octets en donnent 43 au lieu de 44. Une longueur dont le reste de la division par 4 vaut 1 n'est jamais valide, quelle que soit la variante.

Où chacun est utilisé

ContexteVariante
En-tête, payload et signature d'un JWTBase64URL sans padding
Valeurs des JSON Web Keys, comme n et eBase64URL sans padding
code_challenge de PKCE (OAuth)Base64URL sans padding
Data URI, data:image/png;base64,...Base64 standard (RFC 2397)
Pièces jointes d'e-mail (MIME)Base64 standard, lignes de 76 caractères au maximum (RFC 2045)
Certificats et clés PEMBase64 standard, lignes de 64 caractères (RFC 7468)
En-tête d'authentification HTTP BasicBase64 standard (RFC 7617)
Tokens dans les URL, noms de fichiers, clés de cacheBase64URL est le choix sûr

Les fonctions par défaut des bibliothèques suivent le Base64 standard : btoa, b64encode en Python, base64_encode en PHP et Base64.getEncoder() en Java produisent tous +, / et du padding. Base64URL doit être demandé explicitement.

Convertir de l'un à l'autre

atob et btoa ne connaissent que le Base64 standard : avec elles, c'est à vous de convertir les caractères et de corriger le padding.

// Base64 -> Base64URL
const url = b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

// Base64URL -> Base64
let std = url.replace(/-/g, '+').replace(/_/g, '/');
std += '='.repeat((4 - std.length % 4) % 4);

Les navigateurs récents proposent aussi Uint8Array.prototype.toBase64({ alphabet: 'base64url', omitPadding: true }) et Uint8Array.fromBase64(s, { alphabet: 'base64url' }), qui travaillent directement sur les octets ; vérifiez leur prise en charge dans les navigateurs que vous ciblez avant de vous y fier. Les autres plateformes offrent un support direct ou une formule courte :

// Node.js
Buffer.from(data).toString('base64url');     // no padding
Buffer.from(s, 'base64url');

# Python
base64.urlsafe_b64encode(data).rstrip(b'=')
base64.urlsafe_b64decode(s + '=' * (-len(s) % 4))

// PHP
rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
base64_decode(strtr($s, '-_', '+/'), true);

// Java
Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
Base64.getUrlDecoder().decode(s);

En Python, urlsafe_b64decode rejette une entrée sans padding, d'où l'ajout des signes =. En PHP, base64_decode accepte un padding manquant, même en mode strict.

Les pièges

  • Des bugs qui passent les tests. Un décodeur standard n'échoue que sur une entrée Base64URL qui contient - ou _. Pour des données aléatoires, cela concerne environ la moitié des valeurs de 16 octets et environ les trois quarts des valeurs de 32 octets, comme les empreintes SHA-256 (1 − (62/64) puissance 42 ≈ 74 %). Un test avec une ou deux valeurs d'exemple peut donc facilement passer. Pourquoi les décodeurs rejettent ces chaînes, et comment chaque langage réagit : tout est expliqué dans Chaîne Base64 invalide.
  • Les décodeurs permissifs masquent la confusion. Sous Node.js, Buffer.from(s, 'base64') accepte les deux alphabets, ce qui est pratique mais signifie que le bug n'apparaît que dans un autre service. Le b64decode par défaut de Python ignore silencieusement - et _, puis renvoie de mauvais octets ou une erreur trompeuse sur le nombre de caractères.
  • Base64URL n'est pas l'encodage URL. Encoder en pourcent une chaîne Base64 standard donne %2B, %2F et %3D : cela ne la transforme pas en Base64URL, et un décodeur qui attend l'un n'acceptera pas l'autre.
  • Aucune des deux variantes ne cache quoi que ce soit. Le payload d'un JWT n'est que du JSON encodé en Base64URL, lisible par quiconque détient le token. Collez-en un dans le décodeur JWT pour voir son en-tête et son payload.

L'encodeur Base64 de ce site produit du Base64 standard avec padding ; appliquez la substitution ci-dessus si vous avez besoin de Base64URL. Pour le décodage, le décodeur Base64 accepte les deux alphabets et ajoute lui-même le padding manquant : vous pouvez donc coller l'une ou l'autre forme.

Questions Fréquentes

Quelle est la différence entre Base64 et Base64URL ?

Base64URL remplace + par - et / par _, pour que le résultat soit utilisable dans les URL et les noms de fichiers, et omet généralement le padding =. Les 62 autres caractères et le découpage des données en groupes de 6 bits sont identiques.

Un décodeur Base64 classique peut-il lire du Base64URL ?

Seulement après conversion : remplacez - par + et _ par /, puis ajoutez des = jusqu'à ce que la longueur soit un multiple de 4. Certains décodeurs, comme Buffer sous Node.js, acceptent directement les deux alphabets.

Pourquoi les JWT utilisent-ils Base64URL sans padding ?

Les tokens circulent dans des URL, des en-têtes HTTP et des cookies, où +, / et = posent problème. La RFC 7515 définit l'encodage des JSON Web Signatures comme du base64url dont tous les caractères = finaux sont supprimés.

Base64URL est-il plus long que Base64 ?

Non. Les deux produisent le même nombre de caractères par octet ; sans padding, Base64URL est même plus court, de deux caractères au maximum.