Skip to main content

ct_codecs/
lib.rs

1//! # CT-Codecs
2//!
3//! A Rust implementation of constant-time Base64, Base32, and Hexadecimal codecs,
4//! reimplemented from libsodium and libhydrogen.
5//!
6//! ## Features
7//!
8//! - **Constant-time implementation** for cryptographic applications where timing attacks are a concern
9//! - **Strict validation** ensuring encoded strings are not malleable and use canonical alphabets by default
10//! - **Multiple variants** of Base64: standard, URL-safe, with and without padding
11//! - **Multiple variants** of Base32: standard and hexadecimal alphabets, with and without padding
12//! - **Character filtering** for ignoring specific characters during decoding (like whitespace)
13//! - **Zero dependencies** and **`no_std` compatible**
14//! - **Memory safety** with `#![forbid(unsafe_code)]`
15//!
16//! ## Usage Examples
17//!
18//! ### Base64 Encoding
19//!
20//! ```
21//! use ct_codecs::{Base64, Encoder};
22//!
23//! fn example() -> Result<(), ct_codecs::Error> {
24//!     let data = b"Hello, world!";
25//!     let encoded = Base64::encode_to_string(data)?;
26//!     assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ==");
27//!     Ok(())
28//! }
29//! # example().unwrap();
30//! ```
31//!
32//! ### Base64 Decoding
33//!
34//! ```
35//! use ct_codecs::{Base64, Decoder};
36//!
37//! fn example() -> Result<(), ct_codecs::Error> {
38//!     let encoded = "SGVsbG8sIHdvcmxkIQ==";
39//!     let decoded = Base64::decode_to_vec(encoded, None)?;
40//!     assert_eq!(decoded, b"Hello, world!");
41//!     Ok(())
42//! }
43//! # example().unwrap();
44//! ```
45//!
46//! ### Base32 Encoding
47//!
48//! ```
49//! use ct_codecs::{Base32, Encoder};
50//!
51//! fn example() -> Result<(), ct_codecs::Error> {
52//!     let data = b"foobar";
53//!     let encoded = Base32::encode_to_string(data)?;
54//!     assert_eq!(encoded, "MZXW6YTBOI======");
55//!     Ok(())
56//! }
57//! # example().unwrap();
58//! ```
59//!
60//! ### Base32 Decoding
61//!
62//! ```
63//! use ct_codecs::{Base32, Decoder};
64//!
65//! fn example() -> Result<(), ct_codecs::Error> {
66//!     let encoded = "MZXW6YTBOI======";
67//!     let decoded = Base32::decode_to_vec(encoded, None)?;
68//!     assert_eq!(decoded, b"foobar");
69//!     Ok(())
70//! }
71//! # example().unwrap();
72//! ```
73//!
74//! ### Hexadecimal Encoding/Decoding
75//!
76//! ```
77//! use ct_codecs::{Hex, Encoder, Decoder};
78//!
79//! fn example() -> Result<(), ct_codecs::Error> {
80//!     let data = b"Hello, world!";
81//!     let encoded = Hex::encode_to_string(data)?;
82//!     let decoded = Hex::decode_to_vec(&encoded, None)?;
83//!     assert_eq!(decoded, data);
84//!     Ok(())
85//! }
86//! # example().unwrap();
87//! ```
88//!
89//! ### No-std Usage with Pre-allocated Buffers
90//!
91//! ```
92//! use ct_codecs::{Base64, Encoder, Decoder};
93//!
94//! fn example() -> Result<(), ct_codecs::Error> {
95//!     let data = b"Hello, world!";
96//!     let mut encoded_buf = [0u8; 20]; // Must be large enough
97//!     let encoded = Base64::encode(&mut encoded_buf, data)?;
98//!     
99//!     let mut decoded_buf = [0u8; 13]; // Must be large enough
100//!     let decoded = Base64::decode(&mut decoded_buf, encoded, None)?;
101//!     assert_eq!(decoded, data);
102//!     Ok(())
103//! }
104//! # example().unwrap();
105//! ```
106
107#![cfg_attr(not(feature = "std"), no_std)]
108#![forbid(unsafe_code)]
109
110mod base32;
111mod base64;
112mod error;
113mod hex;
114
115pub use base32::*;
116pub use base64::*;
117pub use error::*;
118pub use hex::*;
119
120/// Trait for encoding binary data into text representations.
121///
122/// Implementors of this trait provide constant-time encoding operations
123/// for a specific encoding format (Base64, Hex, etc.).
124pub trait Encoder {
125    /// Calculates the length of the encoded output for a given binary input length.
126    ///
127    /// # Arguments
128    ///
129    /// * `bin_len` - The length of the binary input in bytes
130    ///
131    /// # Returns
132    ///
133    /// * `Ok(usize)` - The required length for the encoded output
134    /// * `Err(Error::Overflow)` - If the calculation would overflow
135    fn encoded_len(bin_len: usize) -> Result<usize, Error>;
136
137    /// Encodes binary data into a text representation.
138    ///
139    /// # Arguments
140    ///
141    /// * `encoded` - Mutable buffer to store the encoded output
142    /// * `bin` - Binary input data to encode
143    ///
144    /// # Returns
145    ///
146    /// * `Ok(&[u8])` - A slice of the encoded buffer containing the encoded data
147    /// * `Err(Error::Overflow)` - If the output buffer is too small
148    fn encode<IN: AsRef<[u8]>>(encoded: &mut [u8], bin: IN) -> Result<&[u8], Error>;
149
150    /// Encodes binary data and returns the result as a string slice.
151    ///
152    /// # Arguments
153    ///
154    /// * `encoded` - Mutable buffer to store the encoded output
155    /// * `bin` - Binary input data to encode
156    ///
157    /// # Returns
158    ///
159    /// * `Ok(&str)` - A string slice containing the encoded data
160    /// * `Err(Error::Overflow)` - If the output buffer is too small
161    fn encode_to_str<IN: AsRef<[u8]>>(encoded: &mut [u8], bin: IN) -> Result<&str, Error> {
162        Ok(core::str::from_utf8(Self::encode(encoded, bin)?).unwrap())
163    }
164
165    /// Encodes binary data and returns the result as a String.
166    ///
167    /// This method is only available when the `std` feature is enabled.
168    ///
169    /// # Arguments
170    ///
171    /// * `bin` - Binary input data to encode
172    ///
173    /// # Returns
174    ///
175    /// * `Ok(String)` - A String containing the encoded data
176    /// * `Err(Error::Overflow)` - If the calculation would overflow
177    #[cfg(feature = "std")]
178    fn encode_to_string<IN: AsRef<[u8]>>(bin: IN) -> Result<String, Error> {
179        let mut encoded = vec![0u8; Self::encoded_len(bin.as_ref().len())?];
180        let encoded_len = Self::encode(&mut encoded, bin)?.len();
181        encoded.truncate(encoded_len);
182        Ok(String::from_utf8(encoded).unwrap())
183    }
184}
185
186/// Trait for decoding text representations back into binary data.
187///
188/// Implementors of this trait provide constant-time decoding operations
189/// for a specific encoding format (Base64, Hex, etc.). By default,
190/// decoders require the canonical alphabet for the selected variant;
191/// only bytes explicitly listed in `ignore` are skipped.
192pub trait Decoder {
193    /// Decodes text data back into its binary representation.
194    ///
195    /// This method rejects non-canonical encodings, invalid padding,
196    /// and characters outside the selected variant's alphabet unless
197    /// they are explicitly listed in `ignore`.
198    ///
199    /// # Arguments
200    ///
201    /// * `bin` - Mutable buffer to store the decoded output
202    /// * `encoded` - Text input data to decode
203    /// * `ignore` - Optional set of characters to ignore during decoding (e.g., whitespace)
204    ///
205    /// # Returns
206    ///
207    /// * `Ok(&[u8])` - A slice of the binary buffer containing the decoded data
208    /// * `Err(Error::Overflow)` - If the output buffer is too small
209    /// * `Err(Error::InvalidInput)` - If the input contains invalid characters
210    fn decode<'t, IN: AsRef<[u8]>>(
211        bin: &'t mut [u8],
212        encoded: IN,
213        ignore: Option<&[u8]>,
214    ) -> Result<&'t [u8], Error>;
215
216    /// Decodes text data and returns the result as a Vec<u8>.
217    ///
218    /// This method is only available when the `std` feature is enabled.
219    ///
220    /// # Arguments
221    ///
222    /// * `encoded` - Text input data to decode
223    /// * `ignore` - Optional set of characters to ignore during decoding (e.g., whitespace)
224    ///
225    /// # Returns
226    ///
227    /// * `Ok(Vec<u8>)` - A Vec containing the decoded binary data
228    /// * `Err(Error::InvalidInput)` - If the input contains invalid characters
229    #[cfg(feature = "std")]
230    fn decode_to_vec<IN: AsRef<[u8]>>(
231        encoded: IN,
232        ignore: Option<&[u8]>,
233    ) -> Result<Vec<u8>, Error> {
234        let mut bin = vec![0u8; encoded.as_ref().len()];
235        let bin_len = Self::decode(&mut bin, encoded, ignore)?.len();
236        bin.truncate(bin_len);
237        Ok(bin)
238    }
239}
240
241/// Constant-time equality check for two byte slices.
242///
243/// # Arguments
244///
245/// * `x` - First byte slice
246/// * `y` - Second byte slice
247///
248/// # Returns
249///
250/// * `bool` - `true` if the slices are equal, `false` otherwise
251#[inline(never)]
252pub fn verify(x: &[u8], y: &[u8]) -> bool {
253    if x.len() != y.len() {
254        return false;
255    }
256    let mut v: u32 = 0;
257    // Old Rust versions, don't have black_box(), using volatile is unsafe,
258    // and WebAssembly doesn't support volatile and ignores black_box() anyway.
259    // So, add an extra layer of compiler confusion.
260    let (mut h1, mut h2) = (0u32, 0u32);
261    for (b1, b2) in x.iter().zip(y.iter()) {
262        h1 ^= (h1 << 5).wrapping_add((h1 >> 2) ^ *b1 as u32);
263        h2 ^= (h2 << 5).wrapping_add((h2 >> 2) ^ *b2 as u32);
264    }
265    v |= h1 ^ h2;
266    for (a, b) in x.iter().zip(y.iter()) {
267        v |= (a ^ b) as u32;
268    }
269    v == 0
270}