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

Base64 en Perl

MIME::Base64 viene con Perl desde hace décadas y es todo lo que necesitas. Sus valores por defecto están pensados para el correo electrónico, y eso sorprende a más de uno. Las salidas de esta página son de Perl 5.40 con MIME::Base64 3.16.

Codificar y decodificar una cadena

use strict;
use warnings;
use utf8;
use MIME::Base64 qw(encode_base64 decode_base64);
use Encode qw(encode decode);

my $enc = encode_base64(encode('UTF-8', 'héllo wörld'), '');
print "$enc\n";                                   # aMOpbGxvIHfDtnJsZA==

print decode('UTF-8', decode_base64($enc)), "\n"; # héllo wörld

Perl distingue entre cadenas de caracteres y cadenas de bytes, y encode_base64 quiere bytes, así que el texto pasa primero por Encode::encode('UTF-8', ...). Una cadena con caracteres por encima de U+00FF muere con Wide character in subroutine entry; una que solo tenga caracteres Latin-1, como é, se codifica sin avisar como Latin-1, no como UTF-8.

El argumento '' se explica en la siguiente sección.

El argumento de final de línea

encode_base64($bytes, $eol) corta la salida a 76 caracteres y añade $eol a cada línea, incluida la última. El valor por defecto es "\n":

print encode_base64('hi');        # "aGk=\n"
print encode_base64('x' x 60);
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
# eHh4

Eso es correcto para los cuerpos de correo MIME e incorrecto para casi todo lo demás: cabeceras HTTP, JSON, URL y campos de base de datos. Pasa '' para obtener una sola línea, o "\r\n" para finales de línea estrictos según el RFC 2045.

Para una cabecera HTTP, eso significa:

print "Authorization: Basic ", encode_base64('user:pass', ''), "\n";
# Authorization: Basic dXNlcjpwYXNz

Al decodificar, los finales de línea dan igual: decode_base64("aGVs\r\nbG8K") devuelve "hello\n", así que los cuerpos MIME con \n o con \r\n se decodifican directamente.

Base64 URL-safe

MIME::Base64 exporta encode_base64url y decode_base64url si los pides. El codificador quita el relleno y el decodificador acepta entradas sin él:

use MIME::Base64 qw(encode_base64url decode_base64url);

print encode_base64url('<<???>>'), "\n";   # PDw_Pz8-Pg
print decode_base64url('PDw_Pz8-Pg'), "\n"; # <<???>>

Para una salida URL-safe que conserve el relleno, traduce la forma estándar con tr:

my $padded = encode_base64('<<???>>', '') =~ tr{+/}{-_}r;   # PDw_Pz8-Pg==

El modificador /r (Perl 5.14+) devuelve la copia modificada. Base64 frente a Base64URL explica qué sistemas esperan cada variante.

Codificar y decodificar un archivo

Abre los archivos en modo :raw para que ninguna capa traduzca saltos de línea ni codificaciones:

open my $in, '<:raw', 'hello.txt' or die $!;
my $data = do { local $/; <$in> };
close $in;
print encode_base64($data, ''), "\n";      # aGVsbG8K

open my $out, '>:raw', 'copy.txt' or die $!;
print {$out} decode_base64('aGVsbG8K');
close $out;

Archivos grandes por bloques

Lee en múltiplos de 57 bytes. 57 bytes se codifican en exactamente 76 caracteres, así que cada bloque produce líneas MIME completas y la salida concatenada es idéntica a codificar el archivo entero:

open my $in,  '<:raw', 'big.bin' or die $!;
open my $out, '>:raw', 'big.b64' or die $!;
while (read($in, my $buf, 57 * 1024)) {
    print {$out} encode_base64($buf);
}
close $in;
close $out;

El resultado se decodificó con base64 -d y devolvió el archivo original de 300 KB. Para una salida de una sola línea, usa encode_base64($buf, '') con cualquier tamaño de bloque divisible entre 3. El módulo también te da los tamaños sin codificar: MIME::Base64::encoded_base64_length('hello') devuelve 9 (incluido el salto de línea) y decoded_base64_length('aGVsbG8=') devuelve 5. La guía del aumento de tamaño tiene la fórmula.

Desde la línea de comandos, el módulo permite dos one-liners que conviene conocer:

perl -MMIME::Base64 -0777 -ne 'print encode_base64($_, "")' < file.bin
perl -MMIME::Base64 -0777 -ne 'print decode_base64($_)' < file.b64 > file.bin

Validación: decode_base64 nunca se queja

decode_base64 es tolerante por diseño. Descarta todo lo que está fuera del alfabeto, se detiene en el relleno y devuelve lo que tenga:

decode_base64('aGk=!!');        # "hi"
decode_base64('aGk');           # "hi"   (missing padding is fine)
decode_base64('aGl=');          # "hi"   (non-zero padding bits ignored)
decode_base64('a');             # ""     (too short, no warning)
decode_base64('PDw_Pz8-Pg==');  # "<<\x0f\xcf\xc3\xe0", not "<<???>>"

El último es el caso peligroso: una entrada URL-safe pasada al decodificador estándar produce bytes incorrectos sin avisar. Usa decode_base64url para los tokens. Con entradas que no son de confianza, comprueba el alfabeto y la longitud, y después confirma la ida y vuelta:

sub is_base64 {
    my ($s) = @_;
    return 0 unless $s =~ m{\A[A-Za-z0-9+/]*={0,2}\z} && length($s) % 4 == 0;
    return encode_base64(decode_base64($s), '') eq $s ? 1 : 0;
}
# is_base64('aGk=') = 1, 'aGk' = 0, 'aGl=' = 0, 'aGk=!!' = 0

El validador Base64 hace las mismas comprobaciones en el navegador y te dice qué carácter falla. Para cadenas rotas sacadas de logs, prueba Reparar Base64; errores habituales de Base64 explica qué suele causarlas.

Trampas

  • Salto de línea final en las cabeceras. "Authorization: Basic " . encode_base64("$user:$pass") termina en \n, lo que rompe la petición. Pasa ''.
  • Doble codificación. Con use utf8 y una capa de salida :encoding(UTF-8), imprimir bytes decodificados sin decode('UTF-8', ...) muestra mojibake como héllo.
  • Tratar Base64 como un secreto. Cualquiera puede revertirlo. Consulta codificación frente a cifrado.

Preguntas frecuentes

¿Por qué encode_base64 añade un salto de línea?

El segundo argumento es el final de línea, y por defecto vale "\n". La salida se corta a 76 caracteres y siempre termina con esa cadena. Pasa '' para obtener una sola línea.

¿Qué significa «Wide character in subroutine entry»?

Has pasado a encode_base64 una cadena de caracteres con puntos de código por encima de 255, y la función solo acepta bytes. Codifica antes el texto: encode_base64(encode('UTF-8', $text), '').

¿decode_base64 valida la entrada?

No. Ignora los caracteres que están fuera del alfabeto y devuelve lo que consiga decodificar, así que decode_base64('aGk=!!') da hi. Si la entrada no es de confianza, comprueba la cadena con una expresión regular antes de decodificar.

Sigue leyendo