¿Tiene que ocuparse del formato Base64? Entonces esta página es perfecta para Ud. Utilice nuestra práctica herramienta en línea para codificar o decodificar sus datos.

Codificación Base64 en PHP: una guía completa

Tienes datos que deben sobrevivir a un canal que no los quiere. Un blob binario que tiene que vivir en un campo JSON. Una imagen que debe vivir dentro de una etiqueta HTML. Un certificado que pertenece a un archivo de configuración. Un token que viajará por URLs, cabeceras y cookies. Esta es la vida cotidiana de la codificación Base64: reescribe cada tres bytes de datos crudos como cuatro caracteres de un alfabeto de 64 letras, con uno o dos signos = rematando la cola, de modo que el resultado es texto plano que cualquiera puede transportar. La página principal de este sitio explica el formato por completo, así que este artículo se centra en lo que PHP te da, lo que decide en silencio por ti, y dónde están las trampas.

Por el lado de PHP, la historia empieza con buena noticia: base64_encode() lleva viviendo en el núcleo desde PHP 4, toma un argumento, siempre devuelve una cadena, y no puede fallar. No hay modo estricto, no hay camino de error, no hay configuración. La codificación es determinista: los mismos bytes producen siempre las mismas letras. Tu trabajo como desarrollador no es hacer que la función funcione, es hacer que el mundo de alrededor se porte bien: elegir el alfabeto correcto para el destino, añadir los saltos de línea correctos, convertir el charset correcto, y asumir la factura del 33 por ciento de tamaño con los ojos bien abiertos. (Base64 normalmente expande los datos en torno a un tercio, cuatro caracteres por cada tres bytes de entrada; ten eso en un rincón de la cabeza, porque vuelve una y otra vez.)

Para el final de este artículo sabrás producir todos los sabores de Base64 con los que un desarrollador PHP se encuentra de verdad: salida de una sola línea, correo envuelto en MIME, claves envueltas en PEM, tokens URL-safe y data URIs, más los trucos de streaming para cuando los datos son demasiado grandes para caber en memoria.

Una función, cero opciones

Toda la API, tal y como la reporta el PHP moderno:

base64_encode(string $string): string

Léelo otra vez. Un parámetro, un valor de retorno, sin flags. El manual lo describe como base64 MIME, "diseñado para hacer que los datos binarios sobrevivan al transporte a través de capas de transporte que no son limpias de 8 bits, como los cuerpos de correo". Fíjate en lo que esa redacción no promete: sin saltos de línea, sin envolvido, sin opinión sobre dónde vivirá la salida. La función emite una línea larga, y sea cual sea el envolvido que quiera el destino, eso es tarea tuya con una segunda llamada. Desde PHP 8.0, la firma lleva tipos nativos; desde PHP 8.1, pasar null lanza un aviso de deprecación, así que coalesce cualquier valor nulo a '' antes.

El tamaño de la salida sigue un patrón fijo que puedes predecir antes de llamar:

Bytes de entrada Caracteres de salida Relleno
0 0 ninguno
1 4 dos =
2 4 un =
3 4 ninguno
3,000,000 4,000,000 ninguno
100,000 133,336 dos =

El patrón son cuatro caracteres por cada grupo completo de tres bytes, más un grupo final parcial rellenado con uno o dos signos =. Una consecuencia que conviene conocer: un byte y tres bytes producen ambos cuatro caracteres, así que la longitud codificada oculta el tamaño exacto de la entrada. Puedes estimarlo (divide entre cuatro, multiplica por tres, resta los rellenos), pero no puedes leerlo con exactitud.

Dónde van los saltos de línea

Como base64_encode() nunca dobla por su cuenta, la decisión del envolvido es un problema del destino. En la práctica hay tres respuestas.

Sin saltos de línea. La salida cruda de la función, exactamente una línea. Esto es lo que quieres para URLs, payloads JSON, cabeceras, valores de base de datos y cualquier otra cosa donde un salto de línea sería un bug. También es lo que la mayoría de la gente quiere decir con "dame solo el Base64".

Envolvido MIME: 76 caracteres más CRLF. La convención de correo del RFC 2045, sección 6.8: las líneas codificadas no deben superar los 76 caracteres, y los decodificadores deben ignorar los saltos de línea. La llamada acompañante clásica es chunk_split(), que el manual empareja con base64_encode() en su lista "Ver también" por exactamente esta razón:

$binary = file_get_contents('/var/www/uploads/report.pdf');
$wrapped = chunk_split(base64_encode($binary), 76, "\r\n");

Envolvido PEM: 64 caracteres más LF. Las claves y los certificados usan la convención más antigua de Privacy-Enhanced Mail (RFC 1421): líneas más cortas de 64 caracteres. Misma herramienta, números diferentes:

$der = file_get_contents('/etc/ssl/raw-key.der');
$armor = "-----BEGIN PRIVATE KEY-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END PRIVATE KEY-----\n";

Una piedra en el camino de chunk_split() se aplica a los dos sabores con envolvido: la función añade el separador al final del resultado incluso cuando la longitud de la entrada es un múltiplo exacto de la longitud de línea. Si un consumidor de abajo se tropieza con una línea vacía al final, esta es la razón; una llamada a rtrim() sobre el separador lo arregla. Aprovecha también la asimetría que algún día te salvará: los decodificadores ignoran los saltos de línea por completo, así que un payload envuelto en MIME y uno sin envolver se decodifican a los mismos bytes. El envolvido es una cortesía para herramientas basadas en líneas y personas, no una diferencia semántica.

Hacer la salida URL-safe

El alfabeto estándar incluye + y /, y ambos son un problema fuera de un archivo de texto. Un + en una query string form-encoded se convierte en espacio antes de que tu aplicación lo vea, y / es un separador de rutas en las URLs. Los nombres de archivo y los tokens tienen sus quejas propias. El RFC 4648, sección 5, lo resuelve con el alfabeto seguro para URLs y nombres de archivo: + se convierte en -, / se convierte en _, y el relleno = final suele descartarse. El RFC insiste en que esto "no debería considerarse igual que la codificación base64", así que trátalo como un formato distinto, comúnmente llamado base64url.

Producirlo son dos operaciones de cadena:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
var_dump(base64url_encode("hi>?there\x00bin")); // string(18) "aGk-P3RoZXJlAGJpbg"

Cuándo usarlo: partes de JSON Web Token, parámetros state y nonce de OAuth, IDs de API que metes en rutas de URL, y cualquier cosa que se copie en una barra de direcciones o en un nombre de archivo. Cuándo no usarlo: cuerpos de correo, armadura PEM, y cualquier sitio donde al otro lado haya un consumidor de alfabeto estándar, porque - y _ no están en su vocabulario. Y no mezcles los dos alfabetos en silencio: un token codificado URL-safe debe decodificarse URL-safe, en todas partes, para siempre. Esa es la regla entera de interoperabilidad de base64url.

Unicode y los bytes que quisiste

Las cadenas de PHP son secuencias de bytes, y base64_encode() codificará los bytes que le des sin preguntar qué significan. Eso es una ventaja hasta el día en que tu "texto" no sea en realidad la codificación que crees que es. El fallo clásico: una cadena que parece UTF-8 en tu editor pero llegó de una fuente heredada como Windows-1252. Codifica esos bytes tal cual y el receptor, que decodificará y asumirá UTF-8, recibe mojibake en vez de tus letras acentuadas.

La solución es normalizar antes de codificar, con la extensión mbstring (viene con el código fuente de PHP pero no está activada por defecto):

$fromLegacy = "caf\xE9 au lait"; // bytes Windows-1252: la e con acento es 0xE9
$utf8 = mb_convert_encoding($fromLegacy, 'UTF-8', 'Windows-1252');
$encoded = base64_encode($utf8);
var_dump($utf8); // string(13) "café au lait": la e con acento es ahora dos bytes UTF-8

Si la fuente ya es UTF-8, puedes saltarte la conversión, y una comprobación barata de sentido común es mb_check_encoding($utf8, 'UTF-8'). Una frase de consejo: nunca intentes "arreglar" una cadena Base64 ya codificada volviéndola a codificar como texto. Esa es la trampa de la doble codificación de la sección de trampas de abajo, y es el bug Base64 más común de todos en los codebases PHP.

Archivos, blobs y la convención .b64

El trabajo de codificación más directo: un archivo se convierte en texto. Las cadenas de PHP son bytes, así que no hay "modo binario" del que preocuparse; file_get_contents() te entrega los bytes exactos y base64_encode() te entrega el texto exacto:

$binary = file_get_contents('/var/www/uploads/photo.png');
$encoded = base64_encode($binary);
file_put_contents('/var/www/uploads/photo.png.b64', $encoded);
var_dump(strlen($binary), strlen($encoded)); // la factura del 33%, cada vez

Dos hábitos mantienen esto a salvo. Primero, saber qué estás codificando. La clase finfo (extensión fileinfo, incluida en las compilaciones estándar de PHP) te dice el tipo real a partir de los bytes, no del nombre del archivo:

$mime = (new finfo(FILEINFO_MIME_TYPE))->file('/var/www/uploads/photo.png');
var_dump($mime); // string(9) "image/png"

Segundo, recuerda la factura del tamaño cuando planifiques el almacenamiento: una imagen de 500 KB se convierte en un archivo de texto de 670 KB, y un vídeo de 1 GB en un archivo de texto de 1.33 GB. Por eso existe la sección de datos grandes de abajo.

Data URIs: meter una imagen en la página

Un data URI incrusta el payload directamente en la URL, así que no hace falta una segunda petición para obtenerlo. El RFC 2397 define la forma: data:, un tipo de medio opcional, un flag opcional ;base64, una coma, y los datos. Para medios binarios como imágenes el flag está presente, así que el payload es exactamente lo que base64_encode() produjo:

function data_uri_for(string $path): string
{
  $mime = (new finfo(FILEINFO_MIME_TYPE))->file($path);
  $payload = base64_encode(file_get_contents($path));
  return 'data:' . $mime . ';base64,' . $payload;
}
$uri = data_uri_for('/var/www/uploads/avatar.png');
echo '<img src="' . $uri . '" alt="avatar" />' . "\n";

¿Por qué Base64 aquí? Porque una URI no puede contener con seguridad bytes crudos ni comas, y el alfabeto Base64 no necesita ningún escapado. Aunque las contrapartidas son reales. El payload codificado es unos 33 por ciento más grande que el archivo, lo que hace que el propio documento HTML crezca. Los navegadores no cachean un data URI como cachean una URL de archivo, así que cada vista de la página vuelve a descargar los bytes. Y el propio RFC dice que los data URIs solo son útiles para valores cortos; los viejos analizadores HTML tenían límites duros en la longitud de atributo, y los navegadores modernos, aunque mucho más generosos, tampoco disfrutan los megabytes dentro de una etiqueta. Úsalos para avatares, iconos y gráficos inline pequeños; usa archivos de verdad para todo lo demás.

JWTs y tokens de API

Los JSON Web Tokens son el consumidor estrella de Base64 en las APIs modernas, y usan el dialecto URL-safe sin relleno de la sección de arriba. Según el RFC 7519, un JWT compacto son tres partes base64url separadas por puntos: cabecera, payload, firma. La cabecera y el payload son JSON plano; la firma es bytes crudos. Construir uno a mano es una manera agradable de ver cada pieza en movimiento:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = base64url_encode(json_encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'iat' => 1516239022,
]));
$signingInput = $header . '.' . $payload;
$signature = base64url_encode(hash_hmac('sha256', $signingInput, $secret, true));
$token = $signingInput . '.' . $signature;

Fíjate en dos cosas. La firma es la codificación base64url de bytes HMAC crudos, que es por qué hash_hmac() se llama con true para salida cruda. Y la cabecera y el payload son legibles por cualquiera, que es por diseño: un JWT es un ticket firmado, no un secreto. En producción, no escribas la firma ni la verificación a mano. El paquete de la comunidad es firebase/php-jwt (v7, requiere PHP 8.0 o más nuevo), instalado con Composer:

composer require firebase/php-jwt
use Firebase\JWT\JWT;
$secret = 'correct-horse-battery-staple-long-enough-secret';
$token = JWT::encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'exp' => time() + 3600,
], $secret, 'HS256');
var_dump(substr_count($token, '.')); // int(2): cabecera, payload, firma

Nota de versión para la línea v7 de la librería: los algoritmos HMAC imponen una longitud mínima de clave, así que un secreto HS256 de menos de 32 bytes se rechaza antes de que ocurra cualquier codificación. Los secretos largos son la norma de todas formas; esto simplemente hace que la librería se niegue a tomárselo a la ligera.

La librería se encarga de la conversión base64url, la firma y las comprobaciones de caducidad por ti, y lanza excepciones tipadas en vez de devolver datos a medio confiar. Cuando escribes tokens con ella, nunca tocas base64_encode() directamente, que es exactamente como debería ser.

HTTP: Basic Auth y el handshake de WebSocket

Dos trabajos de construcción de cabeceras donde PHP hace el Base64 y el protocolo hace el resto.

HTTP Basic auth (RFC 7617): el cliente envía Authorization: Basic más el Base64 de username:password. Construirlo es una concatenación de cadenas:

function basic_authorization_header(string $username, string $password): string
{
  return 'Basic ' . base64_encode($username . ':' . $password);
}
$headers[] = 'Authorization: ' . basic_authorization_header('alice', 'secret123');

Dilo una vez en voz alta, porque el RFC te obliga: esto es codificación, no protección. Cualquiera con una captura de paquetes recupera ambas mitades en una pulsación, así que el Basic auth solo pertenece a conexiones HTTPS.

El handshake de WebSocket (RFC 6455): el servidor demuestra que ha oído al cliente devolviendo una clave transformada. Concatena el Sec-WebSocket-Key del cliente con un GUID mágico fijo, calcula el SHA-1 del resultado y codifica en base64 el resumen. Esto es Base64 estándar, relleno incluido, porque vive en una cabecera, no en una URL:

function websocket_accept_key(string $clientKey): string
{
  return base64_encode(hash('sha1', $clientKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
}
$accept = websocket_accept_key('dGhlIHNhbXBsZSBub25jZQ==');
var_dump($accept); // string(28) "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

Ese ejemplo es el del propio RFC, lo que lo convierte en una autoprueba práctica: si tu implementación produce los mismos 28 caracteres, la capa WebSocket está hablando correctamente.

Correo: el caso de uso original

Todo lo demás de este artículo es un descendiente de un hecho: el SMTP se diseñó para transportar ASCII de 7 bits, y la gente quería enviar binarios. La respuesta del estándar MIME, en la sección 6.8 del RFC 2045, fue Base64 como Content-Transfer-Encoding, con las dos normas de la casa que ya conoces: líneas de como mucho 76 caracteres, y decodificadores que ignoran todo carácter fuera del alfabeto. Así viaja un adjunto PDF:

$pdf = file_get_contents('/var/www/uploads/report.pdf');
$attachment = chunk_split(base64_encode($pdf), 76, "\r\n");
// la librería de correo ahora mete $attachment en la parte MIME,
// con Content-Transfer-Encoding: base64

Los números prácticos: la codificación en sí cuesta 33 por ciento, y el CRLF cada 76 caracteres cuesta un poco más, así que un adjunto de 100 KB se envía como unos 137 KB de texto. Cuando escribes correo desde PHP, las librerías (PHPMailer y sus parientes estables) hacen el envolvido por ti, y tú les entregas el binario crudo. Si algún día ves un muro de letras de 76 caracteres en un archivo .eml crudo, ahora sabes el algoritmo exacto que lo produjo.

Armadura PEM para claves y certificados

Las claves y los certificados necesitan más que un muro de letras; necesitan etiquetas. La armadura PEM es una línea BEGIN, un bloque Base64 envuelto a 64 caracteres, y una línea END, una convención heredada de Privacy-Enhanced Mail (RFC 1421) y mantenida viva por OpenSSL. La extensión openssl de PHP produce y consume esta forma directamente:

$res = openssl_pkey_new([
  'private_key_bits' => 2048,
  'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($res, $pem);
// $pem ya está blindado: etiqueta BEGIN, líneas de 64 caracteres, etiqueta END

El caso interesante es cuando la armadura hay que reconstruirla a mano, por ejemplo cuando recibes bytes DER crudos de una API y necesitas un archivo PEM para una herramienta que solo lee PEM. La convención son 64 caracteres por línea, saltos de línea LF, y una etiqueta que nombra el contenido:

$rearmored = "-----BEGIN CERTIFICATE-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END CERTIFICATE-----\n";
var_dump(openssl_x509_parse($rearmored) !== false); // bool(true): la armadura es válida

Te equivocas con la etiqueta y el archivo es basura, no importa lo perfecto que sea el Base64. Y te equivocas con la longitud de línea y la mayoría de herramientas lo leerán igual, porque los decodificadores ignoran los saltos de línea, pero las herramientas de diff y las personas sufrirán. Sesenta y cuatro es el número.

Archivos de configuración, variables de entorno y bases de datos

Base64 es un contenedor de texto, lo que lo convierte en una herramienta de contrabando para valores que romperían su contenedor. Un DSN de base de datos lleno de punto y comas y comillas, un JWT en un archivo .env, un blob binario en una columna TEXT: todos se convierten en una sola cadena larga y segura.

El sabor de variable de entorno es un ritual de dos pasos. Una vez, en la máquina que construye la configuración, codificas:

echo 'DB_DSN_B64=' . base64_encode('pg:host=db;password=qu"ote') . PHP_EOL;

Luego, en cada arranque de la aplicación, decodificas y validas al inicio, para que una configuración a medias falle ruidosamente en vez de cripticamente:

$dsn = base64_decode(getenv('DB_DSN_B64') ?: '', true);
if ($dsn === false) {
  exit('DB_DSN_B64 is not valid Base64.');
}

Para las bases de datos, la misma idea almacena binarios en columnas de texto. Se aplica la factura del tamaño: el valor almacenado es unos 33 por ciento más grande que el blob, así que un archivo de 1 MB ocupa unos 1.33 MB en la columna, y deberías elegir el tipo de columna teniendo eso en cuenta. Y la misma advertencia que en todas partes: esto es seguridad de formato, no secreto. Cualquiera que pueda leer la configuración o consultar la columna puede invertirlo en una llamada. Si el valor es sensible, cifralo; Base64 solo lo hace portable.

Datos grandes y memoria estable

Codificar es la dirección que te cuesta: la salida es un tercio más grande que la entrada, así que un binario de 2 GB quiere 2.66 GB de cadena codificada en memoria. En un proceso web de larga vida o un host con memoria limitada, esa es una razón para hacer streaming en vez de tragártelo todo, y PHP te da dos caminos.

El primer camino es el filtro de stream convert.base64-encode, el gemelo de streaming de la función. Admite parámetros como un array asociativo: line-length para la anchura del doblado y line-break-chars para el separador, lo que reproduce el efecto de chunk_split() sin sostener la cadena entera:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, [
  'line-length' => 64,
  'line-break-chars' => "\n",
]);
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

El segundo camino es el clásico truco de 57 bytes, y es un pequeño pedazo de saber PHP. Una línea MIME de 76 caracteres guarda exactamente 57 bytes de datos originales, así que si lees el archivo de entrada en trozos de un múltiplo de 57 bytes, cada trozo se codifica de forma independiente, sin bits sobrantes que llevar entre trozos. Leer en trozos de 8151 bytes (57 por 143: 143 líneas completas de 76 caracteres de salida, cerca del buffer de E/S tradicional de 8192 bytes de PHP) mantiene la memoria plana mientras el archivo se emite por streaming de forma MIME-impecable:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
while (!feof($in)) {
  $plain = fread($in, 57 * 143);
  $encoded = chunk_split(base64_encode($plain), 76, "\r\n");
  fwrite($out, $encoded);
}
fclose($in);
fclose($out);

¿Cuál eliges? El filtro cuando quieres que PHP sea dueño de la fontanería y no te importan los límites exactos de los trozos; el bucle de 57 bytes cuando quieres una salida MIME determinista, ganchos de progreso o un tope duro en el tamaño de buffer. En cualquier caso, la huella de memoria se queda en un trozo, no en un archivo.

Trampas con acento PHP

Las trampas que aparecen en codebases PHP reales, reunidas en un solo sitio:

  • Doble codificación. El clásico: un valor que ya es Base64 (de una variable de entorno, una base de datos, un script anterior) pasa otra vez por base64_encode() porque nadie comprobó. El resultado se decodifica una vez y devuelve... más Base64. La cura es una comprobación de ida y vuelta en el límite, o una única función bien conocida que sea dueña de toda la codificación del codebase.
  • El + en URLs. La salida estándar contiene + y /. En una query string form-encoded el más se convierte en espacio antes de que tu código lo vea; en una ruta es un separador. Para lo que vaya ligado a URLs, emite base64url o aplica percent-encoding a todo el valor con rawurlencode().
  • El separador final. chunk_split() termina su resultado con el separador incluso en múltiplos exactos de la longitud de línea. Una línea vacía al final suele ser inofensiva (los decodificadores la ignoran), pero hace tropezar a contadores de líneas ingenuos y herramientas de diff. rtrim() del separador si el consumidor es quisquilloso.
  • Desajuste de envolvido. Escribir con líneas MIME de 76 caracteres y tener un consumidor que espera líneas PEM de 64 caracteres (o al revés) no es un problema de decodificación, porque los decodificadores ignoran los saltos, pero es un problema de herramientas de líneas y de lectura humana. Elige la convención que tu destino espera y cúmplela.
  • El salto de línea final es datos. base64_encode() codifica cada byte, incluido el salto de línea al final de un archivo de texto. Cuando dos sistemas producen Base64 "diferentes" para el mismo texto con la misma pinta, un \n final es el sospechoso habitual.
  • Base64 no es cifrado. Codificar una contraseña antes de que llegue a la base de datos no la protege; la formatea. La columna "cifrada" está a una llamada de función de texto plano para cualquiera con acceso a consultas. Cifra o hashea los secretos de verdad; Base64 es un disfraz de transporte.
  • La memoria crece un tercio. En una compilación PHP de 32 bits o un host con límites de memoria justos, codificar un binario grande puede fallar rotundamente. Hazlo por streaming, como se muestra arriba, antes de ajustar memory_limit.
  • No se añaden saltos de línea, jamás. "MIME base64" en la descripción de la función no significa "salida con envolvido MIME". Si tu salida necesita líneas de 76 caracteres, las añades tú con chunk_split() o el filtro.

Una breve historia de base64_encode

El lado de la codificación de la historia de PHP es casi aburridamente refrescante, en el mejor sentido. base64_encode() llegó a PHP 4 como función del núcleo, con un parámetro y sin opciones, y desde entonces no ha ganado ni uno. Nunca se necesitó modo estricto (no hay nada de lo que ser estricto cuando tú eres quien produce los datos), nunca se añadió una opción de relleno, y el trabajo de envolvido se delegó en chunk_split() desde el primer día, que es por qué las dos funciones siguen juntas en las listas "Ver también" del manual.

El manual lleva la cifra del 33 por ciento desde tiempos inmemoriales: "los datos codificados en Base64 ocupan unos 33% más de espacio que los datos originales". Esa frase sigue ahí hoy, y es la razón por la que el número aparece en este artículo en absoluto. El filtro de stream convert.base64-encode se unió más tarde, y tuvo su propio bug del que madurar: en 2015, PHP corrigió un defecto (bug #68532) donde el filtro, en modo de lectura sobre streams de memoria, podía omitir el carácter final de relleno, que es exactamente el tipo de corrupción silenciosa de la que el camino basado en funciones nunca sufre. PHP 8.0 añadió el parámetro y los tipos de retorno nativos string, y ahí termina el changelog. Una función, un parámetro, veinte años, cero opciones: un monumento a acertar la superficie a la primera.

Datos PHP divertidos

Porque una referencia no está completa sin las rarezas:

  • La identidad vacía. base64_encode('') es ''. Sin relleno, sin salida, sin sorpresas: vacío entra, vacío sale.
  • Una dirección rara. El manual de PHP clasifica base64_encode() bajo "URLs" en el libro "Otras extensiones básicas". No hay capítulo de "codificación"; "URLs" es donde la encontrarás, junto a parse_url().
  • El alfabeto nunca se movió. Los mismos 64 caracteres han salido del codificador de PHP desde PHP 4. Una cadena Base64 producida por un script de PHP 4 en un equipo Windows en 2001 se decodifica idénticamente hoy en PHP 8.4 en Linux. Eso es interoperabilidad con 25 años de historial.
  • Un byte y tres bytes se ven idénticos en longitud. Ambos producen cuatro caracteres; solo el relleno los distingue. Por eso existe la tabla de tamaños de este artículo.
  • Cincuenta y siete es un número mágico. Una línea MIME de 76 caracteres guarda exactamente 57 bytes de datos originales, y esa coincidencia es lo que hace posible el bucle de trozos de streaming de la sección de datos grandes sin llevar estado entre lecturas.
  • Tiene un hermano del dial-up. El "Ver también" de base64_encode() incluso lista convert_uuencode(), el envoltorio PHP para uuencode, el formato que codificaba binarios para el correo antes de que MIME estandarizara Base64. Vive en el capítulo String Functions, funciones de cadena, pero es el registro fósil del propósito original de esta función.
  • Los viejos navegadores eran quisquillosos con el relleno. Una nota de php.net de 2004 cuenta que Internet Explorer rechazaba nombres de cookie que contenían =, que es por qué el código veterano a veces quita los rellenos finales del Base64 almacenado en cookies. Las configuraciones modernas no necesitan ese truco, pero explica las llamadas raras a rtrim($x, '=') que puedes heredar.

La otra cara

Ese es el lado de la codificación, y es el más fácil de los dos: la función no puede fallar, la salida es determinista, y el formato es uno que controlas de principio a fin. La dirección difícil es la de recibir el Base64 de otras personas: sus elecciones de relleno, sus saltos de línea, sus dialectos URL-safe, sus pegados corruptos. Ahí es donde un decodificador necesita modo estricto, una tubería de validación y una sana sospecha de todo. La decodificación Base64 en PHP, enlazada desde esta página, cubre el lado de la decodificación con la misma profundidad.

Última actualización: 2026-09-08

Artículo relacionado: Decodificación Base64 en PHP: una guía completa