Base64-Kodierung in Rust: Ein vollständiger Leitfaden
Sie stehen gerade davor, einige Bytes durch eine rein textuelle Tür zu schicken, und der Eintrittspreis ist ein String aus Buchstaben, Ziffern, Pluszeichen und Schrägstrichen, der etwa ein Drittel länger ist als Ihr Ausgangsmaterial. Willkommen bei Base64, der Mautstation 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 ein oder zwei =-Zeichen sagt dem Leser, wo die echten Daten aufhörten. Dieser Vier-gegen-Drei-Tausch ist die gesamte Wirtschaft des Formats, und dieser Leitfaden dreht sich darum, ihn in Rust gut zu machen.
Das Erste, was man wissen muss, ist, dass die Rust-Standard-Bibliothek es nicht für Sie tut. Es lauert kein base64_encode() in std, und kein use std::... ändert Ihre Meinung. Das Ökosystem hat sich auf ein einziges Crate einfach namens base64 geeinigt, und es ist lasttragend geworden: Version 0.23.1 erschien am 4. August 2026, das Crate hat seit Dezember 2015 45 Versionen veröffentlicht, und der Download-Zähler steht bei knapp 1,5 Milliarden. Jedes Kodierungs-Beispiel unten verwendet genau dieses eine Crate, plus zwei kleine Gefährten für Zeilenumbruch und PEM-Rüstung.
Die Toolchain und das 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 das Crate, in einem beliebigen 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 mit. Das Crate kommt mit drei optionalen Features, die man kennen sollte: std (standardmäßig an; gibt Ihnen std::io-Streaming, die üblichen Error-Implementierungen und die Heap-Zuweisung), alloc (die allozierenden APIs für eingebettete no_std-Builds) und simd-unsafe (standardmäßig an; die SIMD-Engines, die ein paar Abschnitte weiter unten auftauchen). Die minimal unterstützte Rust-Version ist 1.71.0, also läuft es auf jeder aktuellen Version. Rundum sitzen die Gefährten für Jobs, die der Kern bewusst nicht tut:
- line-wrap (Version 0.2) setzt die 76- oder 64-Zeichen-Zeilenumbrüche, die MIME und PEM verlangen; das
base64-Crate selbst verweigert das Umbrechen, aus Absicht, wie Sie sehen werden. - pem (Version 4) baut und parst
-----BEGIN ...------Blöcke für Zertifikate und Schlüssel; es hängt intern vonbase64ab und fügt die Rüstung und das Umbrechen hinzu. - base64ct (Version 1.8) ist der Konstantzeit-Dekodierer aus dem RustCrypto-Projekt, für den Fall, dass die Lese-Seite eines Round Trips die empfindliche Hälfte ist.
- base64-turbo (Version 0.3) ist ein neuerer High-Throughput-Codec, der auf moderner Hardware über 100 GiB/s peakt.
Ein Encode, vier Zeichen
Die kleinste mögliche Zeremonie sieht so aus, und sie beweist gleich 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 lohnen es, bemerkt zu werden. Das prelude-Modul reicht Ihnen still und leise gleich zwei Dinge: die BASE64_STANDARD-Engine und den Engine-Trait, dessen Methoden Sie aufrufen, und deshalb braucht dieses Beispiel nur das nackte use base64::prelude::*;. Und encode() nimmt alles, was als Bytes gelesen werden kann, dank der AsRef<[u8]>-Schranke: ein &str, ein &[u8]-Literal, ein Vec<u8>, Sie dürfen es nennen. Die Decode-Hälfte des Beispiels ist nur da, um dem Encoder auf die Finger zu schauen, denn die Gegenrichtung bekommt ihren eigenen vollständigen Leitfaden auf der Schwestersite. Wenn Sie den Smoke-Test des Crates selbst wollen: Seine Dokumentation kodiert asdf und bekommt YXNkZg== zurück; gleiches Alphabet, gleiche Rechnung.
Der exakte Preis jedes Bytes
Jeder Base64-Encoder, der existiert, berechnet dieselbe Steuer, und wenn man die Rechnung sehen kann, kann man auch einkalkulieren. Jedes Ausgabe-Zeichen trägt 6 Bits, jedes Eingabe-Byte 8, und der kleinste Haufen, der beides ist, sind 24 Bits: genau 3 Bytes rein, genau 4 Zeichen raus. Dieses Verhältnis ist die ganze Show, also wird aus einer 3-Kilobyte-Datei 4 Kilobyte, und aus einem 10-Megabyte-Upload 13,3. Das Padding ist der Rundungsfehler, der sichtbar gemacht wurde: Wenn die Eingabe kein Vielfaches von 3 Bytes ist, hat die letzte Gruppe Restkapazität, und der Encoder füllt sie mit =, damit die Auslä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 | Auslä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 diese erste Zeile zweimal, denn sie ist die, die jeder im Kopf falsch hat: Die leere Eingabe kodiert zum leeren String, nicht zu AA==. Der String AA== ist die Kodierung von genau einem Byte, einem NUL, und das ist eine wirklich andere Nutzlast. Und wenn Sie einen Buffer vor dem Kodieren dimensionieren müssen, gibt Ihnen das Crate die Rechnung als const fn, so dass Sie selbst Arrays zur Kompilierungszeit dimensionieren können:
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 Zeilen für 13 und 14 Bytes, denn dort stolpern Taschentuchrechnungen: 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 None ist, wenn die Längenrechnung überlaufen würde, also ist ein unwrap() für jede Eingabe sicher, die tatsächlich im Speicher existieren kann. In der E-Mail-förmigen Welt kommt ein Aufschlag zur Steuer: MIME bricht Zeilen bei 76 Zeichen um, und die alte Faustregel lautet, dass umgebrochenes Base64 etwa das 1,37-fache der Originalgröße kostet, plus den Header-Overhead. Die FAQ des Crates selbst 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, dass das Padding falsch ist", und "Exabyte an Speicher und Übertragung sind ohne Zweifel an nutzlosen =-Bytes verschwendet worden".
Padding: Eine Entscheidung über den Leser
Im base64 0.23 sind die nackten Encode-Funktionen veraltet - der aktuelle Weg ist, eine Methode auf einer Engine aufzurufen, und eine Engine ist eine Politik: Welches Alphabet zu schreiben, und welches Padding hinzuzufügen. Die Presets leben in base64::engine::general_purpose, und die vier populären sind in das prelude re-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 | Kompakte Nutzlasten, die Sie selbst auch verbrauchen |
URL_SAFE / BASE64_URL_SAFE |
- _ |
Ja | URL-Inhalte, die trotzdem Padding wollen |
URL_SAFE_NO_PAD / BASE64_URL_SAFE_NO_PAD |
- _ |
Nein | JWTs, URLs, Objekt-IDs |
Kodieren ohne Padding ist kein Hack, den das Crate toleriert, sondern eine First-Class-Position, mit vorkonfigurierten NO_PAD- und PAD-Konfigurations-Konstanten neben den *_INDIFFERENT-Geschwistern, die in 0.23.0 hinzugekommen sind. Wenn die Presets nicht passen, bauen Sie Ihre eigene Engine aus einem Alphabet und einem GeneralPurposeConfig mit einem einzigen Regler, und da Engines billig zu konstruieren sind, speichern Sie das Ergebnis in einer const, statt sie pro Request 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 aus der Entscheidung eine über fremde Dekodierer, was nie rein ästhetisch ist. Die Strenge-Regeln auf der Decode-Seite kommen aus DecodePaddingMode, und die folgende Tabelle beantwortet "Kann die andere Seite lesen, was ich geschrieben habe?":
| Sie kodieren mit | Ein strenger STANDARD-Dekodierer |
Ein STANDARD_NO_PAD-Dekodierer |
Ein INDIFFERENT-Dekodierer |
|---|---|---|---|
STANDARD (mit Padding) |
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 |
Aus dieser Tabelle fallen praktische Regeln. 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 von der Außenwelt verbrauchen, hat Ihr Decoder eine Stimme darüber, welche Engine Sie ausgeben sollen: Ein vorgegebener STANDARD-Dekodierer braucht Ihr Padding, während ein STANDARD_PAD_INDIFFERENT-Dekodierer beide akzeptiert. Und die Wahl hat auch eine Sicherheitsfärbung. Beide Schreibweisen derselben Nutzlast zu erlauben, mit und ohne Padding, macht Base64 formbar, und das Paper von 2022 "Base64 Malleability in Practice" (Chatzigiannis und Chalkias, ePrint 2022/361), auf das die Dokumentation des Crates selbst verlinkt, zeigt warum. Ein Protokoll, in dem dieselben Daten auf zwei verschiedene Arten 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 Buchstaben des Standard-Base64-Alphabets sind + und /, und in einer URL sind das zwei der teuersten Zeichen der Sprache: Plus wird zu %2B, Schrägstrich zu %2F, und Padding zu %3D. RFC 4648, Abschnitt 5, löst das mit dem URL- und Dateinamen-sicheren Alphabet, das die beiden Unruhestifter gegen - und _ tauscht und meist auch das Padding weglässt. Die Engine macht den Unterschied unübersehbar:
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 denkbar hässlichsten Inputs werden zu einem vier Zeichen langen String, den Sie ohne ein einziges Prozent-Escape in eine URL, einen Dateinamen, ein Cookie oder einen Datenbank-Schlüssel einfügen können. Das ist das Alphabet, in dem JSON Web Tokens leben: Ein JWT sind drei base64url-Teile, verbunden durch Punkte, und einen mit dem 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 Anfänger beißt: Das Crate braucht genau eines der Features rust_crypto oder aws_lc_rs aktiviert in Cargo.toml, und wenn keines an ist, panikt es beim ersten Signieren oder Verifizieren eines Tokens. Beachten Sie den exp-Claim im Struct: Die Validierung des Crates behandelt ihn als standardmäßig erforderlich, also tragen echte Tokens ihn ohnehin, und das base64url-Alphabet im Token ist reines Geschäft der Bibliothek. Wenn Sie Tokens nur inspizieren statt prägen, zeigt der Schwesternartikel die Fünf-Zeilen-Inspektion. Und eine Erinnerung an die Goldene Regel, die für Tokens mit besonderer Kraft gilt: Die drei Teile eines JWT sind alle ohne Schlüssel lesbar. Base64 ist ein Fensterplatz, kein Schloss.
Text rein, Bytes raus
Encoder lesen keine Gedanken, also heißt "kodiere diesen String" in Rust immer "kodiere die UTF-8-Bytes dieses Strings", denn das ist, was str::as_bytes() herausrückt. Die gute Nachricht: Das moderne Web ist fast durchweg UTF-8, also ist der ehrliche Weg kurz und glücklich:
use base64::prelude::*;
let text = "café";
let packed = BASE64_STANDARD.encode(text.as_bytes());
println!("{packed}"); // Y2Fmw6k=
Alle Multibyte-Fälle benehmen sich:
| Originaltext | Base64 | Round Trip |
|---|---|---|
café |
Y2Fmw6k= |
sauber |
日本語 |
5pel5pys6Kqe |
sauber |
😀 |
8J+YgA== |
sauber |
π ≈ 3.14159 |
z4Ag4omIIDMuMTQxNTk= |
sauber |
Die eine echte Entscheidung ist, von welchen Bytes Sie starten. Wenn Daten als Bytes ankommen und nicht als Text, eine Datei von der Platte oder ein Buffer aus einem Netzwerk-Call, überspringen Sie den String ganz und kodieren Sie den Vec<u8> direkt. Das ist auch die einzig richtige Antwort für nicht-UTF-8-Nutzlasten wie eine PNG oder ein protobuf. Legen Sie ein Bild neben Ihren Code und zeigen Sie den Read 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());
// jede kodierte PNG beginnt mit iVBORw0K
assert!(packed.starts_with("iVBORw0K"));
Diese letzte Assertion ist eine kostenlose Plausibilitätsprüfung und einer der wiedererkennbarsten Präfixe im gesamten Internet. Und wenn Sie denselben logischen Text über zwei verschiedene Zeichensätze kodieren, oder Bytes kodieren, die Sie als anderen Zeichensatz missverstanden haben, kommt der Round Trip mit gerader Miene als Mojibake zurück. Der Encoder lügt nie. Er kodiert einfach, welche Bytes Sie ihm geben, und das ist zugleich seine größte Stärke und seine einzige Falle.
Wenn ein Format Zeilen will
Das base64-Crate setzt bewusst keine Zeilenumbrüche ein, und es ist nicht das erste Mal, dass es diese Entscheidung trifft. Version 0.5.0 lieferte eingebauten MIME-Zeilenumbruch mit konfigurierbaren Zeilenendungen, und Version 0.10.0 entfernte ihn, die Bibliothek entschied, dass Umbrechen für ein Allzweck-Crate zu meinungsvoll war und die no_std-Geschichte verkomplizierte. Wenn ein Format Zeilen verlangt, existiert das line-wrap-Crate genau dafür. Seine eine Funktion, line_wrap(), nimmt Ihren vorallokierten Buffer, die Eingabelänge, die Spaltenbeschränkung und das Zeilenende und gibt die Anzahl der eingefügten Zeilenend-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 Zeichen rein, 410 Bytes raus, 10 Zeilenenden (fünf CRLF-Paare)
Dimensionieren Sie den Buffer vorab mit Platz für die Zeilenenden, rufen Sie die Funktion auf und truncieren Sie auf die gemeldete Gesamtgröße. Die fünf CRLF-Paare sind der Preis von MIMEs 76-Spalten-Regel. Für PEM tauschen Sie Beschränkung und Zeilenende, 64 Spalten und line_wrap::lf(), und Sie haben den Rüstungstext. Dann fügt das pem-Crate die Banner in einem Call 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-artige Zeilenenden, falls der Verbraucher 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 wechselt zu LF für die Tools, die es erwarten. Beachten Sie, was das pem-Crate nicht tut: Es ruft nie eine Base64-Funktion auf, die Sie sehen, denn die Kodierung ist sein internes Geschäft. Wenn ein Format Zeilen will, ist die Architektur ein Crate pro Job.
Streaming in konstantem Speicher
Für Daten, die zu groß sind, um in einer einzigen Variable zu bleiben, antwortet das Crate mit derselben Streaming-Philosophie wie der Rest von Rusts io: Der write::EncoderWriter verpackt jeden Writer und base64-kodiert alles, was Sie ihm schreiben, in konstantem Speicher. Der vollständige Ritus für einen Buffer sieht so aus, und der Star des Abschnitts ist der finish()-Call:
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 Call ist, der die letzte unvollständige Gruppe auswascht und das Padding hinzufügt, und der Encoder hat eine Schwestern-Methode, die das nicht tut. Die Dokumentation des Crates sagt es offen: finish() "kodiert übrig gebliebene Eingabe-Bytes und fügt Padding hinzu, wenn angebracht. Es wird automatisch bei der Aufbereinigung aufgerufen (siehe die Drop-Implementierung), aber Fehler, die beim Aufruf des zugrunde liegenden Writers auftreten, werden unterdrückt. Wenn Sie solche Fehler behandeln wollen, rufen Sie finish() selbst auf." Die Drop-Implementierung verhält sich wie BufWriter: Sie wäscht aus, ignoriert aber Fehler während des Drops. Die letzte unvollständige Gruppe geht also nicht verloren, aber "es hat wahrscheinlich funktioniert" ist keine Auslieferungsstrategie, denn der Schreibfehler, von dem es Ihnen hätte erzählen können, ist weg.
Derselbe Stream funktioniert eine Ebene entfernt über io::copy, wenn Sie die ganze Pipeline in einem Call wollen, und es gibt einen Bonus-Wrapper für die Momente, in denen Sie es nur in einer 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 innerhalb einer beliebigen Format-String, ohne eine einzige Heap-Allokation, was Log-Zeilen und Debug-Output plötzlich angenehm macht.
Allokation - und ihre Abwesenheit
Bequeme Methoden allozieren, und für den Großteil Ihres Lebens ist das der richtige Handel. Aber der Engine-Trait zeigt drei Sorten von Encode, und die folgende Tabelle ist die gesamte Entscheidungs-Matrix:
| Methode | Ausgabe | Alloziert |
|---|---|---|
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 ihn falsch dimensioniert 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 falsch haben, bekommen Sie ein sauberes EncodeSliceError::OutputSliceTooSmall statt undefiniertem Verhalten, was in einer Systemsprache der Unterschied zwischen einem langweiligen Nachmittag und einem langen ist. Für eingebettete Arbeit existieren dieselben Funktionen hinter dem alloc-Feature, so dass Sie die API behalten und den Heap ablegen können.
Geschwindigkeit: Die SIMD-Engines
Version 0.23.0, die im Juli 2026 erschien, brachte die Schlagzeilen-Funktion: SIMD-beschleunigte Engines für die Standard- und URL-sicheren Alphabete. Es gibt drei, und sie teilen sich danach, wie sehr sie Ihrer Hardware vertrauen:
| 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, geht davon aus, dass die CPU AVX2 hat | ja, auf x86_64-Zielen |
Neon |
nein, geht davon aus, dass die CPU NEON hat | 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 passt, also bauen Sie ihn einmal in einer const oder beim Start und verwenden Sie ihn immer wieder. Auf fähiger Hardware ist er ein Vielfaches schneller als der skalare Weg, sowohl beim Kodieren als auch beim Dekodieren. Eine ehrliche Fußnote: Der SIMD-Weg ist der einzige Ort im Crate, der unsafe berührt, deshalb heißt das Feature simd-unsafe. Schalten Sie das Feature ab, und das gesamte Crate ist wieder #![forbid(unsafe_code)], und die skalare Engine macht weiter ihre ehrliche Arbeit. Wenn roher Durchsatz der ganze Punkt ist, stößt das base64-turbo-Crate die Hülle noch weiter und peakt über 100 GiB/s mit AVX512-, AVX2- und NEON-Kernels hinter Laufzeit-Erkennung, und 100% sicheren skalaren Fallbacks auf allem anderen. Das base64-Crate ist dual lizenziert MIT/Apache-2.0, also ist all das kostenlos, einschließlich der Geschwindigkeit.
Vier Alphabete mehr
Das RFC-Alphabet ist der Standard, aber das base64-Crate liefert vier mehr, jedes eine kleine Statue für ein reales Protokoll, das seinen eigenen Twist brauchte:
| Alphabet | Der Twist | Wer es nutzt | abc 123 kodiert zu |
|---|---|---|---|
alphabet::CRYPT |
./ kommt 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 leicht 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
Dasselbe Input, drei verschiedene Outputs, 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 Bitmustern entsprechen, ergibt das Sortieren der kodierten Strings dieselbe Reihenfolge wie das Sortieren der Original-Bytes, deshalb verwendete GEDCOM 5.5 (1996) es für Multimedia-Felder, die 5.5.1-Revision ließ die Funktion fallen, und das Crate liefert das Alphabet immer noch für Sie. Und wenn der Dialekt, den Sie brauchen, nicht im Crate ist, können Sie ihn mit einem 64-Zeichen-String definieren, denn Alphabet::new() baut die Encode- und Decode-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 vor dem Custom-Alphabet-Weg: Im Moment, in dem Sie einen Dialekt erfinden, sind Sie der einzige Mensch auf der Erde, der Ihre Daten lesen kann, also tun Sie es nur, wenn ein Protokoll es verlangt, und schreiben Sie einen Kommentar, der sagt, welches.
Wo Encoder arbeiten
Base64-Kodierung taucht in Rust-Projekten in einem vorhersehbaren Cast 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, fragwürdig für Hero-Images. - JWTs und OAuth, wo base64url der Dialekt ist und das
jsonwebtoken-Crate das Werkzeug. - PEM-Blöcke für Zertifikate und Schlüssel, die
-----BEGIN CERTIFICATE------Abschnitte, die Base64 bei 64 Zeichen pro Zeile umbrechen. - Binär in XML- und Config-Dateien, das
<data encoding="base64">-Muster, das man noch in exportierten Bookmarks und Settings-Dumps findet. - LDAP- und LDIF-Dateien, die Base64 nutzen, um binäre Attributwerte auf einer Zeile zu halten.
- QR-Code-Nutzlasten und Clipboard-Übergaben, wo Text die Reise übersteht und Binary nicht.
- HTTP-Basic-Auth-Header, wo
Basic TWFuOnBhc3M=ein Credential-Paar ist, und eine Erinnerung daran, dass dies ein Pack-Problem ist, kein Verstecke-Problem.
Und die Goldene Regel, die all das regiert: Base64 ist Verpackungsklebefilm, kein Schloss. Es ist keine Verschlüsselung und keine Komprimierung - es ist das Gegenteil von Komprimierung, und jeder, der diesen Artikel hat, kann alles, was es tut, in einer Zeile rückgängig machen. Kodieren Sie frei, aber kodieren Sie niemals ein Passwort, einen API-Key oder ein Geheimnis und nennen Sie es geschützt. Wenn es versteckt werden muss, verwenden Sie echte Verschlüsselung, und wenn es groß ist, erwägen 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 Nachkomme dieser Entscheidung, deshalb brechen PEM-Dateien heute noch bei 64 um. 1996 übernahm die MIME-Spezifikation (RFC 2045) das Schema, nannte es nach seinem 64-Zeichen-Alphabet "base64" und verlegte das Umbrechen auf 76 Zeichen. Bevor all das kam, lieferten Unix-Kisten uuencode und Macs BinHex, jedes mit seinem eigenen Alphabet, und beide tauchen noch immer in alten Systemen auf, wie Fossilien mit Datei-Headern. 2006 wurde RFC 4648 der Standard, den alle zitieren, mit seinen Alphabettabellen, der base64url-Variante und den kanonischen Kodierungsregeln, die jede Engine in diesem Artikel umsetzt. Sein Abschnitt 3.5 verlangt von Encodern, die ungenutzten Bits am Ende auf null zu setzen, und das Crate tut es. Wenn Ihre Nutzlast später einen strengen Dekodierer am InvalidLastSymbol-Check hängen lässt, passierte die Korruption upstream.
Die eigene Geschichte des Crates reimt sich. Es erschien im Dezember 2015 auf crates.io, und Version 0.5.0 fügte stolz MIME-Zeilenumbruch mit konfigurierbaren Zeilenendungen hinzu. Dann entfernte Version 0.10.0 im Jahr 2018 das Umbrechen und das Weißraum-Handling, die Bibliothek entschied, dass ein Allzweck-Crate kodieren und die Poesie der Anwendungsebene überlassen sollte; 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 Default, und 0.21.0 erklärte die alten freien Funktionen zugunsten von Engine-Methoden für veraltet, mit dem Compiler-Hinweis "Use Engine::encode" (sie funktionieren noch, und deshalb kompiliert ein Großteil des Legacy-Codes fröhlich weiter). 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 traf Version 0.23.0 ein, mit den SIMD-Engines, eigenen Padding-Symbolen, einer klareren Fehlermeldung und dem MSRV-Anstieg auf 1.71, wobei das 0.23.1-Patch am 4. August die Test-Suite für nicht-SIMD-Architekturen reparierte.
Dinge, über die man lächeln kann
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 eines NUL-Bytes. In Base64 sind "nichts" und "eine Null" verschiedene Wesen, und Dekodierer unterscheiden sie.- Jede Base64-kodierte PNG beginnt mit
iVBORw0K. Das ist die PNG-Magie-Zahl in ihrem Verpackungsklebefilm, einer der wiedererkennbarsten Präfixe im gesamten Internet. - In einer URL brauchen Standard-Base64-Zeichen Escape-Kostüme: Plus wird zu
%2B, Schrägstrich zu%2F, und Padding zu%3D. Base64url existiert, damit die Zeichen ihre eigenen Gesichter tragen können. - YouTube-Videoids sind base64url ohne Padding: Acht Bytes ID werden zum elf Zeichen langen 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 ließ die Funktion fallen, und das Crate liefert es immer noch für Sie.
- BinHex, der alte Macintosh-Wrapper, baute sein Alphabet so, dass optisch verwechselbare Zeichen wie
7,O,gundoausgeschlossen werden. Ein Encoder, der für Menschen-Augen entworfen wurde, in einer Welt vor dem Rechtschreib-Check. - Die FAQ des Crates selbst ist ungeschminkt über das Padding: Exabyte an Speicher und Übertragung sind ohne Zweifel an nutzlosen
=-Bytes verschwendet worden. Die Mautstation kassiert seit 1987. - Base64 ist keine Verschlüsselung. Wäre es es, könnten Sie die Ausgabe keines Beispiels in diesem Artikel lesen. Es ist ein Fensterplatz, kein Tresor.
Die Kurzversion
Wählen Sie Ihre Engine nach der Straße, auf der die Daten reisen werden: BASE64_STANDARD für alles, was Sie selbst auch 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 Custom-Alphabet nur, wenn ein Protokoll insistiert. Dimensionieren Sie Ihre Buffer mit encoded_len(), streamen Sie die großen Dinge durch EncoderWriter und schließen Sie immer mit finish(), brechen Sie Zeilen nur mit line-wrap und pem um, wenn ein Format es verlangt, lassen Sie die SIMD-Engines die schwere Arbeit machen, wenn Sie können, und denken Sie daran, dass der Vier-gegen-Drei-Tausch der Preis ist, um durch die rein textuelle Tür zu kommen. Kodieren Sie alles, schützen Sie nur, was ein echtes Schloss braucht. Und wenn Sie die andere Richtung einschlagen müssen, einen String zurück in die Bytes auspacken, die die Reise begannen, deckt der Schwesternartikel das Dekodieren in Rust ab, komplett mit der vollständigen Scorecard exakter Fehlermeldungen.
Zuletzt aktualisiert: 2026-09-08
Verwandter Artikel: Base64-Dekodierung in Rust: Ein vollständiger Leitfaden