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:
| Prelude | Engine | Alfabeto | Relleno al codificar | Relleno al decodificar |
|---|---|---|---|---|
BASE64_STANDARD | STANDARD | + / | sí | obligatorio |
BASE64_STANDARD_NO_PAD | STANDARD_NO_PAD | + / | no | debe faltar |
BASE64_URL_SAFE | URL_SAFE | - _ | sí | obligatorio |
BASE64_URL_SAFE_NO_PAD | URL_SAFE_NO_PAD | - _ | no | debe 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:
| Entrada | Variante | Mensaje |
|---|---|---|
PDw_Pz8-Pg== | InvalidByte(3, 95) | Invalid symbol 95, offset 3. |
aGk | InvalidPadding | Invalid padding |
aGk=!! | InvalidByte(3, 61) | Invalid symbol 61, offset 3. |
a | InvalidLength(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 aURL_SAFE.encode(x)ySTANDARD.encode(x). - Olvidar importar el trait. Sin
use base64::Engine;(o el prelude), llamar a.encodesobre un engine falla con «no method namedencodefound». - 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/decodepasan a estar obsoletas y se eliminan las funciones*_config. Las configuraciones sonGeneralPurposeConfigmásDecodePaddingMode. - 0.22:
DecodeError::InvalidLengthpasa a referirse al número de símbolos válidos;decode_slicefunciona 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.