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}