crypto: use constant-time equality for Poly1305 tags
What changed, and why it matters
This commit fixes a timing attack weakness in the ChaCha20-Poly1305 decryption code. Previously, the code compared the authentication tag using Rust's normal `==` operator, which can stop early when it finds a mismatch. That early-stop behavior can leak information about how much of the tag is correct if an attacker can measure tiny timing differences. The patch replaces it with a constant-time comparison that always scans all 16 bytes, so no timing information about the tag's contents is revealed.
Review the new `constant_time_eq` implementation to confirm the compiler does not optimize away the loop or short-circuit the OR accumulation under release builds; consider adding a `black_box`-style barrier or using a well-reviewed constant-time crate if stronger guarantees are needed. Ensure the fix is included in the next release and that issue #6122 is closed with an advisory note.
Security signals we found
Timing side-channel in cryptographic tag comparison
Use of non-constant-time equality (`==`) on Poly1305 authentication tag
Introduction of constant-time equality helper
Fixes issue #6122
Evidence from the diff
In chacha20_poly1305/src/lib.rs, the ChaCha20Poly1305::decrypt method previously verified the Poly1305 tag with if derived_tag == tag. Rust’s equality for arrays is not guaranteed to be constant-time; a mismatching byte can cause an early return, creating a timing side-channel. The patch introduces constant_time_eq(a: &[u8; 16], b: &[u8; 16]) -> bool, which XORs each byte pair, ORs the results, and only checks whether the accumulator is zero after the full loop. This is a textbook constant-time comparison. A unit test verifies equal tags, fully different tags, and single-byte differences at the first, middle, and last positions.
Changed components
chacha20_poly1305/src/lib.rsChaCha20Poly1305::decryptInspect captured patch +44 / −1
diff --git a/chacha20_poly1305/src/lib.rs b/chacha20_poly1305/src/lib.rs
index 4473f837..b37ce839 100644
--- a/chacha20_poly1305/src/lib.rs
+++ b/chacha20_poly1305/src/lib.rs
@@ -130,7 +130,8 @@ impl ChaCha20Poly1305 {
let len_buffer = encode_lengths(aad.len() as u64, content.len() as u64);
poly.input(&len_buffer);
let derived_tag = poly.tag();
- if derived_tag == tag {
+
+ if constant_time_eq(&derived_tag, &tag) {
let mut chacha = ChaCha20::new_from_block(self.key, self.nonce, 1);
chacha.apply_keystream(content);
Ok(())
@@ -140,6 +141,17 @@ impl ChaCha20Poly1305 {
}
}
+/// Performs a constant-time equality check between two 16-byte arrays.
+/// ensuring that the comparison time does not leak information about the contents.
+#[inline]
+fn constant_time_eq(a: &[u8; 16], b: &[u8; 16]) -> bool {
+ let mut res = 0u8;
+ for (x, y) in a.iter().zip(b.iter()) {
+ res |= x ^ y;
+ }
+ res == 0
+}
+
/// AAD and content lengths are each encoded in 8-bytes.
fn encode_lengths(aad_len: u64, content_len: u64) -> [u8; 16] {
let aad_len_bytes = aad_len.to_le_bytes();
@@ -212,4 +224,35 @@ mod tests {
assert_eq!(&buffer.to_lower_hex_string(), "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691");
}
+
+ #[cfg(not(chacha20_poly1305_fuzz))]
+ #[test]
+ fn test_constant_time_eq() {
+ let tag_a = [0x42u8; 16];
+ let tag_b = [0x42u8; 16];
+ let tag_c = [0x00u8; 16];
+
+ // full equality
+ assert!(constant_time_eq(&tag_a, &tag_b));
+
+ // full difference
+ assert!(!constant_time_eq(&tag_a, &tag_c));
+
+ // edge case - single byte diff
+ let mut tag_d = tag_a;
+
+ // first byte diff
+ tag_d[0] ^= 1;
+ assert!(!constant_time_eq(&tag_a, &tag_d));
+
+ // last byte only diff
+ tag_d = tag_a;
+ tag_d[15] ^= 1;
+ assert!(!constant_time_eq(&tag_a, &tag_d));
+
+ // mid byte diff
+ tag_d = tag_a;
+ tag_d[7] ^= 0xff;
+ assert!(!constant_time_eq(&tag_a, &tag_d));
+ }
}
Why this scored 60/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.