Skip to main content

rama_utils/bytes/
ct.rs

1//! Constant-time byte-slice comparison.
2//!
3//! `==` on `&[u8]` short-circuits on the first mismatching byte, which lets
4//! an attacker who can observe comparison latency probe a secret byte by
5//! byte. The helpers below always inspect the full shorter slice and then
6//! fold the length check into the result, so the time taken depends only on
7//! the lengths of the inputs (not on where they differ).
8//!
9//! Primary use is comparing credential blobs (HTTP Basic, Bearer tokens,
10//! API keys); see `rama-net::user::credentials` for the consumers.
11
12/// Constant-time equality for two byte slices.
13///
14/// Compares every byte of the shorter slice — the time taken depends only on
15/// `min(a.len(), b.len())` and on whether the lengths match, never on the
16/// position of the first mismatching byte.
17///
18/// Leaking the *length* of the secret is unavoidable in HTTP Basic Auth
19/// (the credentials live in a fixed-length header), and any attempt to hide
20/// the length would either dilate runtime for legitimate requests or still
21/// be observable. What this protects against is the byte-wise prefix
22/// oracle.
23#[inline]
24pub fn ct_eq_bytes(a: &[u8], b: &[u8]) -> bool {
25    if a.len() != b.len() {
26        return false;
27    }
28    let mut diff: u8 = 0;
29    for (x, y) in a.iter().zip(b.iter()) {
30        diff |= x ^ y;
31    }
32    // `black_box` discourages the optimizer from turning the OR-reduction
33    // back into a short-circuiting compare.
34    core::hint::black_box(diff) == 0
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn empty_inputs_are_equal() {
43        assert!(ct_eq_bytes(b"", b""));
44    }
45
46    #[test]
47    fn equal_bytes_compare_equal() {
48        assert!(ct_eq_bytes(b"secret", b"secret"));
49    }
50
51    #[test]
52    fn different_bytes_compare_unequal() {
53        assert!(!ct_eq_bytes(b"secret", b"sxcret"));
54        assert!(!ct_eq_bytes(b"secret", b"secrex"));
55    }
56
57    #[test]
58    fn different_lengths_compare_unequal() {
59        assert!(!ct_eq_bytes(b"secret", b"secrets"));
60        assert!(!ct_eq_bytes(b"", b"x"));
61    }
62}