Skip to main content

seekable_stream_cipher/
ascon.rs

1use core::cmp;
2
3/// An ASCON-based seekable stream cipher.
4#[derive(Clone, Copy)]
5pub struct StreamCipher {
6    /// The ASCON state
7    st: [u64; 5],
8}
9
10impl StreamCipher {
11    /// The key length in bytes
12    pub const KEY_LENGTH: usize = 32;
13
14    /// The ASCON constants
15    const RKS: [u64; 12] = [
16        0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b,
17    ];
18
19    /// Create a new state with the given key and context.
20    ///
21    /// The key must be 32 bytes long, and must be randomly generated, for example using
22    /// `rand::thread_rng().gen::<[u8; 32]>()` or `getrandom::fill()`.
23    ///
24    /// The context is optional can be of any length. It is used to improve multi-user security.
25    pub fn new(key: &[u8; Self::KEY_LENGTH], context: impl AsRef<[u8]>) -> Self {
26        let context = context.as_ref();
27        let st = [0x010080cc00000000, 0, 0, 0, 0];
28
29        let mut state = StreamCipher { st };
30        state.st[1] ^= u64::from_le_bytes(key[0..8].try_into().unwrap());
31        state.st[2] ^= u64::from_le_bytes(key[8..16].try_into().unwrap());
32        state.st[3] ^= u64::from_le_bytes(key[16..24].try_into().unwrap());
33        state.st[4] ^= u64::from_le_bytes(key[24..32].try_into().unwrap());
34        state.permute();
35
36        let mut context = context;
37
38        while context.len() > 32 {
39            let context_part_len = 32;
40            state.st[0] ^= u64::from_le_bytes(context[0..8].try_into().unwrap());
41            state.st[1] ^= u64::from_le_bytes(context[8..16].try_into().unwrap());
42            state.st[2] ^= u64::from_le_bytes(context[16..24].try_into().unwrap());
43            state.st[3] ^= u64::from_le_bytes(context[24..32].try_into().unwrap());
44            context = &context[context_part_len..];
45            state.permute();
46        }
47
48        let context_len = context.len();
49        let mut buf = [0u8; 32];
50        buf[..context_len].copy_from_slice(context);
51        state.st[0] ^= u64::from_le_bytes(buf[0..8].try_into().unwrap());
52        state.st[1] ^= u64::from_le_bytes(buf[8..16].try_into().unwrap());
53        state.st[2] ^= u64::from_le_bytes(buf[16..24].try_into().unwrap());
54        state.st[3] ^= u64::from_le_bytes(buf[24..32].try_into().unwrap());
55        state.st[4] ^= 0x01;
56        state.permute();
57
58        state.st[0] ^= u64::from_le_bytes(key[0..8].try_into().unwrap());
59        state.st[1] ^= u64::from_le_bytes(key[8..16].try_into().unwrap());
60        state.st[2] ^= u64::from_le_bytes(key[16..24].try_into().unwrap());
61        state.st[3] ^= u64::from_le_bytes(key[24..32].try_into().unwrap());
62
63        state
64    }
65
66    /// Squeeze a 40-byte block, and store it in the given buffer.
67    #[inline(always)]
68    fn store_rate(mut self, out: &mut [u8], block_offset: u64) {
69        self.st[4] ^= block_offset;
70        let mask = self.st;
71        self.permute();
72        for (x, mask) in self.st.iter_mut().zip(mask) {
73            *x ^= mask;
74        }
75        for i in 0..5 {
76            out[i * 8..][..8].copy_from_slice(&self.st[i].to_le_bytes());
77        }
78    }
79
80    /// Squeeze a 40-byte block, and add it to the given buffer.
81    #[inline(always)]
82    fn apply_rate(mut self, out: &mut [u8], block_offset: u64) {
83        self.st[4] ^= block_offset;
84        let mask = self.st;
85        self.permute();
86        for (x, mask) in self.st.iter_mut().zip(mask) {
87            *x ^= mask;
88        }
89        for i in 0..5 {
90            let x = u64::from_le_bytes(out[i * 8..][..8].try_into().unwrap());
91            out[i * 8..][..8].copy_from_slice(&(self.st[i] ^ x).to_le_bytes());
92        }
93    }
94
95    /// Squeeze and return a 40-byte block.
96    #[inline(always)]
97    fn squeeze_rate(self, block_offset: u64) -> [u8; 40] {
98        let mut out = [0u8; 40];
99        self.store_rate(&mut out, block_offset);
100        out
101    }
102
103    /// Fill the given buffer with the keystream starting at the given offset.
104    ///
105    /// The offset is in bytes.
106    ///
107    /// The key stream is deterministic: the same key, context and offset will always produce the same output.
108    pub fn fill(&self, mut out: &mut [u8], start_offset: u64) -> Result<(), &'static str> {
109        if start_offset.checked_add(out.len() as u64).is_none() {
110            return Err("offset would overflow");
111        }
112        let mut block_offset = start_offset / 40;
113        let offset_in_first_block = (start_offset % 40) as usize;
114        if offset_in_first_block != 0 {
115            let bytes_to_copy = cmp::min(40 - offset_in_first_block, out.len());
116            if bytes_to_copy > 0 {
117                let rate = self.squeeze_rate(block_offset);
118                out[..bytes_to_copy]
119                    .copy_from_slice(&rate[offset_in_first_block..][..bytes_to_copy]);
120                out = &mut out[bytes_to_copy..];
121            }
122            block_offset += 1;
123        }
124        while out.len() >= 40 {
125            self.store_rate(&mut out[..40], block_offset);
126            block_offset += 1;
127            out = &mut out[40..];
128        }
129        if !out.is_empty() {
130            let rate = self.squeeze_rate(block_offset);
131            out.copy_from_slice(&rate[..out.len()]);
132        }
133        Ok(())
134    }
135
136    /// Encrypt or decrypt the given buffer in place, given the offset.
137    ///
138    /// The buffer is modified in place.
139    /// The offset is in bytes.
140    ///
141    /// The key stream is deterministic: the same key, context and offset will always produce the same output.
142    /// This function is equivalent to calling `fill` and then XORing the output with the input.
143    ///
144    /// # Caveats
145    ///
146    /// * There is no integrity.
147    /// * An adversary can flip arbitrary bits in the ciphertext and the corresponding bits in the plaintext will be flipped when decrypted.
148    pub fn apply_keystream(
149        &self,
150        mut out: &mut [u8],
151        start_offset: u64,
152    ) -> Result<(), &'static str> {
153        if start_offset.checked_add(out.len() as u64).is_none() {
154            return Err("offset would overflow");
155        }
156        let mut block_offset = start_offset / 40;
157        let offset_in_first_block = (start_offset % 40) as usize;
158        if offset_in_first_block != 0 {
159            let bytes_to_copy = cmp::min(40 - offset_in_first_block, out.len());
160            if bytes_to_copy > 0 {
161                let rate = self.squeeze_rate(block_offset);
162                for i in 0..bytes_to_copy {
163                    out[i] ^= rate[offset_in_first_block + i];
164                }
165                out = &mut out[bytes_to_copy..];
166            }
167            block_offset += 1;
168        }
169        while out.len() >= 40 {
170            self.apply_rate(&mut out[..40], block_offset);
171            block_offset += 1;
172            out = &mut out[40..];
173        }
174        if !out.is_empty() {
175            let rate = self.squeeze_rate(block_offset);
176            for i in 0..out.len() {
177                out[i] ^= rate[i];
178            }
179        }
180        Ok(())
181    }
182
183    #[inline(always)]
184    fn round(&mut self, rk: u64) {
185        let x = &mut self.st;
186        x[2] ^= rk;
187
188        x[0] ^= x[4];
189        x[4] ^= x[3];
190        x[2] ^= x[1];
191        let mut t = [
192            x[0] ^ (!x[1] & x[2]),
193            x[1] ^ (!x[2] & x[3]),
194            x[2] ^ (!x[3] & x[4]),
195            x[3] ^ (!x[4] & x[0]),
196            x[4] ^ (!x[0] & x[1]),
197        ];
198        t[1] ^= t[0];
199        t[3] ^= t[2];
200        t[0] ^= t[4];
201
202        x[2] = t[2] ^ t[2].rotate_right(6 - 1);
203        x[3] = t[3] ^ t[3].rotate_right(17 - 10);
204        x[4] = t[4] ^ t[4].rotate_right(41 - 7);
205        x[0] = t[0] ^ t[0].rotate_right(28 - 19);
206        x[1] = t[1] ^ t[1].rotate_right(61 - 39);
207        x[2] = t[2] ^ x[2].rotate_right(1);
208        x[3] = t[3] ^ x[3].rotate_right(10);
209        x[4] = t[4] ^ x[4].rotate_right(7);
210        x[0] = t[0] ^ x[0].rotate_right(19);
211        x[1] = t[1] ^ x[1].rotate_right(39);
212        x[2] = !x[2];
213    }
214
215    fn permute(&mut self) {
216        for &rk in &Self::RKS {
217            self.round(rk);
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_ascon() {
228        let mut key = [0u8; StreamCipher::KEY_LENGTH];
229        getrandom::fill(&mut key).unwrap();
230
231        let st = StreamCipher::new(&key, b"test");
232
233        let mut out = [0u8; 10000];
234        st.apply_keystream(&mut out, 10).unwrap();
235
236        let mut out2 = [0u8; 10000];
237        st.fill(&mut out2, 10).unwrap();
238
239        assert_eq!(out, out2);
240
241        st.fill(&mut out2, 11).unwrap();
242        assert_eq!(out[1..], out2[0..out2.len() - 1]);
243
244        out.fill(0);
245        st.apply_keystream(&mut out, 0).unwrap();
246        st.fill(&mut out2, 0).unwrap();
247        assert_eq!(out, out2);
248
249        st.fill(&mut out, 40).unwrap();
250        assert_eq!(out[..out2.len() - 40], out2[40..]);
251    }
252
253    #[test]
254    fn test_large_context() {
255        let mut key = [0u8; StreamCipher::KEY_LENGTH];
256        getrandom::fill(&mut key).unwrap();
257        let context = [0u8; 10000];
258        let _ = StreamCipher::new(&key, context);
259    }
260}