Decodificación Base64 en Perl: una guía completa
Te han entregado una cadena de letras, dígitos y el ocasional + o /, y en el fondo sabes que no es lo que parece. Quizá sea un token montado en una cabecera Authorization, un archivo .b64 desenterrado de un ticket de soporte, un certificado con su armadura de -----BEGIN puesta, o un blob descansando tranquilamente en un archivo de configuración. Abres una terminal, escribes perl, y una pregunta se lleva todo lo demás: ¿cómo recupero los datos de verdad?
La respuesta es corta y tranquilizadora. Perl lleva un módulo de Base64 incluido en el propio lenguaje desde 2002, y una sola llamada a función, decode_base64, hace todo el trabajo: nada que instalar, nada que configurar. Un repaso rápido mientras se prepara el café: Base64 reescribe cada tres bytes de datos como cuatro caracteres de un alfabeto de 64 símbolos, rellenando el final con uno o dos signos = para que el resultado caiga siempre en un múltiplo de cuatro, y por eso la forma codificada suele ser unos 33 por ciento más grande que la original. La página de inicio de este sitio explica el formato al completo, así que esta guía se dedica por completo al lado de Perl: las reglas del decodificador, los dialectos y los formatos del mundo real que de verdad te encontrarás.
La caja de herramientas: cinco funciones, cero instalaciones
Todas las llamadas que necesitas viven en MIME::Base64, que forma parte de la distribución core de Perl desde la 5.8, así que está presente en toda instalación seria, desde la que va embebida en el firmware de un router hasta la de un servidor de base de datos. La comprobación es de una línea:
perl -MMIME::Base64 -e 'print $MIME::Base64::VERSION, "\n"'
# 3.16_01
Aquí está el lado de decodificación del módulo, completo:
| Función | Qué hace | Notas |
|---|---|---|
decode_base64($str) |
la estrella de este artículo: convierte un blob de Base64 en bytes crudos | ignora en silencio cada carácter fuera del alfabeto, para siempre |
MIME::Base64::decode($str) |
el mismo decodificador, llamado sin import | la forma que te encontrarás en un montón de scripts más antiguos |
decode_base64url($str) |
decodifica el dialecto seguro para URLs con - y _, con padding o sin él |
añadida en la 3.11 en 2010; la que lee los JWT |
MIME::Base64::decoded_base64_length($str) |
te dice de qué tamaño será la decodificada, sin decodificar | no exportada por defecto, práctica para preajustar buffers |
unpack("u", $data) |
decodifica datos uuencoded, el formato anterior a Base64 | integrada en el propio Perl, sin módulo |
El mapa de versiones de esas funciones, por si mantienes una flota de máquinas viejas:
| Funcionalidad | Disponible desde |
|---|---|
decode_base64() con el camino rápido en C |
Perl 5.8 en 2002, cuando el módulo se unió al core |
decoded_base64_length() |
módulo 3.10 en 2010 |
decode_base64url() |
módulo 3.11 en 2010 |
| Decodificación silenciosa, sin avisos con entrada sospechosa | módulo 3.11 en 2010 |
| La actual línea 3.16 | 2020, requiere Perl 5.6 o más reciente |
Si el Perl de tu sistema no tiene el módulo por alguna razón, y no debería pasar, la reparación es de una de dos líneas: el paquete de la distribución libmime-base64-perl en Debian y Ubuntu, o cpanm MIME::Base64 para descargar la versión actual desde CPAN, donde el módulo vive como paquete de vida dual desde sus días de core. Para la extraña máquina sin compilador de C, el gemelo puro en Perl MIME::Base64::Perl en CPAN ofrece la misma interfaz básica, unas veces más lento pero lo bastante bueno para cualquier cosa menos trabajo a granel. Esa es la historia completa de las dependencias: nada más.
Un decodificador que nunca dice que no
El contrato cabe en una línea. Le pasas una cadena, y te devuelve los bytes decodificados como una cadena Perl corriente que contiene octetos crudos. Sin objetos, sin excepciones, sin flags. La documentación enuncia en una sola frase las dos reglas que definen su personalidad: cualquier carácter que no forme parte del subconjunto Base64 de 65 caracteres se ignora en silencio, y cualquier carácter que aparezca después de un carácter de padding = nunca se decodifica. Esa cortesía es lo más importante de este artículo, así que déjala trabajar por ti una vez:
use MIME::Base64 qw(decode_base64);
print decode_base64("TWFu!"), "\n"; # Man - el signo de exclamación desaparece sin dejar rastro
print decode_base64("TWFu=XX"), "\n"; # Man - todo lo que hay después de = se omite
print decode_base64("TQ"), "\n"; # M - sin aviso, sin comentario
print decode_base64("T"), "\n"; # la cadena vacía, y aún así sin queja
Las dos últimas líneas son la tolerancia en su extremo. TQ lleva un byte completo más cuatro bits sobrantes, y el decodificador simplemente se queda el byte y descarta el resto. T no llega ni a un byte completo, así que el resultado está vacío. No hay modo estricto ni validador en el módulo que restaure la pedantería de vieja escuela: desde la versión 3.11 en 2010, decode_base64 ni siquiera avisa de la entrada truncada, y las versiones anteriores llegaban a emitir un aviso de Fin prematura de los datos base64 bajo -w. Si el blob es incorrecto, de todos modos lo decodifica, y eso te convierte en la puerta de calidad.
Aquí está la política de tolerancia en un solo sitio, para que puedas verla entera de un vistazo:
| Entrada | Resultado | Por qué |
|---|---|---|
"TWFu" |
Man |
entrada limpia, el camino feliz |
"TWFu!" |
Man |
el signo de exclamación no está en el alfabeto, así que se omite |
"TWFu=XX" |
Man |
nada después del padding se decodifica jamás |
"TWFuIFdvcmxkIQ==" |
Man World! |
los espacios en blanco donde sean, gratis |
"TQ" |
M |
cabe un byte completo, los bits sobrantes se descartan en silencio |
"T" |
la cadena vacía | ni siquiera un byte completo, y sin aviso tampoco |
"ab-cd_efgh" |
bytes incorrectos en silencio | las letras seguras para URLs se descartan como ruido, la trampa clásica |
Esa última fila es la que hay que recordar. Un segmento base64url pasado al decodificador estándar no falla: se decodifica a basura con aspecto plausible, porque los caracteres - y _ se tratan como ruido ajeno mientras las letras restantes siguen formando grupos válidos. El decodificador es un testigo, no un portero, así que si la entrada no es de confianza, tú la validas. Basta una pequeña comprobación estricta:
sub strict_base64 {
my ($blob) = @_;
$blob =~ s/[\r\n]//g; # el decodificador ignora estos, y nosotros también
return 0 unless length($blob) % 4 == 0;
return $blob =~ /\A[0-9A-Za-z+\/]+(?:={1,2})?\z/ ? 1 : 0;
}
print strict_base64("TWFu"), "\n"; # 1
print strict_base64("TQ="), "\n"; # 0 - recuento de padding incorrecto
print strict_base64("ab-cd"), "\n"; # 0 - alfabeto seguro para URLs
Una palabra corta sobre esa regex, aprendida a duras penas: si un sub termina en un return $x =~ /.../ a secas y el resultado del match fallido se pasa directamente a printf, Perl lanza un aviso engañoso de Falta un argumento en printf en lugar de un cero limpio. Fuerza el match con ? 1 : 0 antes de devolver, como hace la función de arriba, y el truco desaparece por completo.
Primero bytes, después caracteres
Recuerda qué devuelve decode_base64: bytes crudos, una cadena corriente sin la flag UTF-8 activada. Qué significan esos bytes es una decisión que solo puedes tomar tú, y es el paso donde el Unicode hace tropezar a la gente. Perl lleva la cuenta de si una cadena contiene caracteres o bytes, y length(), substr() y la mayoría de las expresiones regulares se comportan de forma distinta según la respuesta. La solución es nombrar tu codificación a propósito, con el módulo Encode que viene con cada instalación de Perl:
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $raw = decode_base64("SMOrbGxvIFdvcmxkIQ==");
my $text = decode("UTF-8", $raw);
print $text, "\n"; # Hëllo World!
print length($text), " chars\n"; # 12
print length($raw), " bytes\n"; # 13
Ese par de números es toda la lección. El blob es de 13 bytes pero solo de 12 caracteres, porque la letra acentuada ocupa dos bytes en UTF-8. Si saltas el paso del charset, los bytes se imprimirán igual de bien en una terminal UTF-8, y por eso el error se queda oculto hasta que una función de cadenas los cuenta, o los bytes viajan por un pipeline que espera caracteres. Si tienes dudas, decodifica con un charset estricto y deja que la excepción te cuente la verdad sobre los bytes: pasa Encode::FB_CROAK y decode() muere con secuencias inválidas en lugar de la sustitución silenciosa por U+FFFD del valor por defecto, y eso es una ventaja.
La lista corta de charsets que de verdad usarás:
| Charset | Cuándo usarlo | Cuidado con |
|---|---|---|
UTF-8 |
el supuesto por defecto: APIs, JSON, contenido web, texto moderno | las secuencias inválidas se convierten en U+FFFD por defecto; con Encode::FB_CROAK mueren limpias, que es exactamente lo que quieres |
Latin-1 |
texto occidental heredado, un byte por carácter, no puede fallar nunca | con gusto desgarra UTF-8 hasta convertirlo en mojibake doblemente codificado |
ASCII |
datos de los que estás seguro de que son texto plano de 7 bits | cualquier byte por encima de 127 se convierte en U+FFFD por defecto (muere bajo Encode::FB_CROAK) |
UTF-16 |
texto de Windows, donde la marca de orden de bytes decide la endianidad | el BOM es la única pista de endianidad, así que déjalo en los bytes |
Y aquí está la trampa de la que te protege el paso del charset. Si los bytes que decodificaste ya son UTF-8 y los pasas por encode("UTF-8", ...) al salir, no obtienes una copia: obtienes una codificación doble, donde cada carácter acentuado se hincha hasta convertirse en dos caracteres propios. El síntoma clásico es un texto que antes leía Hëllo y ahora lee Hëllo, y el receptor al otro lado del cable lo decodificará con fidelidad. Bytes entrando, bytes saliendo, una conversión nombrada en medio.
base64url: el alfabeto para URLs y tokens
La mitad del Base64 que cruza el internet moderno no es el alfabeto estándar en absoluto. El carácter + es la forma en que un navegador codifica un espacio en una query string, y / es un separador de rutas, así que las letras estándar son un desastre en las URLs. El RFC 4648, sección 5, define la solución: un segundo alfabeto que cambia + y / por - y _, y por convención descarta también el padding = y los saltos de línea. El RFC es explícito en que esta codificación no debe considerarse la misma que la codificación base64, y Perl lleva un par dedicado para ella desde la versión 3.11 en 2010:
use MIME::Base64 qw(decode_base64url);
my $raw = decode_base64url("c3Vuc2V0LTQy");
print $raw, "\n"; # sunset-42
Dos cosas que saber. Primera, decode_base64url se lleva bien con la entrada sin padding, que es la forma que de verdad encontrarás en la naturaleza, así que el ritual de restaurar el padding antes que requieren otros lenguajes no se aplica aquí; la entrada con padding también funciona. Segunda, el decodificador estándar es otra bestia: pásale un segmento base64url y obtendrás bytes incorrectos en silencio, porque los caracteres - y _ se descartan como ruido y el resto sigue decodificándose. Usa el decodificador adecuado, o normaliza a mano cuando estés atado a un camino de código heredado:
my $seg = "ab-cd_efgh";
$seg =~ tr{-_}{+/}; # las letras seguras para URLs, traducidas a casa
$seg .= "=" x (-length($seg) % 4); # padding restaurado para el decodificador estándar
my $raw = decode_base64($seg);
print unpack("H*", $raw), "\n"; # 69bf9c77f79f82 - siete bytes, de vuelta al alfabeto estándar
Te encontrarás con base64url inmediatamente en los JWT, los tokens que reparte cada API moderna, y en cualquier ID opaco que viva en una URL: IDs de video de once caracteres, UUIDs guardados en el alfabeto seguro para URLs (CPAN tiene Data::UUID::Base64URLSafe justo para esto) y claves de base de datos que necesitan sobrevivir a una barra de direcciones. Y si vas con un Perl viejo anterior a las funciones del core, el módulo independiente MIME::Base64::URLSafe de 2006, un port del codec urlsafe de Python, ofrece urlsafe_b64encode y urlsafe_b64decode; en cualquier cosa desde 3.11 en adelante, las funciones integradas son la mejor opción.
JWTs: leyendo la cabecera y el payload
Un JSON Web Token es, estructuralmente, dos trozos de JSON con un disfraz más un recibo criptográfico. La forma compacta del RFC 7515 son tres segmentos base64url unidos por puntos: la cabecera protegida, el payload y la firma. Dividirlo y leerlo son tres líneas:
use MIME::Base64 qw(decode_base64url);
use JSON::PP;
my $jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJob21lciJ9.uzM6l0c4...";
my ($head_b64, $claims_b64, $sig_b64) = split /\./, $jwt, 3;
my $head = decode_json(decode_base64url($head_b64));
my $claims = decode_json(decode_base64url($claims_b64));
print $claims->{sub}, " (", $head->{alg}, ")\n"; # homer (HS256)
Fíjate en la división del trabajo: decode_base64url convierte cada segmento en bytes, y decode_json del módulo core JSON::PP, presente desde Perl 5.14, convierte los bytes de la cabecera y del payload en estructuras de datos Perl. El segmento de la firma también es base64url, pero es un resumen criptográfico, así que solo decodificas los dos primeros segmentos y dejas que una librería adecuada se encargue del tercero.
El bache es el que todos olvidan: legible no significa válido. La cabecera y el payload son legibles por diseño, lo cual también significa que cualquiera puede reescribirlas; la firma es la única prueba. Para lo que sea serio, verifica, no te limites a decodificar. El módulo de CPAN Crypt::JWT, que se apoya en CryptX, hace todo el trabajo:
use Crypt::JWT qw(decode_jwt);
my $claims = decode_jwt(
token => $jwt,
key => $secret,
accepted_alg => "HS256",
);
Explota con una firma mala, y fijar accepted_alg cierra el agujero de confusión de algoritmos donde un atacante cambia el token a una variante más débil. Un flujo de decodificar e imprimir va bien para inspeccionar un token durante una llamada de soporte; no es autenticación.
Archivos: del .b64 de vuelta al original
Los archivos son donde la cultura de one-liners de Perl realmente brilla, y el trabajo entero cabe en un solo comando. La flag -0777 es el ingrediente secreto, porque slurpea el archivo entero en una sola cadena en lugar de alimentar al decodificador línea por línea:
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < in.b64 > out
La forma línea por línea es segura para una clase específica de archivos: aquellos donde cada línea contiene un múltiplo de cuatro caracteres Base64, que es cierto de todo cuerpo MIME doblado correctamente, ya que 76 es un múltiplo de 4. En el momento en que los puntos de doblado se ponen irregulares - y en los archivos doblados a mano normalmente lo están - la decodificación línea por línea empieza a producir padding en medio de los datos. El modo slurp no tiene tal condición, y por eso es la opción por defecto:
perl -MMIME::Base64 -ne 'print decode_base64($_)' < in.b64 > out
Dentro de un script, el patrón es la coreografía estándar de archivos de Perl, con un detalle callado pero importante: las capas :raw en ambos manejadores, para que Perl nunca intente interpretar los bytes como texto de la plataforma al entrar o al salir:
use MIME::Base64 qw(decode_base64);
use Digest::SHA qw(sha256_hex);
open my $in, "<:raw", $ARGV[0] or die $!;
local $/;
my $blob = <$in>;
close $in;
my $decoded = decode_base64($blob);
print sha256_hex($decoded), "\n"; # compara con la suma de verificación del remitente
open my $out, ">:raw", $ARGV[1] or die $!;
print {$out} $decoded;
close $out;
La línea del hash hace más que presumir. Como el decodificador acepta casi cualquier cosa, una suma de verificación que coincida con lo que publicó el remitente es la única prueba de que el viaje fue exacto al byte. Para archivos verdaderamente gigantescos, el bucle línea por línea es la alternativa de poca memoria, siempre que los puntos de doblado caigan en límites de cuatro caracteres, y MIME::Base64::decoded_base64_length te dice de qué tamaño será la salida antes de comprometerte con un buffer.
Armadura PEM: quita la capa, conserva el DER
Los archivos .pem de cada pila de seguridad son el mismo Base64 con armadura puesta: una línea de cabecera, una línea de pie, y un cuerpo doblado a 64 caracteres según la vieja convención de Privacy Enhanced Mail. El envoltorio es la única parte interesante, porque al decodificador del módulo no le importan las longitudes de línea en absoluto:
use MIME::Base64 qw(decode_base64);
open my $fh, "<:raw", "cert.pem" or die $!;
local $/;
my $blob = <$fh>;
close $fh;
my @body = grep { !/^-----/ && /\S/ } split /\n/, $blob;
my $der = decode_base64(join "", @body);
print length($der), " bytes of DER\n";
Las líneas BEGIN y END se quitan, el resto se une en una sola cadena, y cada salto de línea se ignora por el camino. Para el trabajo cotidiano con certificados las herramientas OpenSSL ya hacen esto por ti; las ocho líneas de arriba son el patrón que hay que recordar cuando necesitas los bytes DER crudos tú mismo, para un hash, una huella o una comparación.
Data URIs: imágenes que llevan su propia dirección
El esquema data: del RFC 2397 inyecta un payload directamente en una URL: data:, un tipo de media opcional, una flag opcional ;base64, una coma, y los datos. Los medios binarios como las imágenes usan la flag, así que el payload es el alfabeto estándar con padding, y el decodificador corriente se encarga de él tras un pequeño corte:
use MIME::Base64 qw(decode_base64);
my $uri = "data:image/png;base64,iVBORw0KGgo...";
$uri =~ s/^data:[^,]+,// or die "not a data URI";
my $raw = decode_base64($uri);
print unpack("H8", $raw), "\n"; # 89504e47: los bytes mágicos de PNG
Comprobar los bytes mágicos es la jugada. Si esos primeros ocho caracteres hexadecimales no son 89504e47, la imagen no es un PNG pase lo que pase el tipo de media, y un decodificador que nunca se queja hace posible exactamente ese tipo de mentira callada.
Primos y fósiles: uuencode y los demás alfabetos
Antes de que Base64 ganara, la forma clásica de UNIX para enviar un binario por correo era uuencode, y todavía te la encontrarás en listas de correo viejas y herramientas viejas. La buena noticia: Perl tiene un decodificador integrado para ella, sin módulo, gracias a la plantilla u de pack y unpack:
my $uu = pack("u", "Hello, World!");
print $uu, "\n"; # -2&5L;&\L(%=O<FQD(0`` más un salto de línea
my $back = unpack("u", $uu);
print $back, "\n"; # Hello, World!
Las dos llamadas son inversas exactas, y esa es toda la historia que necesitas; y el clásico comando uuencode de la cadena de herramientas de UNIX simplemente envuelve las líneas desnudas en una cabecera begin y un pie end, así que el payload que estás decodificando es la parte que hay entre ellas.
Base64 también tiene primos de dialecto, y saber qué decodificador se come cuál te ahorra una sesión de depuración:
| Dialecto | Doblado | Dónde te lo encuentras | Qué hace decode_base64 |
|---|---|---|---|
| MIME (RFC 2045) | 76 caracteres | cuerpos de correo | lo decodifica tal cual: los saltos de línea y el CRLF se ignoran |
| PEM (RFC 1421) | 64 caracteres | certificados y claves | lo decodifica tal cual |
| PKIX (RFC 7468) | 64 caracteres | estructuras textuales X.509 | lo decodifica tal cual |
| Armadura OpenPGP (RFC 9580) | 76 caracteres más una línea CRC24 | claves y firmas PGP | lo decodifica tal cual, la línea de suma de verificación se ignora |
| IMAP (RFC 3501) | ninguno | nombres de buzones | no es este alfabeto: la barra se convierte en coma, traduce las letras primero |
La moraleja: para toda variante del alfabeto estándar que solo se dobla de forma distinta, un decodificador tolerante lo cubre todo. Solo cuando cambia el propio alfabeto necesitas traducir los caracteres primero.
Configuración, bases de datos y variables de entorno
Las plataformas de contenedores, las consolas en la nube y un número sorprendente de archivos de configuración guardan credenciales y documentos pequeños como cadenas Base64 opacas, porque un blob de letras y dígitos parece menos peligroso que la contraseña que es. La decodificación siempre es la misma, de dos fases: decode_base64 más una decisión de charset:
use MIME::Base64 qw(decode_base64);
use Encode qw(decode);
my $secret = decode("UTF-8", decode_base64($config->{api_key}));
La razón de que el formato sea tan popular en este sitio es exactamente la que el RFC 4648 advierte: los humanos dejan de notar que los datos son legibles. Así que trata la salida decodificada como confidencial desde el momento en que se devuelve, y mantén tanto el blob como su resultado fuera de los archivos de registro, las alertas y los volcados de depuración.
La misma forma aparece en las bases de datos, donde los datos binarios a menudo viajan en una columna TEXT como Base64 porque la columna no puede prometer que dejará pasar bytes arbitrarios intactos:
use MIME::Base64 qw(decode_base64);
my $icon = decode_base64($row->{icon_data});
open my $fh, ">:raw", "icon.png" or die $!;
print {$fh} $icon;
close $fh;
Correo: partes MIME y adjuntos
El correo es donde Base64 consiguió su nombre, y la tolerancia del módulo está diseñada para exactamente este tráfico. Una parte MIME con Content-Transfer-Encoding: base64 llega como líneas de texto de 76 caracteres terminadas en CRLF, y el decodificador se come el sobre entero tal cual, saltos de línea incluidos:
use MIME::Base64 qw(decode_base64);
my $part_body = "SGVsbG8sIHF1ZXJ5IQpUaGlzIE1JTUUgcGFydCB0cmF2ZWxsZWQgYXMgYmFzZTY0LCB3cmFwcGVk";
$part_body .= "\r\nIGF0IDc2IGNoYXJhY3RlcnMsIENSTEYgYmV0d2VlbiBsaW5lcy4=";
my $text = decode_base64($part_body);
print $text; # el cuerpo original de dos líneas del mensaje
Si construyes o analizas correo con un framework, no haces nada de esto a mano: MIME::Lite codifica en base64 un adjunto por ti cuando pasas Encoding => "base64" a attach, y Email::MIME hace lo mismo automáticamente. La versión hecha a mano de arriba es para el correo que llega como texto crudo en un registro, un ticket o un mensaje reenviado, que en la práctica es un montón.
Trampas, recogidas y clasificadas
El módulo es lo bastante pequeño para memorizarlo, así que aquí está la lista completa de trampas en un solo sitio, ordenada aproximadamente por lo a menudo que pica:
| Trampa | Qué pasa | Solución |
|---|---|---|
| Alimentar al decodificador estándar con un segmento base64url | los - y _ se descartan como ruido, y el resto se decodifica en bytes incorrectos en silencio |
usa decode_base64url, o traduce el alfabeto y restaura el padding primero |
| Confiar en el silencio con entrada corrupta | caracteres ajenos, truncamiento y un alfabeto equivocado se decodifican todos sin un solo aviso | haz primero la comprobación estricta, y verifica con un hash cuando el original esté disponible |
| Caracteres no ASCII dentro del blob | una letra acentuada perdida o un espacio Unicode pegado se ignora en silencio, encogiendo el resultado sin comentario | la misma comprobación estricta rechaza todo lo que esté fuera del alfabeto de 7 bits |
| Tratar el resultado como texto | los bytes no llevan flag UTF-8, así que length() cuenta bytes y las funciones de cadenas se hacen una idea equivocada |
encadena decode("UTF-8", $raw) o el charset que elijas antes de cualquier procesamiento de texto |
| Codificación doble al salir | pasar bytes ya UTF-8 por encode("UTF-8", ...) convierte Hëllo en Hëllo |
codifica caracteres, nunca bytes crudos, y comprueba la flag con utf8::is_utf8() si tienes dudas |
| Decodificar un archivo línea por línea con doblados irregulares | las líneas que no terminan en límites de cuatro caracteres producen padding en medio de la salida | slurpea con -0777, o garantiza puntos de doblado de cuatro caracteres |
| Devolver un match fallido de regex a un contexto numérico | un sub que termina en return $x =~ /.../ y lo pasa a printf lanza un aviso engañoso de Falta un argumento en printf |
fuerza el match: return $x =~ /.../ ? 1 : 0 |
| Código viejo que espera el aviso viejo | los scripts anteriores a 3.11 que dependían del aviso Fin prematura de los datos base64 bajo -w ahora no ven nada |
añade tu propia comprobación estricta; el aviso se fue para siempre |
| Suponer que decodificar es verificar | el decodificador acepta casi cualquier cosa y no dice nada al respecto | una suma de verificación coincidente o una firma verificada es la única prueba que importa |
| Registrar en el log lo que decodificas | el formato no oculta nada, y el archivo de registro es justo donde lo encuentra el siguiente | mantén los secretos decodificados fuera de los registros, las alertas y los volcados de depuración |
Buenos hábitos
Los hábitos que evitan que Base64 se salga con la suya con tus scripts:
- Espera bytes, siempre. Escribe código que sepa que
decode_base64devuelve octetos crudos, y encadena eldecodede charset explícitamente en lugar de esperar que la terminal haga lo correcto. - Nombra tu charset. Por defecto,
UTF-8, y cambia solo cuando los datos digan lo contrario. El fallo estricto dedecode("UTF-8", ..., Encode::FB_CROAK)es una ventaja: te dice que los bytes no son lo que supusiste. - Empareja el alfabeto con la fuente.
decode_base64urlpara URLs, tokens e IDs;decode_base64para todo lo demás. Los dos alfabetos no son intercambiables, y el decodificador no te dirá cuando elijas mal. - Valida antes de decodificar. No hay flag de modo estricto en este módulo, así que una pequeña comprobación es el portero de entrada.
- Slurpea los archivos por defecto.
-0777olocal $/ = undefelimina una clase entera de bugs de puntos de doblado, y el coste de memoria no es problema para los archivos que de verdad decodificas. - Usa
:rawen cada manejador de archivo. Binario entrando, binario saliendo. Las capas de texto son para humanos, no para bytes. - Verifica con un hash. Cuando el original esté disponible, una suma de verificación coincidente es la única prueba de una decodificación exacta al byte.
- Nunca registres en el log lo que decodificas. El formato no oculta nada.
Una historia corta, contada por el changelog
El formato es viejo, y la relación de Perl con él es más vieja de lo que parece. Algunas fechas verificadas, en orden:
- El código C es anterior a Perl 5. El decodificador rápido dentro del módulo desciende de código de metamail, el programa de correo de Bellcore, con copyright en 1991, tres años antes del primer lanzamiento de Perl 5. Cuando hoy llamas a
decode_base64, un pedazo de los noventa está haciendo el trabajo. - Nacido en las herramientas web. El módulo empezó como
LWP::Base64dentro de libwww-perl a mediados de los noventa, escrito por Martijn Koster y Joerg Reichelt, y ascendió a su propia distribución de CPAN,MIME::Base64, en abril de 1997, versión 2.00, con la entrada del changelog que dice basado en libwww-perl-5.08. - La era de los avisos. Desde la 2.03 en 1997, la entrada truncada producía un aviso de Fin prematura de los datos base64 bajo
-wen lugar de un croak, y la 2.11 en 1999 arregló las compilaciones que avisaban de datos que estaban bien. Fue una década más nerviosa para los decodificadores. - En el core desde 2002. Perl 5.8 metió el módulo en la distribución core, y la sincronización 2.13 con el core ese mismo diciembre trajo el soporte EBCDIC por el camino, y por eso el codificador y el decodificador siguen funcionando en las mainframes.
- El dialecto seguro para URLs llegó al core de Perl en 2010. La versión 3.11 añadió
decode_base64urly su hermano, cuatro años después de que el módulo independienteMIME::Base64::URLSafeaterrizara en CPAN en 2006, el mismo año en que el RFC 4648 codificó el dialecto. - El silenciamiento. Ese mismo lanzamiento 3.11 quitó hasta el viejo aviso de truncamiento, por si acaso la entrada sospechosa era intencionada - y cada lanzamiento desde entonces, incluida la actual línea 3.16 de 2020, ha mantenido al decodificador educado y callado.
Datos curiosos, específicamente Perl
Para redondear el recorrido, las curiosidades que hacen de esta historia una buena:
- Decodifica el propio nombre del formato.
decode_base64("YmFzZTY0")devuelvebase64. Ha sido cierto desde 1997 y lo será para siempre. - El decodificador es un fantasma educado. En su historia 3.x nunca ha lanzado una excepción con entrada mala. Corrupta, truncada, alfabeto equivocado: lo decodifica todo y no se queja de nada, un comportamiento que el changelog cementó a propósito en 2010.
- El doblado MIME es un múltiplo de cuatro a propósito. El límite de 76 caracteres corresponde a diecinueve grupos de tres bytes, 57 bytes en total, por cuatro caracteres, y por eso la decodificación línea por línea es segura con cualquier cuerpo MIME doblado correctamente e insegura con todo lo demás.
- uuencode nunca imprime una letra minúscula. Su alfabeto se detiene en el guion bajo, y por eso los archivos uuencoded viejos parecen escritos por una máquina de solo mayúsculas, y por eso Perl sigue llevando un decodificador integrado para un formato más viejo que el internet.
- Perl llegó a traer su propio comando decode-base64. Las versiones desde la 2.14 en 2003 hasta la 3.05 en 2004 incluían
encode-base64,decode-base64y sus gemelos quoted-printable como scripts; la 3.06 en 2005 los movió a la distribución aparte MIME-Base64-Scripts. Si encuentras una instalación vieja con ese comando en el PATH, ya sabes de dónde viene. - Los IDs de video de YouTube son base64url con disfraz. El ID de once caracteres en tu barra de direcciones es un número de 64 bits en el alfabeto seguro para URLs con el padding quitado, así que cada video que hayas visto tiene una cadena Base64 en su URL, y
decode_base64urlpuede leerla. - La tolerancia es un estándar, no un bug. La regla MIME de ser liberal en lo que aceptas es la razón de que este decodificador sobreviva a tres décadas de datos desordenados, y la razón de que el RFC 4648 advierta que esa misma tolerancia se puede convertir en un canal encubierto si confías en entrada no confiable.
Así que la próxima vez que una cadena de letras, dígitos, más y barra aterrice en tu terminal, ya conoces la historia entera. Una llamada a función hace el trabajo, el decodificador es un fantasma educado que nunca te negará nada, base64url tiene su propio decodificador, el charset es una decisión que tomas a propósito, los archivos entran crudos y salen crudos, y un hash es la única prueba que importa. Y si un día necesitas hacer el viaje en la otra dirección, envolviendo tus propios datos crudos en un sobre de texto y enviándolos al mundo, el artículo relacionado sobre la codificación Base64 en Perl, enlazado abajo, cubre ese ritual con la misma profundidad.
Última actualización: 2026-09-08
Artículo relacionado: Codificación Base64 en Perl: una guía completa