seekable_stream_cipher/
keccak.rs

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