Skip to main content

dynomite/crypto/
base64.rs

1//! Base64 encoding helpers.
2//!
3//! Wraps the workspace `base64` crate with the standard alphabet.
4//! The reference engine emits padded output (it calls OpenSSL's
5//! `BIO_f_base64()` with `BIO_FLAGS_BASE64_NO_NL`, which suppresses
6//! embedded newlines but keeps the trailing `=` padding); this
7//! module preserves that behavior. Decoding accepts both padded and
8//! unpadded inputs.
9
10use base64::engine::general_purpose::STANDARD;
11use base64::engine::general_purpose::STANDARD_NO_PAD;
12use base64::Engine;
13
14use crate::crypto::CryptoError;
15
16/// Encode `bytes` to a base64 string using the standard alphabet
17/// with trailing `=` padding (RFC 4648).
18///
19/// # Examples
20///
21/// ```
22/// use dynomite::crypto::base64_encode;
23/// assert_eq!(base64_encode(b"hi"), "aGk=");
24/// assert_eq!(base64_encode(b"Hello"), "SGVsbG8=");
25/// assert_eq!(base64_encode(b""), "");
26/// ```
27pub fn base64_encode(bytes: &[u8]) -> String {
28    STANDARD.encode(bytes)
29}
30
31/// Decode a base64 string. Accepts both padded and unpadded inputs
32/// using the standard alphabet.
33///
34/// # Examples
35///
36/// ```
37/// use dynomite::crypto::base64_decode;
38/// assert_eq!(base64_decode("aGk=").unwrap(), b"hi");
39/// assert_eq!(base64_decode("aGk").unwrap(), b"hi");
40/// assert!(base64_decode("not base64!@#").is_err());
41/// ```
42pub fn base64_decode(s: &str) -> Result<Vec<u8>, CryptoError> {
43    if s.contains('=') {
44        STANDARD
45            .decode(s.as_bytes())
46            .map_err(|e| CryptoError::Base64(e.to_string()))
47    } else {
48        STANDARD_NO_PAD
49            .decode(s.as_bytes())
50            .map_err(|e| CryptoError::Base64(e.to_string()))
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn empty_round_trip() {
60        assert_eq!(base64_encode(b""), "");
61        assert_eq!(base64_decode("").unwrap(), Vec::<u8>::new());
62    }
63
64    #[test]
65    fn standard_vectors() {
66        // RFC 4648 test vectors with padding.
67        assert_eq!(base64_encode(b"f"), "Zg==");
68        assert_eq!(base64_encode(b"fo"), "Zm8=");
69        assert_eq!(base64_encode(b"foo"), "Zm9v");
70        assert_eq!(base64_encode(b"foob"), "Zm9vYg==");
71        assert_eq!(base64_encode(b"fooba"), "Zm9vYmE=");
72        assert_eq!(base64_encode(b"foobar"), "Zm9vYmFy");
73    }
74
75    #[test]
76    fn unpadded_decodes_too() {
77        assert_eq!(base64_decode("Zg").unwrap(), b"f");
78        assert_eq!(base64_decode("Zm8").unwrap(), b"fo");
79    }
80
81    #[test]
82    fn invalid_input_errors() {
83        assert!(base64_decode("@@@").is_err());
84        assert!(base64_decode("####").is_err());
85    }
86}