Subir un archivo
  1. base64hub
  2. Base64 en código
  3. PHP
Código

Base64 en PHP

PHP tiene dos funciones, base64_encode y base64_decode, más un filtro de stream para archivos grandes. Todas las salidas de esta página son de PHP 8.4.

Codificar y decodificar una cadena

Las cadenas de PHP son cadenas de bytes, así que base64_encode codifica los bytes que contenga la cadena. Un archivo fuente guardado en UTF-8 te da Base64 de UTF-8 sin ningún paso de conversión:

<?php
$enc = base64_encode("héllo wörld");
echo $enc;                  // aMOpbGxvIHfDtnJsZA==
echo base64_decode($enc);   // héllo wörld

Si el texto llega de una base de datos o de un formulario en otro juego de caracteres, conviértelo antes con mb_convert_encoding($s, 'UTF-8', 'Windows-1252'). Si no, el Base64 será correcto para esos bytes, pero en otro sistema se decodificará como mojibake. La herramienta Base64 a texto te deja probar distintos juegos de caracteres con el resultado.

Modo estricto: base64_decode($s, true)

Sin el segundo argumento, base64_decode descarta cualquier carácter que no esté en el alfabeto y decodifica el resto. Con true, devuelve false:

var_dump(base64_decode("aGk=!!"));        // string(2) "hi"
var_dump(base64_decode("aGk=!!", true));  // bool(false)

Compara siempre con ===, porque una entrada vacía válida se decodifica como "", que se evalúa como falso:

$bytes = base64_decode($input, true);
if ($bytes === false) {
    throw new InvalidArgumentException('Invalid Base64');
}

El modo estricto es menos estricto de lo que sugiere su nombre. Esto se probó en PHP 8.4:

EntradaResultado en modo estricto
aGk (sin relleno)"hi"
"aGk=\n" (salto de línea final)"hi"
aGl= (bits de relleno distintos de cero)"hi"
aGk== (relleno de más)false
a (longitud imposible)false
PDw_Pz8-Pg== (caracteres URL-safe)false

Si solo quieres Base64 canónico, quita los espacios en blanco, decodifica en modo estricto y comprueba después que base64_encode($bytes) === $input. El validador Base64 hace las mismas comprobaciones y señala el carácter incorrecto.

Base64 URL-safe con y sin relleno

PHP no tiene ninguna función integrada para Base64URL. Cambia los dos caracteres con strtr, que es más rápido que str_replace para correspondencias de un solo carácter:

function b64url_encode(string $data, bool $pad = false): string {
    $s = strtr(base64_encode($data), '+/', '-_');
    return $pad ? $s : rtrim($s, '=');
}

function b64url_decode(string $s): string|false {
    return base64_decode(strtr($s, '-_', '+/'), true);
}

echo b64url_encode("<<???>>");        // PDw_Pz8-Pg
echo b64url_encode("<<???>>", true);  // PDw_Pz8-Pg==
echo b64url_decode("PDw_Pz8-Pg");     // <<???>>

Como el modo estricto tolera la falta de relleno, no hace falta volver a añadir = antes de decodificar. Más sobre las diferencias en Base64 frente a Base64URL.

Codificar y decodificar un archivo

echo base64_encode(file_get_contents("hello.txt"));   // aGVsbG8K

file_put_contents("copy.txt", base64_decode("aGVsbG8K", true));

Para un data URI, antepón el tipo MIME: 'data:image/png;base64,' . base64_encode($png). mime_content_type($path) puede detectar el tipo si fileinfo está activado.

Archivos grandes con filtros de stream

file_get_contents carga el archivo entero y la copia en Base64, así que el uso de memoria es de unas 2,3 veces el tamaño del archivo. El filtro de stream convert.base64-encode codifica mientras copia:

$in  = fopen("big.bin", "rb");
$out = fopen("big.b64", "wb");
stream_filter_append($out, "convert.base64-encode");
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

La decodificación funciona igual, o en una línea con una URL php://filter:

copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");

Con un archivo de prueba de 300 KB, las dos formas produjeron una salida idéntica a la de las funciones en memoria. Un bucle manual también sirve si cada bloque es múltiplo de 3 bytes, por ejemplo fread($in, 3 * 8192). Cualquier otro tamaño inserta = en mitad de la salida.

Cortes de línea MIME

Los cuerpos de correo necesitan líneas de 76 caracteres como máximo. chunk_split hace exactamente eso, con finales de línea \r\n por defecto:

echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4

También añade un \r\n final. Las bibliotecas de correo lo hacen por ti; el codificador Base64 MIME muestra el formato.

Errores habituales

  • Olvidar el modo estricto con entradas que no son de confianza. Los datos incorrectos se decodifican como bytes aleatorios y fallan en algún punto muy alejado de la causa.
  • Entradas URL-safe en el modo por defecto. base64_decode("PDw_Pz8-Pg==") devuelve string(6) "<<���", no <<???>>. Traduce antes con strtr.
  • Pasar null. En PHP 8.1+ obtienes Deprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated. Pasar un array lanza TypeError: base64_encode(): Argument #1 ($string) must be of type string, array given.
  • El + que se convierte en espacio. El Base64 estándar dentro de una query string se estropea al decodificar la URL, porque ahí + significa espacio. Usa la variante URL-safe o rawurlencode.
  • Usarlo para guardar contraseñas. Base64 no oculta nada. Usa password_hash para las contraseñas y consulta codificación frente a cifrado.

Notas de versión

  • base64_encode, base64_decode y el parámetro $strict existen en todas las versiones de PHP con soporte (el modo estricto se añadió en la 5.2).
  • Desde PHP 8.0, base64_decode declara que devuelve string|false, y pasarle un array o un objeto lanza TypeError en lugar de un aviso.
  • El filtro convert.base64-encode acepta las opciones line-length y line-break-chars para salida MIME, por ejemplo stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).

Preguntas frecuentes

¿Para qué sirve el segundo parámetro de base64_decode?

base64_decode($s, true) activa el modo estricto: la función devuelve false cuando la entrada contiene caracteres fuera del alfabeto Base64. Sin él, esos caracteres se descartan sin avisar.

¿PHP tiene una función base64url_encode?

No. Usa rtrim(strtr(base64_encode($data), '+/', '-_'), '=') para codificar y base64_decode(strtr($s, '-_', '+/'), true) para decodificar. El modo estricto acepta que falte el relleno.

¿El modo estricto de base64_decode es un validador completo?

No. Sigue aceptando espacios en blanco, la falta de relleno y bits de relleno distintos de cero, como en aGl=. Para una validación exacta, vuelve a codificar el resultado y compáralo con la entrada normalizada.

¿Por qué base64_decode devuelve basura en lugar de false?

Probablemente la llamaste sin true. En el modo por defecto, base64_decode('PDw_Pz8-Pg==') descarta _ y - y devuelve seis bytes incorrectos. Traduce antes la entrada URL-safe con strtr.

Sigue leyendo