Werk je met Base64-indeling? Dan is deze site perfect voor jou! Gebruik onze handige online tool om je gegevens te coderen of te decoderen.

Base64-codering in Rust: een complete gids

Je staat op het punt een paar bytes door een uitsluitend-tekst-deur te sturen, en de inrittaks is een string van letters, cijfers en plus- en slash-tekens die ruwweg een derde langer is dan waar je mee begon. Welkom bij Base64, het tolhuis van het internet. De startpagina van deze site legt het formaat in volle diepte uit, dus hoeft hier alleen de vorm herhaald te worden: Base64 schrijft drie invoerbytes weg als vier tekens uit een alfabet van 64 symbolen, en een staart van één of twee =-tekens vertelt de lezer waar de echte data ophield. Die ruil van vier voor drie is de hele economie van het formaat, en deze gids gaat over hoe je dat goed doet in Rust.

Het eerste om te weten is dat de Rust-standaardbibliotheek het niet voor je doet. Er zit geen base64_encode() verborgen in std, en er is geen use std::... die je van gedachten verandert. Het ecosysteem zette in op één enkele crate, simpelweg base64 genoemd, en die werd dragend: versie 0.23.1 verscheen op 4 augustus 2026, de crate publiceerde 45 versies sinds december 2015, en de downloadteller zit rond de 1,5 miljard. Elk voorbeeld over encoderen hieronder gebruikt die ene crate, plus twee kleine maatjes voor regelomwikkeling en PEM-pantsering.

De toolchain en de crate

Eerst de toolchain, één commando per wereld:

# Debian / Ubuntu
sudo apt install rustc cargo
# of de officiële installer, die rustup en cargo installeert
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Daarna de crate, in elk cargo-project:

cargo new my-app
cd my-app
cargo add base64

Die ene regel is de volledige installatie, en hij trekt exact nul afhankelijkheden met zich mee. De crate wordt geleverd met drie optionele features die je moet kennen: std (standaard aan; geeft je std::io-streaming, de standaard Error-implementaties en heap-toewijzing), alloc (de allocerende API's voor ingebedde no_std-builds) en simd-unsafe (standaard aan; de SIMD-engines, die een paar secties verderop verschijnen). De minimum ondersteunde Rust-versie is 1.71.0, dus alles recente draait het. Rond de crate zitten de maatjes voor het werk dat de kern bewust niet doet:

  • line-wrap (versie 0.2) voegt de regeleindes van 76 of 64 tekens toe die MIME en PEM eisen; de base64-crate zelf weigert te omwikken, met opzet, zoals je straks ziet.
  • pem (versie 4) bouwt en parst -----BEGIN ...------blokken voor certificaten en sleutels; het hangt intern af van base64 en voegt de pantsering en de omwikkeling toe.
  • base64ct (versie 1.8) is de constant-time-decoder van het RustCrypto-project, voor wanneer de lees-kant van een heen-en-weertrip de gevoelige helft is.
  • base64-turbo (versie 0.3) is een jongere codec met hoge doorvoer die op moderne hardware piekt boven de 100 GiB/s.

Één encode, vier tekens

Het kleinste denkbare ceremonieeltje ziet er zo uit, en het bewijst al de hele heen-en-weertrip:

use base64::prelude::*;
fn main() {
  let packed = BASE64_STANDARD.encode("Hello, world!");
  println!("{packed}");
  // SGVsbG8sIHdvcmxkIQ==
  let back = BASE64_STANDARD.decode(packed).unwrap();
  println!("{}", String::from_utf8(back).unwrap());
  // Hello, world!
}

Twee dingen verdienen aandacht. Het prelude-module reikt je stilzwijgend twee dingen tegelijk aan: de BASE64_STANDARD-engine en de Engine-trait wiens methodes je aanroept, en daarom is een kaal use base64::prelude::*; alles wat dit voorbeeld nodig heeft. En encode() accepteert alles wat als bytes gelezen kan worden, dankzij de AsRef<[u8]>-bound: een &str, een &[u8]-literal, een Vec<u8>, noem maar op. Het decoderende deel van het voorbeeld staat er alleen om een oogje in het zeil te houden op de encoder, want de andere richting krijgt zijn eigen volledige gids op de zustersite. Als je de eigen smoke test van de crate wilt, codeert zijn documentatie asdf en krijgt YXNkZg== terug; zelfde alfabet, zelfde rekenwerk.

De exacte prijs van elke byte

Elke Base64-encoder die er bestaat, factureert dezelfde belasting, en als je het rekenwerk een keer ziet, kun je het in je planning meenemen. Elk uitvoerteken draagt 6 bits, elke invoerbyte draagt 8, en de kleinste hoop die beide is, is 24 bits: exact 3 bytes binnen, exact 4 tekens buiten. Die verhouding is het hele spektakel, dus wordt een bestand van 3 kilobyte 4 kilobyte en een upload van 10 megabyte 13,3. De padding is de afrondingsfout die zichtbaar is gemaakt: als de invoer geen veelvoud van 3 bytes is, dan heeft de laatste groep overcapaciteit, en de encoder vult die met =, zodat de uitvoerlengte een veelvoud van 4 blijft. Hier is de waarheidstafel uit RFC 4648, die de standaard-engine exact reproduceert:

Invoer Lengte mod 3 Gecodeerd Uitvoerslengte
"" (leeg) 0 "" (leeg) 0
f 1 Zg== 4
fo 2 Zm8= 4
foo 0 Zm9v 4
foobar 0 Zm9vYmFy 8
use base64::prelude::*;
let words: [&[u8]; 4] = [b"", b"f", b"fo", b"foo"];
for input in words {
  println!("{:?} -> {:?}", String::from_utf8_lossy(input), BASE64_STANDARD.encode(input));
}
// "" -> ""
// "f" -> "Zg=="
// "fo" -> "Zm8="
// "foo" -> "Zm9v"

Lees die eerste rij twee keer, want die is het waar iedereen in zijn hoofd het over de kop houdt: de lege invoer codeert naar de lege string, niet naar AA==. De string AA== is de codering van exact één byte, een NUL, en dat is een volstrekt andere payload. En als je vóór het encoderen een buffer van de juiste grootte nodig hebt, reikt de crate je het rekenwerk aan als een const fn, zodat je zelfs arrays op compileertijd kunt dimensioneren:

let padded = base64::encoded_len(15, true).unwrap();
let slim = base64::encoded_len(15, false).unwrap();
println!("{padded} / {slim}");   // 20 / 20
println!("{:?}", base64::encoded_len(13, true));  // Some(20)
println!("{:?}", base64::encoded_len(13, false)); // Some(18)
println!("{:?}", base64::encoded_len(14, false)); // Some(19)
println!("{:?}", base64::encoded_len(100, false)); // Some(134)

Let op de rijen van 13 en 14 bytes, want die zijn het die schattingen op een papiertje in het gedrang brengen: 13 bytes hebben 18 tekens zonder padding nodig maar 20 met, terwijl 14 bytes 19 en 20 nodig hebben. De functie retourneert een Option, die None is alleen als de lengterekening zou overlopen, dus een unwrap() is veilig voor elke invoer die daadwerkelijk in het geheugen zou kunnen bestaan. Voor de e-mail-vormige wereld heeft de belasting een toeslag: MIME wikkelt regels om op 76 tekens, en de oude vuistregel zegt dat omwikkelde Base64 ongeveer 1,37 keer de oorspronkelijke grootte kost, plus de header-overhead. De eigen FAQ van de crate heeft een minder beleefde mening over de padding zelf: de =-bytes "hebben geen invloed op het decoderen, behalve dat ze de gelegenheid bieden te zeggen 'die padding is onjuist'", en "exabytes aan opslag en overdracht zijn ongetwijfeld verspild aan nutteloze =-bytes".

Padding: een besluit over de lezer

In base64 0.23 zijn de kale encode-functies verouderd - de huidige manier is een methode aan te roepen op een Engine, en een engine is een beleid: welk alfabet je schrijft, en welke padding je toevoegt. De standaardinstellingen wonen in base64::engine::general_purpose, met de vier populaire opnieuw geëxporteerd in het prelude:

Engine Alfabet Voegt padding toe Best voor
STANDARD / BASE64_STANDARD + / ja alles, de standaard
STANDARD_NO_PAD / BASE64_STANDARD_NO_PAD + / nee slanke payloads die je zelf ook consumeert
URL_SAFE / BASE64_URL_SAFE - _ ja URL-inhoud die nog steeds padding wil
URL_SAFE_NO_PAD / BASE64_URL_SAFE_NO_PAD - _ nee JWTs, URLs, object-ID's

Encoderen zonder padding is geen truc die de crate tolereert; het is een eerste-klasse positie, met vooraf geconfigureerde NO_PAD- en PAD-configuratieconstanten naast de *_INDIFFERENT-broers die in 0.23.0 zijn toegevoegd. Als de standaardinstellingen niet passen, bouw je je eigen engine uit een Alphabet en een GeneralPurposeConfig met één knop, en omdat engines goedkoop op te bouwen zijn, bewaar je het resultaat in een const in plaats van het per aanvraag opnieuw op te bouwen:

use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::prelude::*;
const SLIM: GeneralPurpose = GeneralPurpose::new(
  &base64::alphabet::STANDARD,
  GeneralPurposeConfig::new().with_encode_padding(false),
);
fn main() {
  println!("{}", SLIM.encode("fo"));   // Zm8
  println!("{}", BASE64_STANDARD.encode("fo")); // Zm8=
}

Nu wordt het besluit een besluit over de decoders van anderen, en dat is nooit puur esthetisch. De strengheidsregels aan de decoderkant komen uit DecodePaddingMode, en de onderstaande tabel antwoordt op "kan de andere kant wat ik schreef lezen?":

Jij codeert met Een strikte STANDARD-decoder Een STANDARD_NO_PAD-decoder Een INDIFFERENT-decoder
STANDARD (met padding) leest het wijst de = af leest het
STANDARD_NO_PAD wijst af: padding ontbreekt leest het leest het
URL_SAFE_NO_PAD wijst af: verkeerd alfabet wijst af: verkeerd alfabet leest het alleen met het URL-alfabet

De praktische regels vallen uit die tabel. Als je beide uiteinden onder controle hebt, kies dan één engine en gebruik die overal, en geef de voorkeur aan geen padding om bytes te besparen. Als je data consumeert uit de buitenwereld, dan krijgt je decoder een stem over welke engine jij moet gebruiken: een standaard STANDARD-decoder heeft je padding nodig, terwijl een STANDARD_PAD_INDIFFERENT-decoder beide accepteert. En de keuze heeft ook een beveiligingskant. Door zowel met- als zonder-padding-spellingen van dezelfde payload toe te staan, wordt Base64 vervormbaar; het paper uit 2022 "Base64-vervormbaarheid in de praktijk" (Chatzigiannis en Chalkias, ePrint 2022/361), waarnaar de eigen documentatie van de crate linkt, toont waarom. Een protocol waarin dezelfde data op twee verschillende manieren geschreven kan worden, heeft de gewoonte code te verrassen die de gecodeerde string als een identiteit behandelt, dus als je formaat één canonieke spelling definieert, dan dwing die af aan de grens.

Base64url voor tokens en links

De laatste twee alfabettekens van standaard Base64 zijn + en /, en in een URL zijn dat twee van de duurste tekens van de taal: plus wordt %2B, slash wordt %2F, en padding wordt %3D. Sectie 5 van RFC 4648 lost dit op met het URL- en bestandsnaam-veilige alfabet, dat de twee rotkoeien vervangt door - en _ en meestal ook de padding slaat. De engines maken het onderscheid onmiskenbaar:

use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
let packed = URL_SAFE_NO_PAD.encode(b"\xfb\xef\xbe");
println!("{packed}");          // ----
let back = URL_SAFE_NO_PAD.decode(packed).unwrap();
println!("{back:02x?}");       // [fb, ef, be]

Drie bytes van de ongenadigst mogelijke invoer worden een string van vier tekens die je kunt plakken in een URL, een bestandsnaam, een cookie of een database-sleutel zonder één percentage-escape. Dit is het alfabet waarin JSON Web Tokens leven: een JWT is drie base64url-delen, samengevoegd met punten, en er een slaan met de jsonwebtoken-crate (versie 11 in 2026) ziet er zo uit:

use serde::Serialize;
use jsonwebtoken::{EncodingKey, Header, encode};
#[derive(Debug, Serialize)]
struct Claims {
  sub: String,
  company: String,
  exp: u64,
}
let key = b"secret";
let my_claims = Claims {
  sub: "b@b.com".to_owned(),
  company: "ACME".to_owned(),
  exp: 19_000_000_000,  // ver in de toekomst
};
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)).unwrap();
println!("{token}");
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29t...

Versie 11 heeft één configuratievereiste waar nieuwkomers over struikelen: de crate heeft exact één van de features rust_crypto of aws_lc_rs nodig, ingeschakeld in Cargo.toml, en als noch één aan staat, paniceert zij de eerste keer dat je een token ondertekent of verifieert. Let op de exp-claim in de struct: de verificatie van de crate behandelt hem als verplicht bij standaard, dus echte tokens dragen er toch een, en het base64url-alfabet in het token is volledig zaak van de bibliotheek. Als je tokens alleen inspecteert in plaats van ze te slaan, toont het zusterartikel de vijfregelige blik. En een herinnering aan de gouden regel, die voor tokens met extra kracht geldt: de drie delen van een JWT zijn allemaal leesbaar zonder sleutel. Base64 is een stoel bij het raam, geen slot.

Tekst binnen, bytes buiten

Encoders lezen geen gedachten, dus "codeer deze string" betekent in Rust altijd "codeer de UTF-8-bytes van deze string", want dat is wat str::as_bytes() oplevert. Het goede nieuws is dat het moderne web vrijwel volledig UTF-8 is, dus is het eerlijke pad kort en gelukkig:

use base64::prelude::*;
let text = "café";
let packed = BASE64_STANDARD.encode(text.as_bytes());
println!("{packed}");   // Y2Fmw6k=

De multibyte-gevallen gedragen zich allemaal:

Originele tekst Base64 Heen-en-weertrip
café Y2Fmw6k= netjes
日本語 5pel5pys6Kqe netjes
😀 8J+YgA== netjes
π ≈ 3.14159 z4Ag4omIIDMuMTQxNTk= netjes

Het ene echte besluit is van welke bytes je uitgaat. Als de data aankomt als bytes en niet als tekst, een bestand gelezen van schijf of een buffer van een netwerkverzoek, sla dan de string helemaal over en codeer de Vec<u8> direct; dat is ook het enige juiste antwoord voor niet-UTF-8-payloads als een PNG of een protobuf. Leg een willekeurige afbeelding naast je code en wijs de lezing daarop:

use base64::prelude::*;
let file_bytes = std::fs::read("sprite.png").unwrap();
let size = file_bytes.len();
let packed = BASE64_STANDARD.encode(file_bytes);
println!("{size} bytes -> {} base64 chars", packed.len());
// elke gecodeerde PNG begint met iVBORw0K
assert!(packed.starts_with("iVBORw0K"));

Die laatste assertie is een gratis sanity-check en een van de meest herkenbare prefixen op internet. En als je ooit dezelfde logische tekst codeert via twee verschillende charsets, of bytes codeert die je verkeerd gelezen hebt als een andere charset, dan komt de heen-en-weertrip terug als mojibake met een recht gezicht. De encoder liegt nooit; hij codeert gewoon welke bytes je hem ook geeft, en dat is zowel zijn grootste kracht als zijn enige val.

Wanneer een formaat regels wil

De base64-crate voegt met opzet geen regeleindes toe, en het is niet de eerste keer dat die keuze gemaakt is. Versie 0.5.0 bracht ingebouwde MIME-regelomwikkeling met instelbare regeleindes, en versie 0.10.0 verwijderde die; de bibliotheek besloot dat omwikkeling te meningsvormend was voor een algemene crate en het no_std-verhaal vermorzelde. Als een formaat regels eist, bestaat de line-wrap-crate precies daarom. Zijn ene functie, line_wrap(), neemt je vooraf toegewezen buffer, de invoerlengte, de kolomgrens en het regeleinde en geeft terug het aantal regeleinde-bytes die hij invoegde:

use base64::prelude::*;
let data = BASE64_STANDARD.encode(vec![b'a'; 300]);  // 400 tekens
let mut buf = vec![0u8; data.len() + 16];
buf[..data.len()].copy_from_slice(data.as_bytes());
let endings = line_wrap::line_wrap(&mut buf, data.len(), 76, &line_wrap::crlf());
buf.truncate(data.len() + endings);
let wrapped = String::from_utf8(buf).unwrap();
println!("{} chars in, {} bytes out, {} line endings", data.len(), wrapped.len(), endings);
// 400 chars in, 410 bytes out, 10 line endings (vijf CRLF-paren)

Dimensioneer de buffer vooraf met ruimte voor de regeleindes, roep de functie aan en knip af op het gemelde totaal; de vijf CRLF-paren zijn de prijs van MIME's regel van 76 kolommen. Voor PEM verwissel je de grens en het regeleinde, 64 kolommen en line_wrap::lf(), en dan heb je de lichaamstekst van de pantsering. Daarna voegt de pem-crate de banners toe in één aanroep:

let pem_block = pem::encode(&pem::Pem::new("CERTIFICATE", b"0123456789abcdef"));
println!("{pem_block}");
// -----BEGIN CERTIFICATE-----
// MDEyMzQ1Njc4OWFiY2RlZg==
// -----END CERTIFICATE-----
let back = pem::parse(pem_block).unwrap();
println!("{}: {} bytes", back.tag(), back.contents().len());
// CERTIFICATE: 16 bytes
// en Unix-stijl regeleindes, als de gebruiker kieskeurig is
let lf_block = pem::encode_config(
  &pem::Pem::new("KEY", b"0123456789abcdef"),
  pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
);

Standaard gebruikt pem::encode CRLF, de historische PEM-conventie; de set_line_ending-builder schakelt over naar LF voor de tools die dat verwachten. Let op wat de pem-crate niet doet: hij roept nooit een base64-functie aan die je kunt zien, want de codering is zijn interne zaak. Wanneer een formaat regels wil, is de architectuur één crate per klus.

Streaming in constante ruimte

Voor data te groot om in één variabele vast te houden, antwoordt de crate met dezelfde streaming-filosofie als de rest van Rust-z'n io: de write::EncoderWriter verpakt elke writer en base64-codeert alles wat je eraan schrijft, in constante ruimte. De volledige ceremonie voor een buffer ziet er zo uit, en de ster van de sectie is de finish()-aanroep:

use std::io::Write;
use base64::prelude::*;
use base64::write::EncoderWriter;
fn main() {
  let mut encoder = EncoderWriter::new(Vec::new(), &BASE64_STANDARD);
  encoder.write_all(b"the quick brown fox jumps over the lazy dog").unwrap();
  let packed = encoder.finish().unwrap();
  println!("{}", String::from_utf8(packed).unwrap());
  // dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==
}

Waarom is finish() de ster? Omdat het de ene aanroep is die de laatste gedeeltelijke groep doorstuurt en de padding toevoegt, en de encoder heeft een zustermethode die dat niet doet. De eigen documentatie van de crate zegt het recht uit: finish() "codeert overgebleven invoerbytes en voegt padding toe indien gepast. Het wordt automatisch aangeroepen bij deallocatie (zie de Drop-implementatie), maar elke fout die optreedt bij het aanroepen van de onderliggende writer wordt onderdrukt. Als je zulke fouten wilt afhandelen, roep dan finish() zelf aan." De Drop-implementatie gedraagt zich als BufWriter: zij stuurt door, maar negeert fouten tijdens het droppen. Dus de laatste gedeeltelijke groep gaat niet verloren, maar "het is wel eens gelukt" is geen versiestrategie, want de schrijffout die zij je zou hebben verteld, is weg.

Dezelfde stream werkt een niveau verder weg via io::copy als je de hele pipeline in één aanroep wilt, en er is een bonus-wrapper voor de "ik heb dit alleen maar nodig binnen een format-string"-momenten:

use std::io;
use base64::prelude::*;
use base64::write::EncoderWriter;
let file = b"the quick brown fox jumps over the lazy dog".to_vec();
let mut cursor = io::Cursor::new(file);
let mut encoder = EncoderWriter::new(Vec::new(), &BASE64_STANDARD);
io::copy(&mut cursor, &mut encoder).unwrap();
let packed = encoder.finish().unwrap();
println!("{}", String::from_utf8(packed).unwrap());
use base64::display::Base64Display;
use base64::prelude::*;
let value = Base64Display::new(b"\0\x01\x02\x03", &BASE64_STANDARD);
println!("base64: {value}");   // base64: AAECAw==

Die Base64Display-wrapper is een klein juweeltje: hij formatteert bytes als Base64 binnen elke format-string zonder een enkele heap-toewijzing, waardoor logregels en debug-uitvoer plotseling aangenaam worden.

Toewijzing, en het ontbreken ervan

De handige methode wijst toe, en voor het grootste deel van je leven is dat de juiste ruil. Maar de Engine-trait blootstelt drie smaken van encode, en de onderstaande tabel is de hele beslismatrix:

Methode Uitvoer Wijst toe
encode() een nieuwe String altijd
encode_string() voegt toe aan je String alleen als hij moet groeien
encode_slice() schrijft in je &[u8] nooit
use base64::prelude::*;
let input = b"Hello, world!";
let mut buf = vec![0u8; base64::encoded_len(input.len(), true).unwrap()];
let written = BASE64_STANDARD.encode_slice(input, &mut buf).unwrap();
buf.truncate(written);
println!("{}", std::str::from_utf8(&buf).unwrap());   // SGVsbG8sIHdvcmxkIQ==
// of houd de buffer helemaal op de stack
let mut stack = [0u8; 24];
let n = BASE64_STANDARD.encode_slice(b"abc 123", &mut stack).unwrap();
println!("{}", String::from_utf8(stack[..n].to_vec()).unwrap());  // YWJjIDEyMw==
// en als je de grootte fout inschatte, krijg je een fout, geen buffer-overflow
let mut tiny = [0u8; 5];
println!("{:?}", BASE64_STANDARD.encode_slice(input, &mut tiny));
// Err(OutputSliceTooSmall)

Dimensioneer de buffer met encoded_len(), schrijf met encode_slice(), en als je de grootte fout inschatte, krijg je een nette EncodeSliceError::OutputSliceTooSmall in plaats van ongedefinieerd gedrag, en dat is in een systementaal het verschil tussen een saaie middag en een lange. Voor ingebed werk bestaan dezelfde functies achter het alloc-feature, zodat je de API kunt houden en de heap kunt laten vallen.

Snelheid: de SIMD-engines

Versie 0.23.0, die in juli 2026 verscheen, bracht de feature mee die de koppen haalt: SIMD-geaccelereerde engines voor de standaard- en URL-veilige alfabetten. Er zijn er drie, en ze splitsen op hoezeer ze op je hardware vertrouwen:

Engine Detecteert op draaitijd Werkt in no_std
Simd ja, kiest AVX2 of NEON, valt terug op de scalaire engine nee, heeft std nodig voor detectie
Avx2 nee, gaat uit van een CPU met AVX2 ja, op x86_64-doelplatformen
Neon nee, gaat uit van een CPU met NEON ja, op aarch64-doelplatformen
use base64::engine::general_purpose::GeneralPurposeConfig;
use base64::engine::{Avx2, Simd};
use base64::Engine;
let turbo = Simd::standard(GeneralPurposeConfig::new());
println!("{}", turbo.encode("simd works!"));
// c2ltZCB3b3JrcyE=
if let Some(fixed) = Avx2::standard(GeneralPurposeConfig::new()) {
  println!("{}", fixed.encode("hello avx2"));  // aGVsbG8gYXZ4Mg==
}

De Simd-constructor doet zijn CPU-detectie één keer en geeft de beste kernel terug die hij vindt, of de scalaire engine als geen van toepassing is, dus bouw hem één keer in een const of bij het opstarten en hergebruik hem; op geschikte hardware is hij enkele keren sneller dan de scalaire route, zowel voor encoderen als decoderen. Een eerlijke voetnoot: de SIMD-route is de enige plek in de crate die unsafe aanraakt, en daarom heet het feature simd-unsafe. Schakel het feature uit en de hele crate is weer #![forbid(unsafe_code)], met de scalaire engine die eerlijk werk blijft doen. Als ruwe doorvoer het hele punt is, dan duwt de base64-turbo-crate de grens verder op, met pieken boven de 100 GiB/s dankzij AVX512-, AVX2- en NEON-kernels achter draaitijd-detectie, en een 100% veilige scalaire fallback op alles andere. De base64-crate is dubbel gelicenseerd onder MIT/Apache-2.0, dus dit alles is gratis, inclusief de snelheid.

Nog vier alfabetten

Het RFC-alfabet is de standaard, maar de base64-crate levert er nog vier mee, elk een klein monument voor een echt protocol dat zijn eigen wending nodig had:

Alfabet De wending Wie gebruikt het abc 123 codeert naar
alphabet::CRYPT ./ komen eerst, daarna cijfers en letters, geen padding klassieke Unix crypt(3)-wachtwoordhashes MK7X612mAk
alphabet::BCRYPT ./ eerst, daarna letters, daarna cijfers bcrypt-wachtwoordhashes WUHhGBCwKu
alphabet::IMAP_MUTF7 een komma vervingt de slash, geen padding de gewijzigde UTF-7-postvaknamen van IMAP YWJjIDEyMw
alphabet::BIN_HEX een alfabet zwaar in leestekens dat verwisselbare letters overslaat BinHex 4, de oude Macintosh-bestandsverpakker B@*M)$%b-`
use base64::engine::general_purpose::{GeneralPurpose, NO_PAD};
use base64::Engine;
let crypt = GeneralPurpose::new(&base64::alphabet::CRYPT, NO_PAD);
println!("{}", crypt.encode(b"abc 123"));   // MK7X612mAk
let bcrypt = GeneralPurpose::new(&base64::alphabet::BCRYPT, NO_PAD);
println!("{}", bcrypt.encode(b"abc 123"));  // WUHhGBCwKu
let imap = GeneralPurpose::new(&base64::alphabet::IMAP_MUTF7, NO_PAD);
println!("{}", imap.encode(b"abc 123"));    // YWJjIDEyMw

Zelfde invoer, drie verschillende uitvoeren, allemaal geldige Base64 in hun eigen dialect. Het crypt-alfabet is het enige met een echte superkracht: omdat de symbolen zo geordend zijn dat ze op de bitpatronen passen, geeft het sorteren van de gecodeerde strings dezelfde volgorde als het sorteren van de originele bytes, en daarom gebruikte GEDCOM 5.5 (1996) het voor multimediavelden - revisie 5.5.1 liet de feature vallen - en de crate levert het alfabet voor je nog steeds mee. En als het dialect dat je nodig hebt niet in de crate zit, kun je het definiëren met een string van 64 tekens, want Alphabet::new() bouwt de encode- en decode-tabellen voor je:

use base64::alphabet::Alphabet;
use base64::engine::general_purpose::{GeneralPurpose, PAD};
use base64::Engine;
// een bizarrewereld-base64: +/ vooraan in plaats van achteraan
let alphabet = Alphabet::new(
  "+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
).expect("a valid 64 char alphabet");
let bizarro = GeneralPurpose::new(&alphabet, PAD);
println!("{}", bizarro.encode(b"hello 99"));  // YETqZE6eMRi=
// terwijl de standaard-engine zegt:
println!("{}", base64::prelude::BASE64_STANDARD.encode(b"hello 99"));
// aGVsbG8gOTk=

Eén waarschuwing over de route van het op maat gemaakte alfabet: het moment dat je een dialect verzint, word je de enige persoon ter wereld die je data kan lezen, dus doe het alleen als een protocol het eist, en schrijf een commentaar waarin je aangeeft welk.

Waar encoders werken

Base64-encoderen duikt op in Rust-projecten in een voorspelbare bezetting van situaties:

  • Bestandsuploads in JSON-API's, waar het bestand een veld met bytes is in een tekstvermomming, bij veruit de meest voorkomende toepassing.
  • Data-URIs in HTML en CSS, het data:image/png;base64,...-type, geweldig voor kleine iconen, twijfelachtig voor hero-afbeeldingen.
  • JWTs en OAuth, waar base64url het dialect is en de jsonwebtoken-crate het gereedschap.
  • PEM-blokken voor certificaten en sleutels, de -----BEGIN CERTIFICATE------secties die Base64 omwikken op 64 tekens per regel.
  • Binair in XML- en config-bestanden, het <data encoding="base64">-patroon dat je nog steeds vindt in geëxporteerde bookmarks en instellingen-dumps.
  • LDAP- en LDIF-bestanden, die Base64 gebruiken om binaire attribuutwaarden op één regel te houden.
  • QR-code-payloads en overdrachten via het klembord, waar tekst de reis overleeft en binair niet.
  • HTTP Basic-auth-headers, waar Basic TWFuOnBhc3M= een aanmeldingsgegevensparen is, en een herinnering dat dit een inpakprobleem is, geen verstopprobleem.

En de gouden regel die over het geheel heerst: Base64 is pakband, geen slot. Het is geen versleuteling en geen compressie - het is het tegenovergestelde van compressie, en iedereen met dit artikel kan alles wat het doet in één regel omkeren. Codeer vrij, maar codeer nooit een wachtwoord, een API-sleutel of een geheim en noem het beschermd. Als het verborgen moet worden, gebruik dan echte versleuteling, en als het groot is, overweeg dan of een multipart-upload gewoon goedkoper was geweest dan de belasting.

Een decennium aan kleine stappen

Het formaat is ouder dan het web. In 1987 moest het Privacy-Enhanced Mail-protocol (RFC 989) binaire data over 7-bit mailkanalen vervoeren, en het standardiseerde deze codering met precies regels van 64 tekens. Elk -----BEGIN CERTIFICATE------blok op internet is een nakomeling van die beslissing, en daarom wikkelen PEM-bestanden vandaag nog steeds om op 64. In 1996 adopteerde de MIME-specificatie (RFC 2045) het schema, noemde het "base64" naar zijn alfabet van 64 tekens, en verplaatste de omwikkeling naar 76 tekens. Voor al dat, leverden Unix-boxen uuencode en Macs BinHex, elk met zijn eigen alfabet, en beide duiken nog steeds op in oude systemen als fossielen met bestandskoppen. In 2006 werd RFC 4648 de standaard die iedereen citeert, met de alfabettabellen, de base64url-variant, en de canonieke coderingsregels die elke engine in dit artikel implementeert. Sectie 3.5 daarvan vereist dat encoders de ongebruikte eindbits op nul zetten, en de crate doet dat; als je payload later de InvalidLastSymbol-check van een strikte decoder triggert, dan gebeurde de beschadiging stroomopwaarts.

De eigen geschiedenis van de crate rijmt. Het verscheen op crates.io in december 2015, en versie 0.5.0 voegde trots MIME-regelomwikkeling met instelbare regeleindes toe. Daarna verwijderde versie 0.10.0 in 2018 de omwikkeling en de witruimte-behandeling; de bibliotheek besloot dat een allround crate moet coderen en de poëzie overlaat aan de applicatielaag. Dezelfde release voegde de streaming-EncoderWriter toe. Versie 0.20.0 in 2022 introduceerde de engine-abstractie en maakte canonieke padding de standaard, en 0.21.0 merkte de oude vrije functies als verouderd, ten gunste van engine-methodes, met de compilernota "Use Engine::encode" (die werken nog steeds, en daarom compileert veel bestaande code zonder morren). In 2024 slijpte versie 0.22.0 de foutsemantiek scherper en maakte decoderen 5 tot 10 procent sneller. En in juli 2026 arriveerde versie 0.23.0 met de SIMD-engines, op maat gemaakte padding-symbolen, een duidelijker foutbericht en de MSRV-stijging naar 1.71, met de 0.23.1-patch op 4 augustus die de test-suite voor niet-SIMD-architecturen fixeert.

Dingen om te lachen om

Want een volledige gids moet eindigen met een glimlach:

  • Het woord "base64" codeert naar YmFzZTY0. Een formaat dat zichzelf beschrijft, is de technische equivalent van een spiegel die in Morse praat.
  • De lege string codeert naar de lege string. Niets is de enige invoer die niets kost, en dat is een soort belastingvrijstelling.
  • AA== is niet de codering van niets; het is de codering van één NUL-byte. In Base64 zijn "niets" en "een nul" verschillende wezens, en decoders zien het verschil.
  • Elke Base64-gecodeerde PNG begint met iVBORw0K. Dat is het PNG-magische getal in zijn pakband, een van de meest herkenbare prefixen op internet.
  • In een URL hebben standaard Base64-tekens ontsnappingskostuums nodig: plus wordt %2B, slash wordt %2F, en padding wordt %3D. Base64url bestaat zodat de tekens hun eigen gezichten kunnen dragen.
  • YouTube-video-ID's zijn base64url zonder padding: acht bytes ID worden de string van elf tekens die je overal kunt plakken. Een van de meest zichtbare toepassingen van de modus zonder padding op het hele internet.
  • Het oude crypt(3)-wachtwoordalfabet sorteert correct: gesorteerde gecodeerde strings staan in dezelfde volgorde als gesorteerde ruwe tekst. GEDCOM 5.5 (1996) gebruikte dat alfabet voor zijn multimediavelden, revisie 5.5.1 liet de feature vallen, en de crate levert het voor je nog steeds mee.
  • BinHex, de oude Macintosh-verpakker, bouwde zijn alfabet om visueel verwisselbare tekens uit te sluiten zoals 7, O, g en o. Een encoder ontworpen voor menselijke ogen, in een wereld vóór de spellingcontrole.
  • De eigen FAQ van de crate is rechtuit over de padding: exabytes aan opslag en overdracht zijn ongetwijfeld verspild aan nutteloze =-bytes. Het tolhuis heft al sinds 1987.
  • Base64 is geen versleuteling. Anders zou je de uitvoer van geen enkel voorbeeld in dit artikel kunnen lezen. Het is een stoel bij het raam, geen kluis.

De korte versie

Kies je engine naar de route die de data zal afleggen: BASE64_STANDARD voor alles wat je zelf ook decodeert, de _NO_PAD-engines wanneer je beide uiteinden onder controle hebt en de bytes terug wilt, URL_SAFE_NO_PAD voor tokens en URLs, en een op maat gemaakte Alphabet alleen als een protocol erop aandringt. Dimensioneer je buffers met encoded_len(), stream de grote dingen door EncoderWriter en sluit altijd af met finish(), wikkel regels met line-wrap en pem alleen wanneer een formaat het eist, laat de SIMD-engines het zware werk doen wanneer je kunt, en onthoud dat de ruil van vier voor drie de prijs is van het door de uitsluitend-tekst-deur heen komen. Codeer alles, bescherm alleen wat een echt slot nodig heeft. En als je de andere richting op moet, een string uitpakken naar de bytes die de reis begonnen, dan dekt het zusterartikel decoderen in Rust, compleet met de volledige scorelijst van exacte foutberichten.

Laatst bijgewerkt: 2026-10-06

Gerelateerd artikel: Base64-decodering in Rust: een complete gids