Skip to main content

cryptography/modes/
poly1305.rs

1//! Poly1305 one-time authenticator (RFC 8439 / RFC 7539 profile).
2//!
3//! This implementation uses the standard 32-byte one-time key split into
4//! `r || s`, applies RFC clamping to `r`, and computes tags modulo 2^130-5.
5
6use crate::public_key::bigint::BigUint;
7
8#[inline]
9fn le_bytes_to_biguint(bytes: &[u8]) -> BigUint {
10    let mut be = bytes.to_vec();
11    be.reverse();
12    BigUint::from_be_bytes(&be)
13}
14
15#[inline]
16fn biguint_to_16_le(value: &BigUint) -> [u8; 16] {
17    let be = value.to_be_bytes();
18    let mut out = [0u8; 16];
19    if be.len() >= 16 {
20        out.copy_from_slice(&be[be.len() - 16..]);
21    } else {
22        out[16 - be.len()..].copy_from_slice(&be);
23    }
24    out.reverse();
25    out
26}
27
28/// Compute a Poly1305 tag over `msg` with the given one-time `key`.
29///
30/// The key must be unique per message under a fixed long-term secret.
31pub fn poly1305_mac(msg: &[u8], key: &[u8; 32]) -> [u8; 16] {
32    let mut r = [0u8; 16];
33    r.copy_from_slice(&key[..16]);
34
35    // RFC 8439 ยง2.5.1 clamp for r: clear the high nibble of bytes
36    // 3/7/11/15 and the low two bits of bytes 4/8/12.
37    r[3] &= 15;
38    r[7] &= 15;
39    r[11] &= 15;
40    r[15] &= 15;
41    r[4] &= 252;
42    r[8] &= 252;
43    r[12] &= 252;
44
45    let r_big = le_bytes_to_biguint(&r);
46    let s_big = le_bytes_to_biguint(&key[16..32]);
47
48    let mut p = BigUint::one();
49    p.shl_bits(130);
50    p = p.sub_ref(&BigUint::from_u64(5));
51
52    let mut acc = BigUint::zero();
53    for chunk in msg.chunks(16) {
54        let mut block = Vec::with_capacity(chunk.len() + 1);
55        block.extend_from_slice(chunk);
56        block.push(1);
57        let n = le_bytes_to_biguint(&block);
58        acc = acc.add_ref(&n).modulo(&p);
59        acc = BigUint::mod_mul(&acc, &r_big, &p);
60    }
61
62    let mut mod_2_128 = BigUint::one();
63    mod_2_128.shl_bits(128);
64    let tag = acc.add_ref(&s_big).modulo(&mod_2_128);
65    biguint_to_16_le(&tag)
66}
67
68/// Poly1305 state wrapper for repeated message authentication with a fixed
69/// one-time key.
70pub struct Poly1305 {
71    key: [u8; 32],
72}
73
74impl Poly1305 {
75    /// Construct a Poly1305 context from a one-time key.
76    #[must_use]
77    pub fn new(key: &[u8; 32]) -> Self {
78        Self { key: *key }
79    }
80
81    /// Construct a Poly1305 context and wipe the caller-provided key bytes.
82    pub fn new_wiping(key: &mut [u8; 32]) -> Self {
83        let out = Self::new(key);
84        crate::ct::zeroize_slice(key.as_mut_slice());
85        out
86    }
87
88    /// Compute a Poly1305 tag over `msg`.
89    #[must_use]
90    pub fn compute(&self, msg: &[u8]) -> [u8; 16] {
91        poly1305_mac(msg, &self.key)
92    }
93
94    /// Verify a Poly1305 tag in constant time.
95    #[must_use]
96    pub fn verify(&self, msg: &[u8], tag: &[u8; 16]) -> bool {
97        crate::ct::constant_time_eq_mask(&self.compute(msg), tag) == u8::MAX
98    }
99}
100
101impl Drop for Poly1305 {
102    fn drop(&mut self) {
103        crate::ct::zeroize_slice(self.key.as_mut_slice());
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::{poly1305_mac, Poly1305};
110
111    fn unhex(input: &str) -> Vec<u8> {
112        let mut out = Vec::with_capacity(input.len() / 2);
113        let bytes = input.as_bytes();
114        let mut i = 0usize;
115        while i + 1 < bytes.len() {
116            let hi = (bytes[i] as char).to_digit(16).expect("hex") as u8;
117            let lo = (bytes[i + 1] as char).to_digit(16).expect("hex") as u8;
118            out.push((hi << 4) | lo);
119            i += 2;
120        }
121        out
122    }
123
124    #[test]
125    fn rfc8439_poly1305_vector() {
126        let key = <[u8; 32]>::try_from(unhex(
127            "85d6be7857556d337f4452fe42d506a8\
128             0103808afb0db2fd4abff6af4149f51b",
129        ))
130        .expect("key");
131        let msg = b"Cryptographic Forum Research Group";
132        let expected =
133            <[u8; 16]>::try_from(unhex("a8061dc1305136c6c22b8baf0c0127a9")).expect("tag");
134        assert_eq!(poly1305_mac(msg, &key), expected);
135    }
136
137    #[test]
138    fn wrapper_verify_roundtrip() {
139        let key = [0x11u8; 32];
140        let mac = Poly1305::new(&key);
141        let msg = b"poly1305 message";
142        let tag = mac.compute(msg);
143        assert!(mac.verify(msg, &tag));
144
145        let mut tampered = tag;
146        tampered[0] ^= 0x80;
147        assert!(!mac.verify(msg, &tampered));
148    }
149}