How do I work with Base64 for Encoding and Decoding in Rust?
Walkthrough
Base64 is a crate for encoding and decoding base64 data in Rust. Base64 is a binary-to-text encoding scheme that represents binary data in an ASCII string format. It's commonly used for encoding binary data in contexts that only support text, such as email attachments, JSON payloads, and data URLs.
Key concepts:
- Encoding β Convert binary data to Base64 string
- Decoding β Convert Base64 string back to binary
- Character sets β Standard, URL-safe, and crypt variants
- Line wrapping β Split output into multiple lines
- Padding β Optional
=padding characters
When to use Base64:
- Encoding binary data for JSON/XMLδΌ θΎ
- Data URLs and web APIs
- Email attachments (MIME)
- Storing binary data in text formats
- JWT tokens and cryptographic signatures
When NOT to use Base64:
- When you need compact data (adds ~33% overhead)
- For simple text encoding (use UTF-8)
- For password storage (use proper hashing)
Code Examples
Basic Encoding and Decoding
use base64::{Engine as _, engine::general_purpose};
fn main() {
let data = b"hello world";
// Encode to base64
let encoded = general_purpose::STANDARD.encode(data);
println!("Encoded: {}", encoded); // "aGVsbG8gd29ybGQ="
// Decode from base64
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
println!("Decoded: {}", String::from_utf8_lossy(&decoded));
}URL-Safe Encoding
use base64::{Engine as _, engine::general_purpose};
fn main() {
let data = b"hello world?param=value";
// Standard encoding (contains + and /)
let standard = general_purpose::STANDARD.encode(data);
println!("Standard: {}", standard);
// URL-safe encoding (uses - and _ instead)
let url_safe = general_purpose::URL_SAFE.encode(data);
println!("URL-safe: {}", url_safe);
// URL-safe without padding
let url_safe_no_pad = general_purpose::URL_SAFE_NO_PAD.encode(data);
println!("URL-safe (no pad): {}", url_safe_no_pad);
}No Padding
use base64::{Engine as _, engine::general_purpose};
fn main() {
let data = b"hello";
// With padding
let with_pad = general_purpose::STANDARD.encode(data);
println!("With padding: {}", with_pad); // "aGVsbG8="
// Without padding
let no_pad = general_purpose::STANDARD_NO_PAD.encode(data);
println!("No padding: {}", no_pad); // "aGVsbG8"
// Decoding works with or without padding
let decoded = general_purpose::STANDARD.decode(&no_pad).unwrap();
println!("Decoded: {:?}", decoded);
}Encoding to String
use base64::{Engine as _, engine::general_purpose};
fn encode_string(input: &str) -> String {
general_purpose::STANDARD.encode(input.as_bytes())
}
fn decode_to_string(encoded: &str) -> Option<String> {
general_purpose::STANDARD
.decode(encoded)
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
}
fn main() {
let original = "Hello, Rust!";
let encoded = encode_string(original);
println!("Encoded: {}", encoded);
let decoded = decode_to_string(&encoded).unwrap();
println!("Decoded: {}", decoded);
}Working with Binary Data
use base64::{Engine as _, engine::general_purpose};
fn main() {
// Binary data (e.g., image header)
let binary_data: Vec<u8> = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
let encoded = general_purpose::STANDARD.encode(&binary_data);
println!("Encoded binary: {}", encoded);
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
assert_eq!(binary_data, decoded);
}Streaming Encoding
use base64::{Engine as _, engine::general_purpose, write::EncoderWriter};
use std::io::Write;
fn main() -> std::io::Result<()> {
// Encode while writing to a Vec
let mut output = Vec::new();
{
let mut encoder = EncoderWriter::new(&mut output, &general_purpose::STANDARD);
encoder.write_all(b"hello world")?;
}
let encoded = String::from_utf8(output).unwrap();
println!("Streamed: {}", encoded);
Ok(())
}Streaming Decoding
use base64::{Engine as _, engine::general_purpose, read::DecoderReader};
use std::io::Read;
fn main() -> std::io::Result<()> {
let encoded = b"aGVsbG8gd29ybGQ=";
// Decode while reading
let mut decoder = DecoderReader::new(&encoded[..], &general_purpose::STANDARD);
let mut decoded = String::new();
decoder.read_to_string(&mut decoded)?;
println!("Decoded: {}", decoded);
Ok(())
}Custom Configuration
use base64::{Engine as _, engine::general_purpose::GeneralPurpose, alphabet, Config};
fn main() {
// Custom configuration
let config = Config::new()
.with_encode_padding(true)
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent);
let engine = GeneralPurpose::new(&alphabet::STANDARD, config);
let encoded = engine.encode(b"custom config");
println!("Custom encoded: {}", encoded);
}JWT Token Encoding
use base64::{Engine as _, engine::general_purpose};
#[derive(Debug)]
struct JwtHeader {
alg: String,
typ: String,
}
impl JwtHeader {
fn to_json(&self) -> String {
format!(r#"{{"alg":"{}","typ":"{}"}}"#", self.alg, self.typ)
}
}
fn encode_jwt_part(data: &str) -> String {
// URL-safe without padding for JWT
general_purpose::URL_SAFE_NO_PAD.encode(data.as_bytes())
}
fn main() {
let header = JwtHeader {
alg: "HS256".to_string(),
typ: "JWT".to_string(),
};
let payload = r#"{"sub":"1234567890","name":"John"}"#;
let header_b64 = encode_jwt_part(&header.to_json());
let payload_b64 = encode_jwt_part(payload);
println!("Header: {}", header_b64);
println!("Payload: {}", payload_b64);
println!("JWT: {}.{}.SIGNATURE", header_b64, payload_b64);
}Data URL Generation
use base64::{Engine as _, engine::general_purpose};
fn create_data_url(mime_type: &str, data: &[u8]) -> String {
let encoded = general_purpose::STANDARD.encode(data);
format!("data:{};base64,{}", mime_type, encoded)
}
fn main() {
// Small PNG-like header
let image_data = b"\x89PNG\r\n\x1a\nfake image data";
let data_url = create_data_url("image/png", image_data);
println!("Data URL: {}", data_url);
}Error Handling
use base64::{Engine as _, engine::general_purpose};
fn decode_safe(encoded: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::STANDARD.decode(encoded)
}
fn main() {
// Valid input
match decode_safe("aGVsbG8=") {
Ok(data) => println!("Decoded: {:?}", data),
Err(e) => eprintln!("Error: {}", e),
}
// Invalid input
match decode_safe("not-valid-base64!!!") {
Ok(data) => println!("Decoded: {:?}", data),
Err(e) => eprintln!("Expected error: {}", e),
}
}Encoding Structs for Storage
use base64::{Engine as _, engine::general_purpose};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct UserSession {
user_id: u64,
username: String,
expires_at: u64,
}
fn encode_session(session: &UserSession) -> Result<String, serde_json::Error> {
let json = serde_json::to_string(session)?;
Ok(general_purpose::STANDARD.encode(json.as_bytes()))
}
fn decode_session(encoded: &str) -> Result<UserSession, Box<dyn std::error::Error>> {
let decoded = general_purpose::STANDARD.decode(encoded)?;
let json = String::from_utf8(decoded)?;
Ok(serde_json::from_str(&json)?)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let session = UserSession {
user_id: 42,
username: "alice".to_string(),
expires_at: 1700000000,
};
let encoded = encode_session(&session)?;
println!("Encoded session: {}", encoded);
let decoded = decode_session(&encoded)?;
println!("Decoded: {:?}", decoded);
Ok(())
}Hex to Base64 Conversion
use base64::{Engine as _, engine::general_purpose};
fn hex_to_base64(hex: &str) -> Result<String, hex::FromHexError> {
let bytes = hex::decode(hex)?;
Ok(general_purpose::STANDARD.encode(&bytes))
}
fn base64_to_hex(encoded: &str) -> Result<String, base64::DecodeError> {
let bytes = general_purpose::STANDARD.decode(encoded)?;
Ok(hex::encode(&bytes))
}
fn main() {
let hex_str = "48656c6c6f"; // "Hello" in hex
match hex_to_base64(hex_str) {
Ok(b64) => println!("Base64: {}", b64),
Err(e) => eprintln!("Error: {}", e),
}
}Cryptographic Key Encoding
use base64::{Engine as _, engine::general_purpose};
struct CryptoKey {
key_data: Vec<u8>,
}
impl CryptoKey {
fn new(data: Vec<u8>) -> Self {
Self { key_data: data }
}
fn to_base64(&self) -> String {
general_purpose::STANDARD.encode(&self.key_data)
}
fn from_base64(encoded: &str) -> Result<Self, base64::DecodeError> {
let decoded = general_purpose::STANDARD.decode(encoded)?;
Ok(Self { key_data: decoded })
}
}
fn main() {
// 32-byte key
let key = CryptoKey::new(vec![0u8; 32]);
let encoded = key.to_base64();
println!("Encoded key: {}", encoded);
let decoded = CryptoKey::from_base64(&encoded).unwrap();
assert_eq!(key.key_data, decoded.key_data);
}Email Attachment Encoding
use base64::{Engine as _, engine::general_purpose};
struct EmailAttachment {
filename: String,
mime_type: String,
data: Vec<u8>,
}
impl EmailAttachment {
fn to_mime(&self) -> String {
let encoded = general_purpose::STANDARD.encode(&self.data);
let chunked = encoded
.as_bytes()
.chunks(76) // Standard MIME line length
.map(|chunk| std::str::from_utf8(chunk).unwrap())
.collect::<Vec<_>>()
.join("\r\n");
format!(
"Content-Type: {}\r\n\
Content-Transfer-Encoding: base64\r\n\
Content-Disposition: attachment; filename=\"{}\"\r\n\
\r\n{}",
self.mime_type,
self.filename,
chunked
)
}
}
fn main() {
let attachment = EmailAttachment {
filename: "document.txt".to_string(),
mime_type: "text/plain".to_string(),
data: b"Hello, this is an attachment.".to_vec(),
};
println!("{}", attachment.to_mime());
}Benchmark Comparison
use base64::{Engine as _, engine::general_purpose};
fn main() {
let data = vec![0u8; 1_000_000]; // 1 MB
// Encoding speed
let start = std::time::Instant::now();
let encoded = general_purpose::STANDARD.encode(&data);
let encode_time = start.elapsed();
// Decoding speed
let start = std::time::Instant::now();
let decoded = general_purpose::STANDARD.decode(&encoded).unwrap();
let decode_time = start.elapsed();
println!("Original size: {} bytes", data.len());
println!("Encoded size: {} bytes", encoded.len());
println!("Overhead: {:.1}%", (encoded.len() as f64 / data.len() as f64 - 1.0) * 100.0);
println!("Encode time: {:?}", encode_time);
println!("Decode time: {:?}", decode_time);
}Validating Base64 Input
use base64::{Engine as _, engine::general_purpose};
fn is_valid_base64(input: &str) -> bool {
general_purpose::STANDARD.decode(input).is_ok()
}
fn main() {
let test_cases = [
("aGVsbG8=", true),
("SGVsbG8gV29ybGQ=", true),
("invalid!!!", false),
("not base64", false),
];
for (input, expected) in test_cases {
let result = is_valid_base64(input);
println!("{}: {} (expected {})", input, result, expected);
}
}Image Data Encoding
use base64::{Engine as _, engine::general_purpose};
fn encode_image_to_data_url(image_data: &[u8], format: &str) -> String {
let encoded = general_purpose::STANDARD.encode(image_data);
format!("data:image/{};base64,{}", format, encoded)
}
fn main() {
// Fake PNG signature + data
let png_data = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR";
let data_url = encode_image_to_data_url(png_data, "png");
println!("Data URL (truncated): {}...", &data_url[..50]);
}API Key Encoding
use base64::{Engine as _, engine::general_purpose};
struct ApiKey {
prefix: String,
key: Vec<u8>,
}
impl ApiKey {
fn new(prefix: &str, key: Vec<u8>) -> Self {
Self {
prefix: prefix.to_string(),
key,
}
}
fn encode(&self) -> String {
format!("{}.{}", self.prefix, general_purpose::URL_SAFE_NO_PAD.encode(&self.key))
}
fn decode(s: &str) -> Option<Self> {
let parts: Vec<&str> = s.splitn(2, '.').collect();
if parts.len() != 2 {
return None;
}
let key = general_purpose::URL_SAFE_NO_PAD.decode(parts[1]).ok()?;
Some(Self {
prefix: parts[0].to_string(),
key,
})
}
}
fn main() {
let api_key = ApiKey::new("sk", vec![1, 2, 3, 4, 5]);
let encoded = api_key.encode();
println!("API Key: {}", encoded);
let decoded = ApiKey::decode(&encoded).unwrap();
assert_eq!(api_key.key, decoded.key);
}Summary
Base64 Key Imports:
use base64::{Engine as _, engine::general_purpose};Encoding Engines:
| Engine | Description |
|---|---|
STANDARD |
Standard base64 with +/ and padding |
STANDARD_NO_PAD |
Standard without padding |
URL_SAFE |
URL-safe with -_ and padding |
URL_SAFE_NO_PAD |
URL-safe without padding |
Core Methods:
// Encoding
let encoded = general_purpose::STANDARD.encode(data);
// Decoding
let decoded = general_purpose::STANDARD.decode(&encoded)?;Character Sets:
| Type | Characters |
|---|---|
| Standard | A-Za-z0-9+/ |
| URL-safe | A-Za-z0-9-_ |
| Padding | = |
Common Use Cases:
| Use Case | Recommended Engine |
|---|---|
| General purpose | STANDARD |
| URLs/Filenames | URL_SAFE_NO_PAD |
| JWT tokens | URL_SAFE_NO_PAD |
| Email/MIME | STANDARD |
Key Points:
- Base64 adds ~33% size overhead
- Use URL-safe encoding for URLs and filenames
- JWT uses URL-safe without padding
- Decode handles both padded and unpadded input
- Streaming available for large data
- Use
Enginetrait for custom configurations - Error handling returns
DecodeError
