Base64-Kodierung in Rust: Ein vollständiger Leitfaden
Sie sind gerade dabei, einige Bytes durch eine nur für Text geöffnete Tür zu schicken, und der Eintrittspreis ist ein String aus Buchstaben, Ziffern, Pluszeichen und Schrägstrichen, der rund ein Drittel länger ist als das, womit Sie angefangen haben. Willkommen bei Base64, der Mautstelle des Internets. Die Startseite dieser Site erklärt das Format in voller Tiefe, also muss hier nur noch die Form wiederholt werden: Base64 schreibt drei Eingabe-Bytes als vier Zeichen aus einem 64-Symbol-Alphabet, und ein Schwanz aus einem oder zwei =-Zeichen sagt dem Leser, wo die echten Daten zu Ende waren. Dieser Vier-gegen-drei-Tausch ist die gesamte Wirtschaft des Formats, und dieser Leitfaden geht darum, ihn gut in Rust zu machen.
Das Erste, was man wissen muss: Die Rust-Standardbibliothek macht es nicht für Sie. Es gibt kein base64_encode(), das in std versteckt ist, und kein use std::..., das Sie umstimmt. Das Ökosystem hat sich auf eine einzige Crate mit dem simplen Namen base64 geeinigt, und sie trägt Last: Version 0.23.1 erschien am 4. August 2026, die Crate hat seit Dezember 2015 45 Versionen veröffentlicht, und ihr Download-Zähler steht bei knapp 1,5 Milliarden. Jedes Kodier-Beispiel unten nutzt diese eine Crate, plus zwei kleine Begleiter für Zeilen-Umbruch und PEM-Rüstung.
Die Toolchain und die Crate
Zuerst die Toolchain, ein Befehl pro Welt:
# Debian / Ubuntu
sudo apt install rustc cargo
# oder der offizielle Installer, der rustup und cargo einrichtet
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Dann die Crate, in jedem cargo-Projekt:
cargo new my-app
cd my-app
cargo add base64
Diese eine Zeile ist die gesamte Installation, und sie zieht genau null Abhängigkeiten nach. Die Crate liefert drei optionale Features, die Sie kennen sollten: std (standardmäßig an; gibt Ihnen std::io-Streaming, die Standard-Implementierungen von Error und Heap-Zuweisung), alloc (die allozierenden APIs für eingebettete no_std-Builds) und simd-unsafe (standardmäßig an; die SIMD-Engines, die ein paar Sektionen weiter unten auftauchen). Die minimal unterstützte Rust-Version ist 1.71.0, also läuft es mit allem aktuellen. Rundherum sitzen die Begleiter für Jobs, die der Kern bewusst nicht macht:
- line-wrap (Version 0.2) setzt die 76- oder 64-Zeichen-Zeilenumbrüche, die MIME und PEM verlangen; die
base64-Crate selbst weigert sich zu umbrechen, bewusst, wie Sie gleich sehen werden. - pem (Version 4) baut und parst
-----BEGIN ...------Blöcke für Zertifikate und Schlüssel; sie hängt intern vonbase64ab und fügt die Rüstung und den Umbruch hinzu. - base64ct (Version 1.8) ist der Constant-Time-Decoder vom RustCrypto-Projekt, für Fälle, in denen die Lesen-Seite eines Round Trips der sensible Teil ist.
- base64-turbo (Version 0.3) ist ein neuerer High-Throughput-Codec, der auf moderner Hardware Spitzen über 100 GiB/s erreicht.
Ein Encode, vier Zeichen
Die kleinstmögliche Zeremonie sieht so aus, und sie beweist schon den ganzen Round Trip:
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!
}
Zwei Dinge verdienen Aufmerksamkeit. Das prelude-Modul reicht Ihnen leise zwei Dinge auf einmal: die BASE64_STANDARD-Engine und den Engine-Trait, dessen Methoden Sie aufrufen, weshalb ein nacktes use base64::prelude::*; alles ist, was dieses Beispiel braucht. Und encode() nimmt alles, was als Bytes gelesen werden kann, dank der AsRef<[u8]>-Bound: ein &str, ein &[u8]-Literal, ein Vec<u8>, Sie sagen es. Die Dekodier-Hälfte des Beispiels ist nur da, um den Kodierer im Auge zu behalten, denn die andere Richtung bekommt ihren eigenen vollständigen Leitfaden auf der Schwester-Site. Wenn Sie den Smoke-Test der Crate selbst wollen: Ihre Dokumentation kodiert asdf und bekommt YXNkZg== zurück; gleiches Alphabet, gleiche Rechnung.
Der genaue Preis jedes Bytes
Jeder existierende Base64-Kodierer berechnet dieselbe Steuer, und wenn man die Rechnung sehen kann, kann man sie einkalkulieren. Jedes Ausgabe-Zeichen trägt 6 Bits, jedes Eingabe-Byte 8, und der kleinste Haufen, der beides ist, sind 24 Bits: exakt 3 Bytes rein, exakt 4 Zeichen raus. Dieses Verhältnis ist die ganze Show, also wird eine 3-Kilobyte-Datei zu 4 Kilobytes und ein 10-Megabyte-Upload zu 13,3. Das Padding ist der Rundungsfehler in sichtbarer Form: Wenn die Eingabe kein Vielfaches von 3 Bytes ist, hat die letzte Gruppe freie Kapazität, und der Kodierer füllt sie mit =, damit die Ausgabelänge ein Vielfaches von 4 bleibt. Hier ist die Wahrheitstabelle aus RFC 4648, die die Standard-Engine exakt reproduziert:
| Eingabe | Länge mod 3 | Kodiert | Ausgabelänge |
|---|---|---|---|
"" (leer) |
0 | "" (leer) |
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"
Lesen Sie die erste Zeile doppelt, denn das ist die, die jeder im Kopf falsch macht: Die leere Eingabe kodiert zum leeren String, nicht zu AA==. Der String AA== ist die Kodierung von exakt einem Byte, einem NUL, was ein wirklich anderes Payload ist. Und wenn Sie einen Buffer dimensionieren müssen, bevor Sie kodieren, gibt die Crate Ihnen die Rechnung als const fn, so können Sie selbst Arrays zur Kompilierzeit dimensionieren:
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)
Achten Sie auf die 13- und 14-Byte-Zeilen, denn sie sind die, die die Daumenrechnung aus dem Gleis bringen: 13 Bytes brauchen 18 Zeichen ohne Padding, aber 20 mit, während 14 Bytes 19 und 20 brauchen. Die Funktion gibt ein Option zurück, das nur dann None ist, wenn die Längenrechnung überlaufen würde, so ist ein unwrap() sicher für jede Eingabe, die tatsächlich im Speicher existieren könnte. In der E-Mail-geformten Welt hat die Steuer einen Zuschlag: MIME bricht Zeilen bei 76 Zeichen um, und die alte Daumenregel sagt, dass umgebrochenes Base64 etwa 1,37-mal die ursprüngliche Größe kostet, plus den Overhead der Header. Die eigene FAQ der Crate hat eine weniger höfliche Meinung über das Padding selbst: Die =-Bytes "beeinflussen das Dekodieren nicht, abgesehen davon, dass sie die Gelegenheit bieten zu sagen 'das Padding ist falsch'", und "Exabyte an Speicher und Übertragung sind ohne Zweifel für sinnlose =-Bytes verschwendet worden".
Padding: Eine Entscheidung über den Leser
In base64 0.23 sind die nackten encode-Funktionen deprecated - der aktuelle Weg ist, eine Methode auf einer Engine aufzurufen, und eine Engine ist eine Politik: welches Alphabet geschrieben wird und welches Padding hinzugefügt wird. Die Presets leben in base64::engine::general_purpose, die vier beliebtesten davon sind in das prelude neu exportiert:
| Engine | Alphabet | Fügt Padding hinzu | Am besten für |
|---|---|---|---|
STANDARD / BASE64_STANDARD |
+ / |
ja | alles, der Standard |
STANDARD_NO_PAD / BASE64_STANDARD_NO_PAD |
+ / |
nein | schmale Payloads, die Sie auch selbst konsumieren |
URL_SAFE / BASE64_URL_SAFE |
- _ |
ja | URL-Inhalt, der trotzdem noch Padding will |
URL_SAFE_NO_PAD / BASE64_URL_SAFE_NO_PAD |
- _ |
nein | JWTs, URLs, Objekt-IDs |
Kodieren ohne Padding ist kein Hack, den die Crate toleriert; es ist eine erstklassige Lösung, mit vorkonfigurierten NO_PAD- und PAD-Konfigurationskonstanten neben den in 0.23.0 hinzugefügten *_INDIFFERENT-Brüdern. Wenn die Presets nicht passen, bauen Sie Ihre eigene Engine aus einem Alphabet und einem GeneralPurposeConfig mit einem einzigen Regler, und weil Engines günstig zu konstruieren sind, speichern Sie das Ergebnis in einer const statt sie pro Anfrage neu zu bauen:
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=
}
Jetzt wird die Entscheidung zu einer Entscheidung über die Decoder anderer Leute, die nie rein ästhetisch ist. Die Strenge-Regeln auf der Dekodier-Seite kommen aus DecodePaddingMode, und die Tabelle unten beantwortet "Kann die andere Seite lesen, was ich geschrieben habe?":
| Sie kodieren mit | Ein strenger STANDARD-Decoder |
Ein STANDARD_NO_PAD-Decoder |
Ein INDIFFERENT-Decoder |
|---|---|---|---|
STANDARD (gepaddet) |
liest es | lehnt das = ab |
liest es |
STANDARD_NO_PAD |
lehnt ab: Padding fehlt | liest es | liest es |
URL_SAFE_NO_PAD |
lehnt ab: falsches Alphabet | lehnt ab: falsches Alphabet | liest es nur mit dem URL-Alphabet |
Die praktischen Regeln ergeben sich aus der Tabelle. Wenn Sie beide Enden kontrollieren, wählen Sie eine Engine und verwenden Sie sie überall, und bevorzugen Sie kein Padding, um Bytes zu sparen. Wenn Sie Daten aus der Außenwelt konsumieren, bekommt Ihr Decoder ein Stimmrecht dafür, welche Engine Sie ausgeben sollen: Ein serienmäßiger STANDARD-Decoder braucht Ihr Padding, während ein STANDARD_PAD_INDIFFERENT-Decoder beides akzeptiert. Und die Wahl hat auch einen Sicherheitsgeschmack. Beide Schreibweisen desselben Payloads zu erlauben, mit Padding und ohne, macht Base64 formbar; das 2022er-Paper "Base64-Formbarkeit in der Praxis" (Chatzigiannis und Chalkias, ePrint 2022/361), auf das die eigene Dokumentation der Crate verlinkt, zeigt warum. Ein Protokoll, in dem dieselben Daten auf zwei verschiedene Weisen geschrieben werden können, hat die Angewohnheit, den Code zu überraschen, der den kodierten String als Identität behandelt, also wenn Ihr Format eine kanonische Schreibweise definiert, durchsetzen Sie sie an der Grenze.
Base64url für Tokens und Links
Die letzten zwei Alphabet-Buchstaben des Standard-Base64 sind + und /, und in einer URL sind das zwei der teuersten Zeichen der Sprache: plus wird zu %2B, Schrägstrich wird zu %2F, und Padding wird zu %3D. Abschnitt 5 von RFC 4648 behebt das mit dem URL- und Dateinamen-sicheren Alphabet, das die beiden Störenfriede durch - und _ ersetzt und meistens auch das Padding weglässt. Die Engines machen den Unterschied unmöglich zu übersehen:
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]
Drei Bytes des hässlichsten möglichen Inputs werden zu einem 4-Zeichen-String, den Sie in eine URL, einen Dateinamen, ein Cookie oder einen Datenbank-Schlüssel einfügen können, ohne ein einziges Percent-Escape. Das ist das Alphabet, in dem JSON Web Tokens leben: Ein JWT ist drei base64url-Teile, die durch Punkte verbunden sind, und eines mit der jsonwebtoken-Crate (Version 11 im Jahr 2026) zu prägen, sieht so aus:
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, // gut in der Zukunft
};
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(key)).unwrap();
println!("{token}");
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiQGIuY29t...
Version 11 hat eine Setup-Voraussetzung, die Einsteiger beißt: Die Crate braucht genau eines der Features rust_crypto oder aws_lc_rs, aktiviert in Cargo.toml, und wenn keins an ist, panikt sie das erste Mal, wenn Sie ein Token signieren oder verifizieren. Achten Sie auf den exp-Claim im Struct: Die Validierung der Crate behandelt ihn standardmäßig als erforderlich, so dass echte Tokens einen haben, und das base64url-Alphabet im Token ist ganz alleine das Geschäft der Library. Wenn Sie nur Tokens inspizieren und keine prägen, zeigt der Schwestern-Artikel den Fünf-Zeilen-Blick. Und eine Erinnerung an die goldene Regel, die bei Tokens mit besonderer Kraft gilt: Die drei Teile eines JWT sind alle lesbar ohne einen Schlüssel. Base64 ist ein Fensterplatz, kein Schloss.
Text rein, Bytes raus
Kodierer lesen keine Gedanken, also bedeutet "kodiere diesen String" in Rust immer "kodiere die UTF-8-Bytes dieses Strings". Das ist es, was str::as_bytes() hergibt. Die gute Nachricht: Das moderne Web ist fast vollständig UTF-8, also ist der ehrliche Weg kurz und fröhlich:
use base64::prelude::*;
let text = "café";
let packed = BASE64_STANDARD.encode(text.as_bytes());
println!("{packed}"); // Y2Fmw6k=
Alle Multi-Byte-Fälle funktionieren:
| Originaltext | Base64 | Round Trip |
|---|---|---|
café |
Y2Fmw6k= |
sauber |
日本語 |
5pel5pys6Kqe |
sauber |
😀 |
8J+YgA== |
sauber |
π ≈ 3.14159 |
z4Ag4omIIDMuMTQxNTk= |
sauber |
Die einzige echte Entscheidung ist, von welchen Bytes Sie starten. Wenn die Daten als Bytes ankommen und nicht als Text, eine Datei, die von der Platte gelesen wird, oder ein Buffer aus einem Netzwerk-Call, überspringen Sie den String ganz und kodieren Sie das Vec<u8> direkt. Das ist auch die einzige richtige Antwort für nicht-UTF-8-Payloads wie PNGs oder Protobufs. Legen Sie ein beliebiges Bild neben Ihren Code und zeigen Sie das Lesen darauf:
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());
// jedes kodierte PNG beginnt mit iVBORw0K
assert!(packed.starts_with("iVBORw0K"));
Die letzte Assertion ist ein kostenloser Sanity-Check und eines der wiedererkennbarsten Präfixe im Internet. Und wenn Sie denselben logischen Text je durch zwei verschiedene Zeichensätze kodieren, oder Bytes kodieren, die Sie als einen anderen Zeichensatz missverstanden haben, kommt der Round Trip mit geradem Gesicht als Mojibake zurück. Der Kodierer lügt nie. Er kodiert einfach die Bytes, die Sie ihm geben, was sowohl seine größte Stärke als auch seine einzige Falle ist.
Wenn ein Format Zeilen will
Die base64-Crate fügt bewusst keine Zeilenumbrüche ein, und es ist nicht das erste Mal, dass sie diese Entscheidung trifft. Version 0.5.0 lieferte eingebauten MIME-Zeilen-Umbruch mit konfigurierbaren Zeilenenden, und Version 0.10.0 entfernte ihn, weil die Library entschied, dass Umbruch für eine allgemeine Crate zu meinungsstark sei und die no_std-Geschichte verkompliziere. Wenn ein Format Zeilen verlangt, existiert die line-wrap-Crate genau dafür. Ihre eine Funktion, line_wrap(), nimmt Ihren vorallokierten Buffer, die Eingabelänge, die Spaltenbegrenzung und das Zeilenende, und gibt die Anzahl der eingefügten Zeilenende-Bytes zurück:
use base64::prelude::*;
let data = BASE64_STANDARD.encode(vec![b'a'; 300]); // 400 Zeichen
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 (fünf CRLF-Paare)
Dimensionieren Sie den Buffer vorher mit Platz für die Enden, rufen Sie die Funktion auf und kürzen Sie auf den gemeldeten Gesamtwert. Die fünf CRLF-Paare sind der Preis für MIMEs 76-Spalten-Regel. Für PEM tauschen Sie die Begrenzung und das Ende, 64 Spalten und line_wrap::lf(), und Sie haben den Rüstungskörper. Dann fügt die pem-Crate die Banner mit einem einzigen Aufruf hinzu:
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
// und Unix-Stil-Zeilenenden, falls der Konsument pingelig ist
let lf_block = pem::encode_config(
&pem::Pem::new("KEY", b"0123456789abcdef"),
pem::EncodeConfig::new().set_line_ending(pem::LineEnding::LF),
);
Standardmäßig verwendet pem::encode CRLF, die historische PEM-Konvention. Der set_line_ending-Builder schaltet auf LF um für die Tools, die es erwarten. Beachten Sie, was die pem-Crate nicht tut: Sie ruft nie eine Base64-Funktion auf, die Sie sehen könnten, denn die Kodierung ist ihre interne Angelegenheit. Wenn ein Format Zeilen will, ist die Architektur eine Crate pro Job.
Streaming in konstantem Speicher
Für Daten, die zu groß sind, um in eine einzige Variable zu passen, antwortet die Crate mit derselben Streaming-Philosophie wie der Rest des io-Moduls von Rust: write::EncoderWriter umhüllt jeden Writer und kodiert alles, was Sie ihm schreiben, in konstantem Speicher Base64. Die komplette Zeremonie für einen Buffer sieht so aus, und der Star dieser Sektion ist der finish()-Aufruf:
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==
}
Warum ist finish() der Star? Weil es der eine Aufruf ist, der die letzte partielle Gruppe flushen und das Padding hinzufügt, und der Encoder hat eine Geschwister-Methode, die das nicht tut. Die eigene Dokumentation der Crate sagt es offen: finish() "kodiert alle übrigen Eingabe-Bytes und fügt Padding hinzu, wenn angemessen. Es wird automatisch aufgerufen, wenn deallociert (siehe die Drop-Implementierung), aber jeder Fehler, der beim Aufruf des darunterliegenden Writers auftritt, wird unterdrückt. Wenn Sie solche Fehler behandeln wollen, rufen Sie finish() selbst auf." Die Drop-Implementierung verhält sich wie BufWriter: Sie flushen, aber sie ignoriert Fehler beim Drop. Also geht die letzte partielle Gruppe nicht verloren, aber "es hat wahrscheinlich funktioniert" ist keine Shipping-Strategie, denn der Schreibfehler, von dem es Ihnen hätte erzählen können, ist weg.
Derselbe Stream funktioniert eine Ebene weiter entfernt über io::copy, wenn Sie die ganze Pipeline in einem Aufruf wollen, und es gibt einen Bonus-Wrapper für die Momente, in denen Sie es nur in einem Format-String brauchen:
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==
Dieser Base64Display-Wrapper ist ein kleines Juwel: Er formatiert Bytes als Base64 in jedem Format-String ohne eine einzige Heap-Allokation, was Log-Zeilen und Debug-Ausgabe auf einmal angenehm macht.
Allokation, und ihre Abwesenheit
Die bequeme Methode allokiert, und für den Großteil Ihres Lebens ist das der richtige Handel. Aber der Engine-Trait bietet drei Varianten von encode, und die Tabelle unten ist die ganze Entscheidungs-Matrix:
| Methode | Ausgabe | Allokiert |
|---|---|---|
encode() |
ein neuer String |
immer |
encode_string() |
hängt an Ihren String an |
nur, wenn er wachsen muss |
encode_slice() |
schreibt in Ihr &[u8] |
nie |
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==
// oder halten Sie den Buffer ganz auf dem 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==
// und wenn Sie die Größe falsch gemessen haben, bekommen Sie einen Fehler, keinen Buffer-Overflow
let mut tiny = [0u8; 5];
println!("{:?}", BASE64_STANDARD.encode_slice(input, &mut tiny));
// Err(OutputSliceTooSmall)
Dimensionieren Sie den Buffer mit encoded_len(), schreiben Sie mit encode_slice(), und wenn Sie die Größe verfehlt haben, bekommen Sie einen sauberen EncodeSliceError::OutputSliceTooSmall statt undefiniertem Verhalten, was in einer System-Sprache der Unterschied zwischen einem langweiligen Nachmittag und einem langen ist. Für eingebettete Arbeit existieren dieselben Funktionen hinter dem alloc-Feature, so können Sie die API behalten und den Heap fallen lassen.
Geschwindigkeit: Die SIMD-Engines
Version 0.23.0, die im Juli 2026 erschien, brachte das Hauptfeature: SIMD-beschleunigte Engines für die Standard- und URL-sicheren Alphabete. Es gibt drei davon, und sie unterscheiden sich danach, wie sehr sie Ihrer Hardware trauen:
| Engine | Erkennt zur Laufzeit | Funktioniert in no_std |
|---|---|---|
Simd |
ja, wählt AVX2 oder NEON, fällt auf die skalare Engine zurück | nein, braucht std für die Erkennung |
Avx2 |
nein, nimmt an, die CPU hat AVX2 | ja, auf x86_64-Zielen |
Neon |
nein, nimmt an, die CPU hat NEON | ja, auf aarch64-Zielen |
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==
}
Der Simd-Konstruktor macht seine CPU-Erkennung einmal und gibt den besten Kernel zurück, den er findet, oder die skalare Engine, wenn keiner zutrifft, also bauen Sie ihn einmal in einer const oder beim Start und wiederverwenden Sie ihn; auf fähiger Hardware ist er mehrere Male schneller als der skalare Pfad, sowohl beim Kodieren als auch beim Dekodieren. Eine ehrliche Fußnote: Der SIMD-Pfad ist der einzige Ort in der Crate, der unsafe berührt, weshalb das Feature simd-unsafe heißt. Schalten Sie das Feature aus, und die ganze Crate ist wieder #![forbid(unsafe_code)], wobei die skalare Engine weiter ehrliche Arbeit macht. Wenn roher Durchsatz der ganze Punkt ist, schiebt die base64-turbo-Crate die Grenzen weiter hinaus, mit Spitzen über 100 GiB/s durch AVX512-, AVX2- und NEON-Kernels hinter der Laufzeit-Erkennung und einem 100% sicheren skalaren Fallback auf allem anderen. Die base64-Crate ist dual lizenziert unter MIT/Apache-2.0, also ist all das kostenlos, einschließlich der Geschwindigkeit.
Vier Alphabete mehr
Das RFC-Alphabet ist der Standard, aber die base64-Crate liefert vier weitere, jedes ein kleines Denkmal für ein reales Protokoll, das seinen eigenen Twist brauchte:
| Alphabet | Der Twist | Wer es benutzt | abc 123 kodiert zu |
|---|---|---|---|
alphabet::CRYPT |
./ zuerst, dann Ziffern und Buchstaben, kein Padding |
klassische Unix-crypt(3)-Passwort-Hashes | MK7X612mAk |
alphabet::BCRYPT |
./ zuerst, dann Buchstaben, dann Ziffern |
bcrypt-Passwort-Hashes | WUHhGBCwKu |
alphabet::IMAP_MUTF7 |
ein Komma steht für den Schrägstrich, kein Padding | IMAPs modifizierte UTF-7-Postfachnamen | YWJjIDEyMw |
alphabet::BIN_HEX |
ein Interpunktion-lastiges Alphabet, das verwechselbare Buchstaben überspringt | BinHex 4, der alte Macintosh-Datei-Wrapper | 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
Derselbe Input, drei verschiedene Ausgaben, alles gültiges Base64 in seinem eigenen Dialekt. Das crypt-Alphabet ist das mit der echten Superkraft: Weil seine Symbole so geordnet sind, dass sie den Bit-Mustern entsprechen, ergibt das Sortieren der kodierten Strings dieselbe Reihenfolge wie das Sortieren der Original-Bytes, weshalb GEDCOM 5.5 (1996) es für Multimedia-Felder verwendete - die 5.5.1-Revision entfernte das Feature - und die Crate liefert Ihnen das Alphabet immer noch. Und wenn der Dialekt, den Sie brauchen, nicht in der Crate ist, können Sie ihn mit einem 64-Zeichen-String definieren, denn Alphabet::new() baut die Kodier- und Dekodier-Tabellen für Sie:
use base64::alphabet::Alphabet;
use base64::engine::general_purpose::{GeneralPurpose, PAD};
use base64::Engine;
// ein bizarro-Welt-base64: +/ vorne statt am Ende
let alphabet = Alphabet::new(
"+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
).expect("a valid 64 char alphabet");
let bizarro = GeneralPurpose::new(&alphabet, PAD);
println!("{}", bizarro.encode(b"hello 99")); // YETqZE6eMRi=
// während die Standard-Engine sagt:
println!("{}", base64::prelude::BASE64_STANDARD.encode(b"hello 99"));
// aGVsbG8gOTk=
Eine Warnung zum Weg des eigenen Alphabets: Im Moment, in dem Sie einen Dialekt erfinden, werden Sie die einzige Person auf der Erde, die Ihre Daten lesen kann, tun Sie es also nur, wenn ein Protokoll es verlangt, und schreiben Sie einen Kommentar, welches es ist.
Wo Kodierer arbeiten
Base64-Kodierung taucht in Rust-Projekten in einer vorhersagbaren Besetzung von Situationen auf:
- Datei-Uploads in JSON-APIs, wo die Datei ein Byte-Feld in einem Text-Kostüm ist, die mit Abstand häufigste Nutzung.
- Data URIs in HTML und CSS, die
data:image/png;base64,...-Art, wunderbar für winzige Icons, zweifelhaft für Hero-Bilder. - JWTs und OAuth, wo base64url der Dialekt und die
jsonwebtoken-Crate das Werkzeug ist. - PEM-Blöcke für Zertifikate und Schlüssel, die
-----BEGIN CERTIFICATE------Sektionen, die Base64 bei 64 Zeichen pro Zeile umbrechen. - Binär in XML und Config-Dateien, das
<data encoding="base64">-Muster, das Sie immer noch in exportierten Lesezeichen und Settings-Dumps finden. - LDAP- und LDIF-Dateien, die Base64 verwenden, um binäre Attributwerte auf einer Zeile zu halten.
- QR-Code-Payloads und Zwischenablage-Übergaben, wo Text die Reise überlebt und Binär nicht.
- HTTP-Basic-Auth-Header, wo
Basic TWFuOnBhc3M=ein Credential-Paar ist, und eine Erinnerung daran, dass dies ein Packen-Problem ist, kein Verstecken-Problem.
Und die goldene Regel, die all das regelt: Base64 ist Paketband, kein Schloss. Es ist keine Verschlüsselung und keine Kompression - es ist das Gegenteil von Kompression - und jeder mit diesem Artikel kann alles, was es tut, in einer Zeile rückgängig machen. Kodieren Sie frei, aber kodieren Sie nie ein Passwort, einen API-Schlüssel oder ein Geheimnis und bezeichnen Sie es als geschützt. Wenn es versteckt werden muss, nutzen Sie echte Verschlüsselung, und wenn es groß ist, überlegen Sie, ob ein multipart-Upload einfach billiger gewesen wäre als die Steuer.
Ein Jahrzehnt kleiner Schritte
Das Format ist älter als das Web. 1987 musste das Privacy-Enhanced-Mail-Protokoll (RFC 989) binäre Daten über 7-Bit-Mail-Kanäle tragen, und es standardisierte diese Kodierung mit exakt 64-Zeichen-Zeilen. Jeder -----BEGIN CERTIFICATE------Block im Internet ist ein Nachfahre dieser Entscheidung, und deshalb brechen PEM-Dateien heute noch bei 64 Zeichen um. 1996 übernahm die MIME-Spezifikation (RFC 2045) das Verfahren, nannte es nach seinem 64-Zeichen-Alphabet "base64" und verlegte den Umbruch auf 76 Zeichen. Vor all dem lieferten Unix-Rechner uuencode und Macs BinHex, jedes mit seinem eigenen Alphabet, und beide tauchen immer noch in alten Systemen auf wie Fossilien mit Dateikopf. 2006 wurde RFC 4648 der Standard, den jeder zitiert, mit seinen Alphabet-Tabellen, der base64url-Variante und den kanonischen Kodier-Regeln, die jede Engine in diesem Artikel umsetzt. Sein Abschnitt 3.5 verlangt, dass Kodierer die ungenutzten Nachlauf-Bits auf null setzen, und die Crate tut das. Wenn Ihr Payload später die InvalidLastSymbol-Prüfung eines strengen Decoders auslöst, passierte die Korruption stromaufwärts.
Die eigene Geschichte der Crate reimt sich. Sie erschien im Dezember 2015 auf crates.io, und Version 0.5.0 fügte stolz den MIME-Zeilen-Umbruch mit konfigurierbaren Zeilenenden hinzu. Dann entfernte Version 0.10.0 im Jahr 2018 den Umbruch und die Leerzeichen-Verarbeitung, die Library entschied, dass eine Allzweck-Crate kodieren sollte und die Poesie der Anwendungsschicht überlässt; dasselbe Release fügte den Streaming-EncoderWriter hinzu. Version 0.20.0 im Jahr 2022 führte die Engine-Abstraktion ein und machte kanonisches Padding zum Standard, und 0.21.0 deklarierte die alten Free-Functions zugunsten der Engine-Methoden als deprecated, mit dem Compiler-Hinweis "Verwenden Sie Engine::encode" (sie funktionieren immer noch, weshalb viel Legacy-Code fröhlich kompiliert). Im Jahr 2024 schärfte Version 0.22.0 die Fehler-Semantik und beschleunigte das Dekodieren um 5 bis 10 Prozent. Und im Juli 2026 kam Version 0.23.0 mit den SIMD-Engines, benutzerdefinierten Padding-Symbolen, einer klareren Fehler-Nachricht und dem MSRV-Anstieg auf 1.71, und der 0.23.1-Patch am 4. August fixte die Test-Suite für Nicht-SIMD-Architekturen.
Sachen, die zum Lächeln laden
Denn ein vollständiger Leitfaden sollte mit einem Lächeln enden:
- Das Wort "base64" kodiert zu
YmFzZTY0. Ein Format, das sich selbst beschreibt, ist das technische Äquivalent eines Spiegels, der in Morse spricht. - Der leere String kodiert zum leeren String. Nichts ist die einzige Eingabe, die nichts kostet, was eine Art Steuerbefreiung ist.
AA==ist nicht die Kodierung von nichts; es ist die Kodierung von einem NUL-Byte. In Base64 sind "nichts" und "eine Null" verschiedene Geschöpfe, und Decoder unterscheiden sie.- Jedes Base64-kodierte PNG beginnt mit
iVBORw0K. Das ist die magische Zahl des PNG in seinem Paketband, eines der wiedererkennbarsten Präfixe im Internet. - In einer URL brauchen Standard-Base64-Zeichen Escape-Kostüme: plus wird zu
%2B, Schrägstrich wird zu%2F, und Padding wird zu%3D. Base64url existiert, damit die Zeichen ihre eigenen Gesichter tragen können. - YouTube-Video-IDs sind base64url ohne Padding: Acht Bytes ID werden zum elfstelligen String, den Sie überall einfügen können. Eine der sichtbarsten Nutzungen des No-Padding-Modus im gesamten Internet.
- Das alte crypt(3)-Passwort-Alphabet sortiert korrekt: sortierte kodierte Strings stehen in derselben Reihenfolge wie sortierter Klartext. GEDCOM 5.5 (1996) verwendete dieses Alphabet für seine Multimedia-Felder, die 5.5.1-Revision entfernte das Feature, und die Crate liefert es immer noch für Sie.
- BinHex, der alte Macintosh-Wrapper, baute sein Alphabet so, dass es visuell verwechselbare Zeichen wie
7,O,gundoausschließt. Ein für menschliche Augen entworfener Kodierer, in einer Welt vor der Rechtschreibprüfung. - Die eigene FAQ der Crate ist direkt über das Padding: Exabyte an Speicher und Übertragung sind ohne Zweifel für sinnlose
=-Bytes verschwendet worden. Die Mautstelle kassiert seit 1987. - Base64 ist keine Verschlüsselung. Wäre es, könnten Sie die Ausgabe eines einzigen Beispiels in diesem Artikel nicht lesen. Es ist ein Fensterplatz, kein Tresor.
Die kurze Version
Wählen Sie Ihre Engine nach der Straße, die die Daten reisen werden: BASE64_STANDARD für alles, was Sie auch selbst dekodieren, die _NO_PAD-Engines, wenn Sie beide Enden kontrollieren und die Bytes zurück wollen, URL_SAFE_NO_PAD für Tokens und URLs, und ein eigenes Alphabet nur, wenn ein Protokoll es verlangt. Dimensionieren Sie Ihre Buffer mit encoded_len(), streamen Sie die großen Sachen durch EncoderWriter und schließen Sie immer mit finish(), brechen Sie Zeilen mit line-wrap und pem nur um, wenn ein Format es verlangt, lassen Sie die SIMD-Engines die schwere Arbeit tun, wenn Sie können, und denken Sie daran, dass der Vier-gegen-drei-Tausch der Preis dafür ist, die nur-für-Text-Tür zu passieren. Kodieren Sie alles, schützen Sie nur das, was ein echtes Schloss braucht. Und wenn Sie in die andere Richtung müssen, einen String zurück in die Bytes auspacken, die die Reise begannen, deckt der Schwestern-Artikel das Dekodieren in Rust ab, komplett mit der vollen Wertungstabelle der exakten Fehler-Nachrichten.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in Rust: Ein vollständiger Leitfaden