Chaîne Base64 invalide : pourquoi le décodage échoue et comment corriger

Un décodeur Base64 rejette une chaîne pour l'une de ces trois raisons : un caractère hors de l'alphabet, une longueur impossible ou un remplissage mal placé.

Base64 utilise 64 caractères, A à Z, a à z, 0 à 9, + et /, plus = comme remplissage (padding) en fin de chaîne (RFC 4648, section 4). Tout autre caractère fait échouer un décodeur strict. Le plus déroutant, c'est que les décodeurs ne s'accordent pas sur ce que recouvre « tout autre caractère » : une chaîne Base64URL comme PDw_Pz8-Pg== se décode correctement avec Buffer de Node.js, échoue en .NET et se transforme sans bruit en octets erronés en Python. Savoir ce que tolère votre décodeur, c'est déjà l'essentiel de la solution.

L'erreur dans chaque langage

OùCe que vous obtenez
Chrome et Edge, atob()InvalidCharacterError : Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
Node.js, atob()InvalidCharacterError : Invalid character, ou The string to be decoded is not correctly encoded. pour une longueur incorrecte
Python, base64.b64decode()binascii.Error : Incorrect padding, Invalid base64-encoded string: number of data characters (5) cannot be 1 more than a multiple of 4 ou, avec validate=True, Only base64 data is allowed
.NET, Convert.FromBase64String()FormatException : The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters.
Java, Base64.getDecoder().decode()IllegalArgumentException : Illegal base64 character suivi du code du caractère en hexadécimal, par exemple 2d pour -
PHP, base64_decode($s, true)Aucun message : la fonction renvoie false
AWS CLI version 2Invalid base64: suivi de la valeur transmise

Sous Node.js, Buffer.from(s, 'base64') fait figure d'exception : il ne lève jamais d'erreur. Il ignore les caractères qu'il ne reconnaît pas, si bien qu'une chaîne abîmée produit des octets abîmés sans le moindre message.

Ce que tolère chaque décodeur

EntréeatobPython b64decode.NETPHP strict
Remplissage manquant : SGVsbG8DécodeIncorrect paddingÉchoueDécode
Remplissage en trop : SGVsbG8==ÉchoueDécodeÉchoueRenvoie false
Espaces ou sauts de ligneIgnorésIgnorés (échec avec validate=True)IgnorésIgnorés
- et _ de Base64URLÉchoueSupprimés sans bruit : octets erronés ou erreurÉchoueRenvoie false
Autres caractères : guillemets, \, !, :ÉchoueSupprimés sans bruitÉchoueRenvoie false

Le mode par défaut de Python mérite un avertissement. Il écarte tous les caractères hors de l'alphabet avant de décoder, si bien que les lettres d'un préfixe comme data:image/jpeg;base64, sont décodées comme s'il s'agissait de données. Selon la longueur du préfixe, vous obtenez une erreur déroutante sur le nombre de caractères, ou aucune erreur et quelques octets parasites devant le vrai contenu. Passez validate=True si vous voulez qu'une entrée incorrecte échoue clairement.

Des caractères qui n'ont rien à faire là

Quand un décodeur strict signale un caractère invalide, la chaîne a presque toujours ramassé quelque chose en chemin :

  • Des guillemets ou du texte autour. Copier "SGVsbG8=" avec ses guillemets, ou une ligne "key": "value" entière, ajoute des caractères qui ne sont pas du Base64.
  • Un préfixe de data URI. Dans data:image/png;base64,iVBOR..., seule la partie qui suit la virgule est du Base64. Supprimez tout jusqu'à la virgule incluse.
  • Des échappements JSON. json_encode de PHP écrit / sous la forme \/, et un contenu découpé en lignes, comme un certificat PEM, est stocké avec des \n entre les lignes. Si vous copiez le texte JSON brut au lieu de la valeur analysée, les antislashs viennent avec. Analysez d'abord le JSON, puis décodez la chaîne obtenue.
  • Des caractères Base64URL. - et _ indiquent que la chaîne utilise l'alphabet adapté aux URL, comme les JWT. Voir la section plus bas.
  • Des caractères invisibles. Une espace insécable ou une espace de largeur nulle collée depuis une messagerie ou un document n'est pas un blanc ASCII : même atob, qui ignore les espaces ordinaires, la rejette.

Un cas ne produit aucune erreur. Si une valeur Base64 standard est placée dans une query string sans encodage pourcent, chaque + devient une espace lors de l'analyse de la requête. atob et Python ignorent ensuite ces espaces : ++++YWJj arrive sous la forme YWJj et se décode en abc, les trois premiers octets ayant disparu sans bruit. Encodez la valeur avec encodeURIComponent, ou utilisez Base64URL dans les URL.

Longueur et remplissage incorrects

Base64 transforme chaque groupe de 3 octets en 4 caractères : une chaîne avec remplissage a donc toujours une longueur divisible par 4. Sans remplissage, le dernier groupe compte 2 ou 3 caractères. Un reste de 1 est impossible, quelle que soit l'entrée : si length % 4 === 1, un caractère a été perdu ou ajouté, et aucun remplissage n'y changera rien.

Le remplissage ne peut figurer qu'à la fin, et il n'y a jamais plus de deux signes =. La RFC 4648 impose le remplissage, sauf si le format qui utilise Base64 en dispose autrement, et plusieurs formats le font : les JWT et les code challenges PKCE l'omettent. Pour rétablir le remplissage manquant avant de passer la chaîne à un décodeur strict :

// JavaScript
const padded = s + '='.repeat((4 - s.length % 4) % 4);

# Python
padded = s + '=' * (-len(s) % 4)

Du Base64URL passé à un décodeur standard

Base64URL (RFC 4648, section 5) remplace + par - et / par _, et supprime en général le remplissage. Un décodeur standard rejette ces deux caractères, mais seules les chaînes qui en contiennent au moins un échouent : le bug semble donc aléatoire. Une valeur aléatoire de 16 octets donne 22 caractères Base64URL, dont 21 peuvent être - ou _. Chacun évite les deux avec une probabilité de 62/64 : (62/64) puissance 21, soit environ 51 % de ces valeurs, se décodent sans problème, et les 49 % restants échouent. Convertissez l'alphabet et rétablissez le remplissage, ou utilisez un décodeur conçu pour cette variante :

// Navigateur
const b64 = s.replace(/-/g, '+').replace(/_/g, '/');
const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));

// Node.js
Buffer.from(s, 'base64url');

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

// Java
Base64.getUrlDecoder().decode(s);

urlsafe_b64decode de Python exige toujours le remplissage, d'où son ajout dans l'exemple. Les différences entre les deux alphabets sont détaillées dans Base64 vs Base64URL.

Les sauts de ligne des e-mails et des fichiers PEM

Les corps d'e-mails MIME découpent le Base64 à 76 caractères par ligne, et les fichiers PEM, comme les certificats, à 64. atob, .NET et PHP ignorent les sauts de ligne. Le décodeur de base de Java, non : utilisez Base64.getMimeDecoder() pour une entrée découpée en lignes. Pour un PEM, supprimez aussi les lignes -----BEGIN ...----- et -----END ...-----, qui ne sont pas du Base64.

Le décodage a réussi, mais le résultat est faux

  • Le résultat est encore du Base64. Les données ont été encodées deux fois. YUdWc2JHOD0= se décode en aGVsbG8=, qui se décode à son tour en hello. Décodez une seconde fois.
  • Les lettres accentuées ressemblent à é. atob renvoie un caractère par octet : un texte UTF-8 demande donc une seconde étape, new TextDecoder().decode(Uint8Array.from(atob(s), c => c.charCodeAt(0))).
  • Python lève UnicodeDecodeError après le décodage. Les octets sont du Base64 valide mais pas du texte UTF-8, par exemple 'utf-8' codec can't decode byte 0xe9 pour un texte enregistré en Latin-1. Décodez avec le bon encodage, comme .decode('cp1252'), ou traitez le résultat comme des données binaires.
  • Le résultat n'est qu'une suite de symboles illisibles. Les données sont binaires : une image, un PDF ou des données compressées. Enregistrez les octets dans un fichier au lieu de les afficher.

AWS CLI : « Invalid base64 »

La version 2 d'AWS CLI attend par défaut des paramètres binaires encodés en Base64. Passer du JSON brut à une commande comme aws lambda invoke --payload '{"id": 1}' échoue avec Invalid base64. Ajoutez --cli-binary-format raw-in-base64-out pour transmettre le texte tel quel, ou encodez vous-même la charge utile.

Vérifier une chaîne rapidement

Collez la valeur dans le décodeur Base64. Avant de décoder, il supprime les blancs et un éventuel préfixe de data URI, convertit les caractères Base64URL et ajoute le remplissage manquant : s'il indique malgré tout que l'entrée n'est pas du Base64 valide, la chaîne contient réellement un caractère étranger ou a une longueur impossible. Quand le décodage réussit, il affiche directement le texte UTF-8 et propose tout le reste, par exemple une image, sous forme de fichier à télécharger.

Questions Fréquentes

Que signifie « Incorrect padding » en Python ?

La longueur de la chaîne n'est pas un multiple de 4 et le remplissage = manque, ce qui est normal pour du Base64URL et pour les parties d'un JWT. Ajoutez des = jusqu'à obtenir un multiple de 4. Si la longueur laisse un reste de 1, la chaîne est en réalité abîmée.

Pourquoi atob lève-t-il InvalidCharacterError ?

La chaîne contient un caractère hors de l'alphabet Base64, comme le - ou le _ de Base64URL, un guillemet ou un antislash, ou bien elle a un remplissage en trop ou une longueur impossible. Les espaces et les sauts de ligne sont ignorés et n'en sont pas la cause.

Pourquoi la même chaîne se décode-t-elle dans un langage et échoue-t-elle dans un autre ?

Les décodeurs n'ont pas la même rigueur. Python et Buffer de Node.js ignorent par défaut les caractères inconnus, le mode strict de PHP et atob acceptent un remplissage manquant, et .NET rejette à la fois le remplissage manquant et les caractères inconnus.

Comment décoder du Base64URL ?

Remplacez - par + et _ par /, ajoutez des = jusqu'à obtenir un multiple de 4, puis décodez normalement. Node.js l'accepte directement avec Buffer.from(s, 'base64url').