Expand description
Good old fashioned base64 encoder and decoder.
It should just work the way you expect: encode takes any bytes-like rust type
and returns a String, while decode takes a String (which can be in any bytes-like container)
and returns a Vec<u8>.
use bouncycastle_base64 as base64;
let out = base64::encode(b"\x00"); // "AA=="
let out = base64::encode(b"Hello, World!"); // "SGVsbG8sIFdvcmxkIQ=="
let out = base64::encode(b"\x00\x01\x02\x03\x04\x05\x06"); // "AAECAwQFBg=="
let out = base64::decode("AA==").unwrap(); // b"\x00"
let out = base64::decode("SGVsbG8sIFdvcmxkIQ==").unwrap(); // b"Hello, World!"
let out = base64::decode("AAECAwQFBg==").unwrap(); // b"\x00\x01\x02\x03\x04\x05\x06"
// note that the decoder automatically ignores whitespace in the b64 input
let out1 = base64::decode("AAEC Aw QFB\ng==").unwrap(); // b"\x00\x01\x02\x03\x04\x05\x06"
assert_eq!(out, out1);
// it is also tolerant of missing padding characters
let out = base64::decode("AAECAwQFBg==").unwrap(); // b"\x00\x01\x02\x03\x04\x05\x06"
let out1 = base64::decode("AAECAwQFBg=").unwrap(); // b"\x00\x01\x02\x03\x04\x05\x06"
assert_eq!(out, out1);
let out2 = base64::decode("AAECAwQFBg").unwrap(); // b"\x00\x01\x02\x03\x04\x05\x06"
assert_eq!(out, out2);§Streaming
Unlike Hex, Base64 does not align cleanly to byte boundaries.
That means that the above one-shot APIs should only be used if you have the entire content to
process at the same time.
In other words, if you arbitrarily break your data into chunks and hand it to the one-shot encode and decode APIs,
you will get incorrect results.
If you need to process your data in chunks, you need to use the streaming API that allows
repeated calls to do_update, producing output as it goes, and correctly holds on to the unprocessed
partial block until either do_update or do_final is called.
use bouncycastle_base64 as base64;
let mut b64_str: String = String::new();
let mut encoder = base64::Base64Encoder::new();
b64_str.push_str( encoder.do_update(b"Hello,").as_str() );
b64_str.push_str( encoder.do_final(b" World!").as_str() );
assert_eq!(b64_str, "SGVsbG8sIFdvcmxkIQ==");
let mut out_bytes = Vec::<u8>::new();
let mut decoder = base64::Base64Decoder::new(/*skip_whitespace*/ false);
out_bytes.extend( decoder.do_update("SGVs").unwrap() );
out_bytes.extend( decoder.do_final("bG8sIFdvcmxkIQ==").unwrap() );
assert_eq!(out_bytes, b"Hello, World!");§Security and constant-time
The following paper proves that extremely clever attack algorithms exist to recover private keys if the attacker is allowed to observe closely side-channels of the base64 decode process.
Util::Lookup: Exploiting key decoding in cryptographic libraries (Sieck, 2021),
As this is a cryptography library, we are assuming that this base64 implementation will be used to encode and decode private keys in PEM and JWK formats and so we are only providing a constant-time implementation in order to remove the temptation to shoot yourself in the foot in the name of a small performance gain.
In our testing, a naïve lookup table-based implementation of base64::decode was 1.7x faster than our constant-time implementation, and we are quite sure that optimized base64 implementations exist that provide still better performance. So if you find yourself in a position of needing to base64 encode gigabytes of non-sensitive data, then we recommend you use one of the good, fast, but non-constant-time base64 implementations available from other projects.
§Alphabets:
At the present time, this base64 implementation only supports the standard alphabet with “+” and “/”, specifically:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=but additional alphabets such as the URLSafe alphabet will likely be added in future versions.
Structs§
- Base64
Decoder - The stateful base64 decoder that supports streaming.
- Base64
Encoder - The stateful base64 encoder that supports streaming.