Cadena Base64 no válida: por qué falla la decodificación y cómo solucionarlo

Un decodificador Base64 rechaza una cadena por uno de tres motivos: un carácter fuera del alfabeto, una longitud que no puede ser correcta o un relleno en el lugar equivocado.

Base64 usa 64 caracteres, de A a Z, de a a z, de 0 a 9, + y /, más = como relleno al final (RFC 4648, sección 4). Cualquier otra cosa hace fallar a un decodificador estricto. Lo confuso es que los decodificadores no se ponen de acuerdo sobre qué incluye «cualquier otra cosa»: una cadena Base64URL como PDw_Pz8-Pg== se decodifica bien con Buffer de Node.js, falla en .NET y en Python se convierte en silencio en bytes equivocados. Saber qué tolera su decodificador es la mayor parte de la solución.

El error en cada lenguaje

DóndeQué obtiene
Chrome y Edge, atob()InvalidCharacterError: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
Node.js, atob()InvalidCharacterError: Invalid character, o The string to be decoded is not correctly encoded. si la longitud es incorrecta
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 o, con 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 seguido del código del carácter en hexadecimal, como 2d para -
PHP, base64_decode($s, true)Ningún mensaje: la función devuelve false
AWS CLI versión 2Invalid base64: seguido del valor que pasó

La excepción es Buffer.from(s, 'base64') de Node.js: nunca lanza errores. Se salta los caracteres que no reconoce, de modo que una cadena dañada produce bytes dañados sin ningún aviso.

Qué tolera cada decodificador

EntradaatobPython b64decode.NETPHP estricto
Falta el relleno: SGVsbG8DecodificaIncorrect paddingFallaDecodifica
Relleno de más: SGVsbG8==FallaDecodificaFallaDevuelve false
Espacios o saltos de líneaSe ignoranSe ignoran (falla con validate=True)Se ignoranSe ignoran
- y _ de Base64URLFallaSe descartan en silencio: bytes erróneos o un errorFallaDevuelve false
Otros caracteres: comillas, \, !, :FallaSe descartan en silencioFallaDevuelve false

El modo por defecto de Python merece una advertencia. Descarta todos los caracteres ajenos al alfabeto antes de decodificar, así que las letras de un prefijo como data:image/jpeg;base64, se decodifican como si fueran datos. Según la longitud del prefijo, obtendrá un error confuso sobre el número de caracteres, o ningún error y varios bytes de basura delante del contenido real. Pase validate=True cuando quiera que una entrada incorrecta falle de forma clara.

Caracteres que no deberían estar ahí

Cuando un decodificador estricto informa de un carácter no válido, casi siempre la cadena recogió algo por el camino hasta usted:

  • Comillas o texto alrededor. Copiar "SGVsbG8=" con sus comillas, o una línea entera "key": "value", añade caracteres que no son Base64.
  • Un prefijo de data URI. En data:image/png;base64,iVBOR... solo es Base64 lo que va después de la coma. Elimine todo hasta la coma, incluida.
  • Escapes de JSON. json_encode de PHP escribe / como \/, y un contenido partido en líneas, como un certificado PEM, se guarda con \n entre línea y línea. Si copia el texto JSON en bruto en lugar del valor ya analizado, las barras invertidas vienen con él. Analice primero el JSON y decodifique la cadena resultante.
  • Caracteres de Base64URL. - y _ indican que la cadena usa el alfabeto seguro para URL, como hacen los JWT. Consulte la sección correspondiente más abajo.
  • Caracteres invisibles. Un espacio de no separación o un espacio de ancho cero pegados desde una aplicación de chat o un documento no son espacios en blanco ASCII, así que incluso atob, que ignora los espacios normales, los rechaza.

Hay un caso que no produce ningún error. Si un valor Base64 estándar se coloca en una query string sin codificación porcentual, cada + se convierte en un espacio cuando se analiza la query. Después, atob y Python se saltan esos espacios: ++++YWJj llega como YWJj y se decodifica como abc, y los tres primeros bytes desaparecen sin dejar rastro. Codifique el valor con encodeURIComponent, o use Base64URL en las URL.

Longitud y relleno incorrectos

Base64 convierte cada 3 bytes en 4 caracteres, así que una cadena con relleno siempre tiene una longitud divisible entre 4. Sin relleno, el último grupo tiene 2 o 3 caracteres. Un resto de 1 es imposible para cualquier entrada: si length % 4 === 1, se perdió un carácter o se añadió uno de más, y ninguna cantidad de relleno lo arreglará.

El relleno solo puede aparecer al final, y nunca hay más de dos signos =. El RFC 4648 exige relleno salvo que el formato que usa Base64 diga otra cosa, y varios formatos lo hacen: los JWT y los code challenges de PKCE lo omiten. Para restaurar el relleno que falta antes de pasar la cadena a un decodificador estricto:

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

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

Base64URL en un decodificador estándar

Base64URL (RFC 4648, sección 5) sustituye + por - y / por _, y normalmente elimina el relleno. Un decodificador estándar rechaza los dos caracteres, pero solo fallan las cadenas que por casualidad contienen alguno de ellos, así que el error parece aleatorio. Un valor aleatorio de 16 bytes se convierte en 22 caracteres Base64URL, 21 de los cuales pueden ser - o _. Cada uno evita ambos con una probabilidad de 62/64, de modo que (62/64) elevado a 21, alrededor del 51%, de esos valores se decodifican bien y el 49% restante falla. Convierta el alfabeto y restaure el relleno, o use un decodificador pensado para esta variante:

// Browser
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);

Aun así, urlsafe_b64decode de Python necesita el relleno, y por eso el ejemplo lo añade. Las diferencias entre los dos alfabetos se explican en Base64 vs Base64URL.

Saltos de línea de correos y archivos PEM

Los cuerpos de correo MIME parten el Base64 en líneas de 76 caracteres, y los archivos PEM, como los certificados, en líneas de 64. atob, .NET y PHP se saltan los saltos de línea. El decodificador básico de Java no lo hace: use Base64.getMimeDecoder() para la entrada partida en líneas. En los PEM, elimine también las líneas -----BEGIN ...----- y -----END ...-----, que no son Base64.

Se decodificó, pero el resultado es incorrecto

  • La salida es más Base64. Los datos se codificaron dos veces. YUdWc2JHOD0= se decodifica como aGVsbG8=, que a su vez se decodifica como hello. Decodifique otra vez.
  • Las letras acentuadas aparecen como é. atob devuelve un carácter por byte, así que el texto UTF-8 necesita un segundo paso: new TextDecoder().decode(Uint8Array.from(atob(s), c => c.charCodeAt(0))).
  • Python lanza UnicodeDecodeError después de decodificar. Los bytes son Base64 válido pero no texto UTF-8, por ejemplo 'utf-8' codec can't decode byte 0xe9 con un texto guardado en Latin-1. Decodifique con la codificación correcta, como .decode('cp1252'), o trate el resultado como datos binarios.
  • La salida son símbolos ilegibles. Los datos son binarios: una imagen, un PDF o datos comprimidos. Guarde los bytes en un archivo en lugar de mostrarlos como texto.

AWS CLI: «Invalid base64»

La versión 2 de AWS CLI espera por defecto que los parámetros binarios vayan en Base64. Pasar JSON plano a un comando como aws lambda invoke --payload '{"id": 1}' falla con Invalid base64. Añada --cli-binary-format raw-in-base64-out para pasar el texto tal cual, o codifique usted mismo el payload.

Comprobar una cadena rápidamente

Pegue el valor en Decodificar Base64. La herramienta elimina los espacios en blanco y el prefijo de data URI, convierte los caracteres de Base64URL y añade el relleno que falte antes de decodificar; si aun así indica que la entrada no es Base64 válido, la cadena contiene de verdad un carácter ajeno o tiene una longitud imposible. Cuando la decodificación funciona, muestra directamente el texto UTF-8 y ofrece todo lo demás, como una imagen, en forma de archivo para descargar.

Preguntas Frecuentes

¿Qué significa «Incorrect padding» en Python?

La longitud de la cadena no es múltiplo de 4 y falta el relleno =, algo normal en Base64URL y en las partes de un JWT. Añada = hasta que la longitud sea múltiplo de 4. Si la longitud deja un resto de 1, entonces la cadena está dañada.

¿Por qué atob lanza InvalidCharacterError?

La cadena contiene un carácter fuera del alfabeto Base64, como - o _ de Base64URL, una comilla o una barra invertida, o bien tiene relleno de más o una longitud imposible. Los espacios y los saltos de línea se ignoran y no son la causa.

¿Por qué la misma cadena se decodifica en un lenguaje y falla en otro?

Los decodificadores difieren en su rigor. Python y Buffer de Node.js se saltan por defecto los caracteres desconocidos, el modo estricto de PHP y atob aceptan que falte el relleno, y .NET rechaza tanto la falta de relleno como los caracteres desconocidos.

¿Cómo se decodifica Base64URL?

Sustituya - por + y _ por /, añada relleno = hasta llegar a un múltiplo de 4 y decodifique de la forma habitual. Node.js lo acepta directamente con Buffer.from(s, 'base64url').