Upload a file
  1. base64hub
  2. Base64 in Code
  3. Rust
Code

Base64 in Rust

Rust’s standard library has no Base64. The de facto choice is the base64 crate, whose API changed to engines in 0.21. Snippets below were built against 0.23.1 and 0.22.1.

Add the crate and encode a string

[dependencies]
base64 = "0.22"   # the snippets below also build with 0.23

Since 0.21, all work goes through an Engine. The prelude module exports the Engine trait and four ready-made engines:

use base64::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let enc = BASE64_STANDARD.encode("héllo wörld");
    println!("{enc}");                              // aMOpbGxvIHfDtnJsZA==

    let bytes = BASE64_STANDARD.decode(&enc)?;
    println!("{}", String::from_utf8(bytes)?);      // héllo wörld
    Ok(())
}

encode accepts anything that is AsRef<[u8]>, so &str, String, Vec<u8> and byte literals all work. Rust strings are UTF-8, so no conversion step is needed. String::from_utf8 returns an error instead of guessing if the decoded bytes are not valid UTF-8.

The prelude names map to base64::engine::general_purpose:

PreludeEngineAlphabetPadding on encodePadding on decode
BASE64_STANDARDSTANDARD+ /yesrequired
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /nomust be absent
BASE64_URL_SAFEURL_SAFE- _yesrequired
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _nomust be absent

URL-safe Base64 with and without padding

let d = b"<<???>>";
BASE64_URL_SAFE.encode(d);          // "PDw_Pz8-Pg=="
BASE64_URL_SAFE_NO_PAD.encode(d);   // "PDw_Pz8-Pg"

let back = BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg")?;   // b"<<???>>"

The no-pad engines reject padded input (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") returns Err(InvalidPadding)). If you accept tokens from several producers, build an engine that does not care:

use base64::alphabet;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;

const URL_LENIENT: GeneralPurpose = GeneralPurpose::new(
    &alphabet::URL_SAFE,
    GeneralPurposeConfig::new()
        .with_encode_padding(false)
        .with_decode_padding_mode(DecodePaddingMode::Indifferent),
);

URL_LENIENT.decode("PDw_Pz8-Pg").is_ok();     // true
URL_LENIENT.decode("PDw_Pz8-Pg==").is_ok();   // true

The other modes are RequireCanonical (the default for padded engines) and RequireNone. Background on the variants: Base64 vs Base64URL and padding explained.

Encode and decode a file

let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data));          // aGVsbG8K

std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;

To append to an existing buffer without a new allocation, use encode_string:

let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}");                                         // Basic dXNlcjpwYXNz

Streaming with EncoderWriter and DecoderReader

The write and read modules wrap std::io types, so large files never sit in memory:

use std::fs::File;
use std::io::{self, Read};

let mut input = File::open("big.bin")?;
let mut w = base64::write::EncoderWriter::new(File::create("big.b64")?, &BASE64_STANDARD);
io::copy(&mut input, &mut w)?;
w.finish()?;            // writes the final block and padding

let mut r = base64::read::DecoderReader::new(File::open("big.b64")?, &BASE64_STANDARD);
let mut out = Vec::new();
r.read_to_end(&mut out)?;

Call finish() explicitly. Dropping the writer also tries to flush, but it cannot report errors. On a 300 KB test file the streamed output equaled BASE64_STANDARD.encode, and the decoded bytes matched the original.

Validation and DecodeError

decode returns Result<Vec<u8>, DecodeError>. These are the results with BASE64_STANDARD, shown as Debug output from 0.22.1 and the Display message:

InputVariantMessage
PDw_Pz8-Pg==InvalidByte(3, 95)Invalid symbol 95, offset 3.
aGkInvalidPaddingInvalid padding
aGk=!!InvalidByte(3, 61)Invalid symbol 61, offset 3.
aInvalidLength(1)Invalid input length: 1
aGl=InvalidLastSymbol(2, 108)Invalid last symbol 108, offset 2.
"aGVs\nbG8K"InvalidByte(4, 10)Invalid symbol 10, offset 4.

Byte values are decimal: 95 is _, 61 is =, 10 is a newline. InvalidLastSymbol means non-zero padding bits, which this crate rejects by default; with_decode_allow_trailing_bits(true) relaxes that. In 0.23 the InvalidLastSymbol variant changed and its message now reads Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Match on the variant name, not the text.

use base64::DecodeError;

match BASE64_STANDARD.decode(input) {
    Ok(bytes) => { /* use bytes */ }
    Err(DecodeError::InvalidByte(offset, byte)) => eprintln!("bad byte {byte} at {offset}"),
    Err(e) => eprintln!("invalid Base64: {e}"),
}

Paste a failing string into the Base64 validator to see the offending character; the Base64 errors guide goes through the causes.

Pitfalls

  • Old tutorials. encode_config(x, base64::URL_SAFE) and the config constants are pre-0.21 and no longer compile; base64::encode(x) still builds with a deprecation warning. Translate them to URL_SAFE.encode(x) and STANDARD.encode(x).
  • Forgetting the trait import. Without use base64::Engine; (or the prelude), calling .encode on an engine fails with “no method named encode found”.
  • Whitespace. The decoder treats newlines and spaces as invalid bytes. Strip them, for example with s.split_ascii_whitespace().collect::<String>().
  • No MIME helper. The crate does not wrap lines. Split the output into 76-character chunks yourself if you produce email bodies.

Version notes

  • 0.21: Engine API introduced; encode/decode free functions deprecated, *_config functions removed. Configs are GeneralPurposeConfig plus DecodePaddingMode.
  • 0.22: DecodeError::InvalidLength now refers to the number of valid symbols; decode_slice works with exactly sized buffers.
  • 0.23: SIMD engines behind the default simd-unsafe feature, more preconfigured constants, custom padding symbols, MSRV 1.71. The general-purpose engines used on this page are unchanged.

FAQ

Why is base64::encode marked as deprecated?

Since 0.21 the free functions base64::encode and base64::decode are deprecated wrappers (still present in 0.23). Use an engine instead: use base64::prelude::*; then BASE64_STANDARD.encode(data).

Why does the base64 crate reject input without padding?

STANDARD requires canonical padding. Use URL_SAFE_NO_PAD or STANDARD_NO_PAD for unpadded input, or build an engine with DecodePaddingMode::Indifferent to accept both.

Does the base64 crate skip newlines?

No. "aGVs\nbG8K" fails with InvalidByte(4, 10). Remove whitespace before decoding MIME or PEM bodies.

Keep reading