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

Base64 en Rust

La biblioteca estándar de Rust no tiene Base64. La opción de facto es el crate base64, cuya API pasó a basarse en engines en la 0.21. Los fragmentos de esta página se compilaron con la 0.23.1 y la 0.22.1.

Añadir el crate y codificar una cadena

[dependencies]
base64 = "0.22"   # the snippets below also build with 0.23

Desde la 0.21, todo el trabajo pasa por un Engine. El módulo prelude exporta el trait Engine y cuatro engines listos para usar:

use base64::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let enc = BASE64_STANDARD.encode("héllo wörld");
    println!("{enc}");                              // aMOpbGxvIHfDtnJsZA==

    let bytes = BASE64_STANDARD.decode(&enc)?;
    println!("{}", String::from_utf8(bytes)?);      // héllo wörld
    Ok(())
}

encode acepta cualquier cosa que sea AsRef<[u8]>, así que funcionan &str, String, Vec<u8> y los literales de bytes. Las cadenas de Rust son UTF-8, así que no hace falta ningún paso de conversión. Si los bytes decodificados no son UTF-8 válido, String::from_utf8 devuelve un error en lugar de adivinar.

Los nombres del prelude corresponden a base64::engine::general_purpose:

PreludeEngineAlfabetoRelleno al codificarRelleno al decodificar
BASE64_STANDARDSTANDARD+ /síobligatorio
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /nodebe faltar
BASE64_URL_SAFEURL_SAFE- _síobligatorio
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _nodebe faltar

Base64 URL-safe con y sin relleno

let d = b"<<???>>";
BASE64_URL_SAFE.encode(d);          // "PDw_Pz8-Pg=="
BASE64_URL_SAFE_NO_PAD.encode(d);   // "PDw_Pz8-Pg"

let back = BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg")?;   // b"<<???>>"

Los engines sin relleno rechazan las entradas con relleno (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") devuelve Err(InvalidPadding)). Si aceptas tokens de varios productores, crea un engine al que le dé igual:

use base64::alphabet;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;

const URL_LENIENT: GeneralPurpose = GeneralPurpose::new(
    &alphabet::URL_SAFE,
    GeneralPurposeConfig::new()
        .with_encode_padding(false)
        .with_decode_padding_mode(DecodePaddingMode::Indifferent),
);

URL_LENIENT.decode("PDw_Pz8-Pg").is_ok();     // true
URL_LENIENT.decode("PDw_Pz8-Pg==").is_ok();   // true

Los otros modos son RequireCanonical (el predeterminado en los engines con relleno) y RequireNone. Contexto sobre las variantes: Base64 frente a Base64URL y el relleno explicado.

Codificar y decodificar un archivo

let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data));          // aGVsbG8K

std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;

Para añadir el resultado a un búfer existente sin una nueva asignación de memoria, usa encode_string:

let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}");                                         // Basic dXNlcjpwYXNz

Flujos con EncoderWriter y DecoderReader

Los módulos write y read envuelven tipos de std::io, así que los archivos grandes nunca se cargan en memoria:

use std::fs::File;
use std::io::{self, Read};

let mut input = File::open("big.bin")?;
let mut w = base64::write::EncoderWriter::new(File::create("big.b64")?, &BASE64_STANDARD);
io::copy(&mut input, &mut w)?;
w.finish()?;            // writes the final block and padding

let mut r = base64::read::DecoderReader::new(File::open("big.b64")?, &BASE64_STANDARD);
let mut out = Vec::new();
r.read_to_end(&mut out)?;

Llama a finish() de forma explícita. Al soltar el writer (drop) también se intenta vaciar, pero entonces no se pueden notificar errores. Con un archivo de prueba de 300 KB, la salida en flujo fue igual a la de BASE64_STANDARD.encode, y los bytes decodificados coincidieron con el original.

Validación y DecodeError

decode devuelve Result<Vec<u8>, DecodeError>. Estos son los resultados con BASE64_STANDARD, con la salida Debug de la 0.22.1 y el mensaje Display:

EntradaVarianteMensaje
PDw_Pz8-Pg==InvalidByte(3, 95)Invalid symbol 95, offset 3.
aGkInvalidPaddingInvalid padding
aGk=!!InvalidByte(3, 61)Invalid symbol 61, offset 3.
aInvalidLength(1)Invalid input length: 1
aGl=InvalidLastSymbol(2, 108)Invalid last symbol 108, offset 2.
"aGVs\nbG8K"InvalidByte(4, 10)Invalid symbol 10, offset 4.

Los valores de byte están en decimal: 95 es _, 61 es =, 10 es un salto de línea. InvalidLastSymbol indica bits de relleno distintos de cero, que este crate rechaza por defecto; with_decode_allow_trailing_bits(true) relaja esa regla. En la 0.23 la variante InvalidLastSymbol cambió y su mensaje ahora dice Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Compara con el nombre de la variante, no con el texto.

use base64::DecodeError;

match BASE64_STANDARD.decode(input) {
    Ok(bytes) => { /* use bytes */ }
    Err(DecodeError::InvalidByte(offset, byte)) => eprintln!("bad byte {byte} at {offset}"),
    Err(e) => eprintln!("invalid Base64: {e}"),
}

Pega una cadena que falle en el validador Base64 para ver el carácter problemático; la guía de errores de Base64 repasa las causas.

Trampas

  • Tutoriales antiguos. encode_config(x, base64::URL_SAFE) y las constantes de configuración son anteriores a la 0.21 y ya no compilan; base64::encode(x) todavía compila con un aviso de obsolescencia. Tradúcelos a URL_SAFE.encode(x) y STANDARD.encode(x).
  • Olvidar importar el trait. Sin use base64::Engine; (o el prelude), llamar a .encode sobre un engine falla con «no method named encode found».
  • Espacios en blanco. El decodificador trata los saltos de línea y los espacios como bytes no válidos. Quítalos, por ejemplo con s.split_ascii_whitespace().collect::<String>().
  • Sin ayuda para MIME. El crate no corta líneas. Si generas cuerpos de correo, divide tú la salida en bloques de 76 caracteres.

Notas de versión

  • 0.21: se introduce la API Engine; las funciones libres encode/decode pasan a estar obsoletas y se eliminan las funciones *_config. Las configuraciones son GeneralPurposeConfig más DecodePaddingMode.
  • 0.22: DecodeError::InvalidLength pasa a referirse al número de símbolos válidos; decode_slice funciona con búferes de tamaño exacto.
  • 0.23: engines SIMD tras la feature por defecto simd-unsafe, más constantes preconfiguradas, símbolos de relleno personalizados, MSRV 1.71. Los engines de propósito general que se usan en esta página no cambian.

Preguntas frecuentes

¿Por qué base64::encode aparece como obsoleta?

Desde la 0.21, las funciones libres base64::encode y base64::decode son wrappers obsoletos (siguen presentes en la 0.23). Usa un engine en su lugar: use base64::prelude::*; y después BASE64_STANDARD.encode(data).

¿Por qué el crate base64 rechaza las entradas sin relleno?

STANDARD exige un relleno canónico. Usa URL_SAFE_NO_PAD o STANDARD_NO_PAD para entradas sin relleno, o crea un engine con DecodePaddingMode::Indifferent para aceptar ambas.

¿El crate base64 se salta los saltos de línea?

No. "aGVs\nbG8K" falla con InvalidByte(4, 10). Quita los espacios en blanco antes de decodificar cuerpos MIME o PEM.

Sigue leyendo