Loading page…
Rust walkthroughs
Loading page…
base64::encode vs base64::encode_config?The base64 crate's encode function uses a default configuration that may not be suitable for all security contexts. encode_config allows customizing the character set and behavior, which is critical for preventing attacks like URL injection, and ensuring compatibility with security-sensitive contexts like cryptographic tokens and URLs.
use base64::{encode, decode};
fn default_behavior() {
let data = b"hello world";
let encoded = encode(data);
println!("{}", encoded); // aGVsbG8gd29ybGQ=
// Default uses:
// - Standard alphabet (A-Za-z0-9+/)
// - Padding with '='
// - No line wrapping
}The default configuration uses the standard Base64 alphabet with padding characters.
use base64::encode;
fn security_issue() {
// Standard Base64 uses + and / which are problematic in:
// - URLs: + becomes space, / is path separator
// - File names: / is path separator
// - Database keys: might have special meaning
let data = b"\xff\xfe\xfd";
let encoded = encode(data);
println!("{}", encoded); // //79/Q==
// Contains '/' which breaks URLs and file paths
}The + and / characters create problems in URLs and file systems.
use base64::{encode_config, decode_config, Config, URL_SAFE, URL_SAFE_NO_PAD};
fn url_safe_encoding() {
let data = b"\xff\xfe\xfd";
// Standard encoding
let standard = encode(data);
println!("Standard: {}", standard); // //79/Q==
// URL-safe encoding
let url_safe = encode_config(data, URL_SAFE);
println!("URL-safe: {}", url_safe); // __79_Q==
// URL-safe without padding
let url_safe_no_pad = encode_config(data, URL_SAFE_NO_PAD);
println!("URL-safe no pad: {}", url_safe_no_pad); // __79_Q
// Character substitution:
// + becomes -
// / becomes _
// = (padding) removed in NO_PAD variants
}URL-safe Base64 replaces problematic characters with URL-safe alternatives.
use base64::{encode_config, decode_config, URL_SAFE_NO_PAD};
// JWT tokens require URL-safe Base64 without padding
fn jwt_token_example() {
// JWT header
let header = br#"{"alg":"HS256","typ":"JWT"}"#;
let header_b64 = encode_config(header, URL_SAFE_NO_PAD);
println!("Header: {}", header_b64);
// JWT payload
let payload = br#"{"sub":"1234567890","name":"John"}"#;
let payload_b64 = encode_config(payload, URL_SAFE_NO_PAD);
println!("Payload: {}", payload_b64);
// Using padding would break the token format
// Standard encoding would create invalid URL tokens
}JWT and other token formats require specific Base64 variants.
use base64::{encode, encode_config, URL_SAFE, URL_SAFE_NO_PAD};
fn padding_security() {
let data = b"hello";
// With padding
let with_pad = encode(data);
println!("With padding: {} (len={})", with_pad, with_pad.len());
// aGVsbG8= (len=8)
// Without padding
let no_pad = encode_config(data, URL_SAFE_NO_PAD);
println!("No padding: {} (len={})", no_pad, no_pad.len());
// aGVsbG8 (len=7)
// Padding reveals original data length modulo 3
// One '=' means original length % 3 == 2
// Two '=' means original length % 3 == 1
// No '=' means original length % 3 == 0
// This can leak information in some cryptographic contexts
}Padding can leak information about the original data length.
use base64::{encode_config, decode_config, Config};
fn custom_config() {
// URL_SAFE is a predefined Config
// You can create custom configurations
// Standard with no padding
let config_no_pad = base64::STANDARD_NO_PAD;
let data = b"test data";
let encoded = encode_config(data, config_no_pad);
println!("No padding: {}", encoded);
// Decode requires matching config
let decoded = decode_config(&encoded, config_no_pad).unwrap();
assert_eq!(data.to_vec(), decoded);
}Configuration must match between encoding and decoding.
use base64::{decode, decode_config, URL_SAFE};
fn decode_errors() {
// Invalid characters cause errors
let invalid = "not valid base64!!!";
match decode(invalid) {
Ok(data) => println!("Decoded: {:?}", data),
Err(e) => eprintln!("Decode error: {}", e),
}
// Wrong config for the encoding
let url_encoded = encode_config(b"test", URL_SAFE);
// This works
let _ = decode_config(&url_encoded, URL_SAFE).unwrap();
// Standard decode may fail or produce wrong result
// depending on the characters used
}Decoding with the wrong configuration can fail or produce incorrect results.
use base64::{encode_config, decode_config, URL_SAFE_NO_PAD};
fn timing_considerations() {
// Base64 encoding/decoding is NOT constant-time
// For security-sensitive comparisons (like API keys):
let encoded_key = encode_config(b"secret_key_123", URL_SAFE_NO_PAD);
// DON'T compare directly - timing attack vulnerable
// if user_input == encoded_key { ... }
// DO use constant-time comparison
use subtle::ConstantTimeEq;
fn verify_token(expected: &[u8], provided: &[u8]) -> bool {
expected.ct_eq(provided).into()
}
// The base64 operations themselves aren't constant-time,
// which may be a concern in some cryptographic contexts
}Base64 operations are not constant-time, which matters for cryptographic applications.
use base64::{encode_config, decode_config, Config, LineWrap};
fn email_encoding() {
// MIME/email requires line wrapping at 76 characters
let long_data: Vec<u8> = (0..200).collect();
// Standard base64 without wrapping
let standard = encode_config(&long_data, base64::STANDARD);
println!("Standard length: {}", standard.len());
// MIME-style with line wrapping
let mime_config = Config::new(
base64::alphabet::STANDARD,
true, // pad
true, // strip whitespace on decode
LineWrap::Wrap(76, "\r\n".to_string()),
);
let mime_encoded = encode_config(&long_data, mime_config);
println!("MIME encoded:\n{}", mime_encoded);
}Email and MIME have specific line-wrapping requirements.
use base64::{encode, encode_config, URL_SAFE};
fn charset_issues() {
// Standard Base64 alphabet: A-Za-z0-9+/
// These characters can cause issues:
// '+' in URLs: may be decoded as space
// '/' in URLs: path separator
// '/' in file paths: directory separator
// '=' in URLs: often used as delimiter
let binary_data: Vec<u8> = (0..=255).collect();
let standard = encode(&binary_data);
// Count problematic characters
let plus_count = standard.matches('+').count();
let slash_count = standard.matches('/').count();
let equals_count = standard.matches('=').count();
println!("Standard: {} '+', {} '/', {} '='",
plus_count, slash_count, equals_count);
// URL-safe eliminates + and /
let url_safe = encode_config(&binary_data, URL_SAFE);
let plus_count = url_safe.matches('+').count();
let slash_count = url_safe.matches('/').count();
println!("URL-safe: {} '+', {} '/'", plus_count, slash_count);
// Both should be 0
}Standard Base64 characters can have special meaning in various contexts.
use base64::{encode_config, URL_SAFE_NO_PAD};
fn filesystem_safety() {
// Using Base64 for filenames
fn safe_filename(content: &str) -> String {
let encoded = encode_config(content.as_bytes(), URL_SAFE_NO_PAD);
format!("file_{}", encoded)
}
let filename1 = safe_filename("user@example.com");
let filename2 = safe_filename("data/with/slashes");
let filename3 = safe_filename("file?query=value");
println!("{}", filename1); // file_dXNlckBleGFtcGxlLmNvbQ
println!("{}", filename2); // file_ZGF0YS93aXRoL3NsYXNoZXM
println!("{}", filename3); // file_ZmlsZT9xdWVyeT12YWx1ZQ
// All safe for filesystem use
// No /, \, ?, *, :, |, <, > characters
}URL-safe Base64 without padding creates filesystem-safe strings.
use base64::{encode_config, URL_SAFE_NO_PAD};
fn database_keys() {
// Using Base64 for database keys
// Problematic with standard encoding
let key_data = b"\xff\x00\xff"; // Binary data
let standard = encode(key_data);
println!("Standard key: {}", standard); // /wD/ (contains /)
// URL-safe for keys used in URLs or as identifiers
let safe_key = encode_config(key_data, URL_SAFE_NO_PAD);
println!("Safe key: {}", safe_key); // _wD_
// Using as part of a larger key
let record_id = format!("record:{}", safe_key);
println!("Record ID: {}", record_id);
}Keys containing / can break hierarchical storage systems.
use base64::{encode_config, URL_SAFE_NO_PAD};
fn api_key_generation() {
use rand::RngCore;
fn generate_api_key() -> String {
let mut bytes = [0u8; 32];
rand::thread_rng().fill_bytes(&mut bytes);
encode_config(&bytes, URL_SAFE_NO_PAD)
}
let key1 = generate_api_key();
let key2 = generate_api_key();
println!("Key 1: {}", key1);
println!("Key 2: {}", key2);
// Keys are:
// - URL-safe (no + or /)
// - No padding (no =)
// - Fixed length (43 characters for 32 bytes)
// - Safe for HTTP headers, URLs, database storage
}API keys should use URL-safe encoding without padding.
use base64::{decode_config, Config};
fn whitespace_handling() {
// Some systems add whitespace to Base64
let with_spaces = "aG VsbG 8gd29y bGQ=";
let with_newlines = "aG VsbG8g\nd29ybGQ=";
// Default config strips whitespace on decode
let decoded = decode_config(with_spaces, base64::STANDARD).unwrap();
println!("Decoded: {:?}", String::from_utf8(decoded).unwrap());
// But be careful: whitespace could be injected maliciously
// Consider normalizing input before decoding in security contexts
}Whitespace handling can be a security concern if not controlled.
use base64::{encode, decode};
fn memory_usage() {
// Base64 encoding increases size by ~33%
let data = b"hello world";
let encoded = encode(data);
println!("Original: {} bytes", data.len());
println!("Encoded: {} bytes", encoded.len());
println!("Overhead: {:.0}%",
(encoded.len() as f64 / data.len() as f64 - 1.0) * 100.0);
// For large data, consider streaming
// The base64 crate doesn't provide streaming in the simple API
// For streaming, use the read/write interfaces
}Base64 encoding increases size and allocates new memory.
use base64::{encode_config, decode_config, URL_SAFE, URL_SAFE_NO_PAD, STANDARD, STANDARD_NO_PAD};
fn security_checklist() {
// 1. Use URL-safe encoding for:
// - URLs and query parameters
// - File names
// - Database keys
// - API keys
// - JWT tokens
// 2. Consider removing padding when:
// - Length shouldn't be leaked
// - Tokens need to be URL-safe
// - Interoperating with systems that don't expect padding
// 3. Match encoding and decoding configs:
// - URL_SAFE encoded → URL_SAFE decoded
// - NO_PAD encoded → NO_PAD decoded (or decode handles missing pad)
// 4. For cryptographic applications:
// - Be aware base64 is not constant-time
// - Don't use direct comparison for secrets
// 5. Validate input before decoding:
// - Check for expected length
// - Check for expected character set
// - Handle decode errors gracefully
// Example: Safe encoding for JWT-like token
fn create_token_component(data: &[u8]) -> String {
encode_config(data, URL_SAFE_NO_PAD)
}
// Example: Safe decoding with validation
fn decode_token_component(encoded: &str) -> Result<Vec<u8>, &'static str> {
// Validate character set
if !encoded.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
return Err("Invalid characters in token");
}
decode_config(encoded, URL_SAFE_NO_PAD)
.map_err(|_| "Invalid base64 encoding")
}
}The security considerations for base64::encode vs base64::encode_config:
| Context | Recommended Config | Reason |
|---------|-------------------|--------|
| URLs | URL_SAFE_NO_PAD | No +, /, or = characters |
| JWT tokens | URL_SAFE_NO_PAD | Standard JWT requirement |
| API keys | URL_SAFE_NO_PAD | Safe in URLs, headers, databases |
| File names | URL_SAFE_NO_PAD | No / path separators |
| MIME/email | Standard with line wrap | RFC 2045 requirement |
| Internal storage | STANDARD | Maximum compatibility |
Key security concerns:
Character injection: Standard Base64's + and / characters break URLs and file paths.
Padding leakage: Padding characters reveal original data length modulo 3, which can leak information in cryptographic contexts.
Config mismatch: Decoding with the wrong configuration can fail or produce incorrect results.
Timing attacks: Base64 operations are not constant-time; direct comparison of encoded secrets is vulnerable.
Whitespace handling: Default decode strips whitespace, which could mask injection attacks.
Best practices:
URL_SAFE_NO_PAD for anything that goes in URLs, tokens, or user-facing identifiers