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 2 | Invalid 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ée | atob | Python b64decode | .NET | PHP strict |
|---|---|---|---|---|
Remplissage manquant : SGVsbG8 | Décode | Incorrect padding | Échoue | Décode |
Remplissage en trop : SGVsbG8== | Échoue | Décode | Échoue | Renvoie false |
| Espaces ou sauts de ligne | Ignorés | Ignorés (échec avec validate=True) | Ignorés | Ignorés |
- et _ de Base64URL | Échoue | Supprimés sans bruit : octets erronés ou erreur | Échoue | Renvoie false |
Autres caractères : guillemets, \, !, : | Échoue | Supprimés sans bruit | Échoue | Renvoie 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_encodede PHP écrit/sous la forme\/, et un contenu découpé en lignes, comme un certificat PEM, est stocké avec des\nentre 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 enaGVsbG8=, qui se décode à son tour enhello. Décodez une seconde fois. - Les lettres accentuées ressemblent à
é.atobrenvoie 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
UnicodeDecodeErroraprè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 0xe9pour 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.