Skip to main content

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::fill()`.
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 200-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        self.st[4] ^= block_offset;
72        let mask = self.st;
73        self.permute();
74        for (x, mask) in self.st.iter_mut().zip(mask) {
75            *x ^= mask;
76        }
77        for i in 0..25 {
78            out[i * 8..][..8].copy_from_slice(&self.st[i].to_le_bytes());
79        }
80    }
81
82    /// Squeeze a 200-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        self.st[4] ^= block_offset;
86        let mask = self.st;
87        self.permute();
88        for (x, mask) in self.st.iter_mut().zip(mask) {
89            *x ^= mask;
90        }
91        for i in 0..25 {
92            let x = u64::from_le_bytes(out[i * 8..][..8].try_into().unwrap());
93            out[i * 8..][..8].copy_from_slice(&(self.st[i] ^ x).to_le_bytes());
94        }
95    }
96
97    /// Squeeze and return a 200-byte block.
98    #[inline(always)]
99    fn squeeze_rate(self, block_offset: u64) -> [u8; 200] {
100        let mut out = [0u8; 200];
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 / 200;
115        let offset_in_first_block = (start_offset % 200) as usize;
116        if offset_in_first_block != 0 {
117            let bytes_to_copy = cmp::min(200 - offset_in_first_block, out.len());
118            if bytes_to_copy > 0 {
119                let rate = self.squeeze_rate(block_offset);
120                out[..bytes_to_copy]
121                    .copy_from_slice(&rate[offset_in_first_block..][..bytes_to_copy]);
122                out = &mut out[bytes_to_copy..];
123            }
124            block_offset += 1;
125        }
126        while out.len() >= 200 {
127            self.store_rate(&mut out[..200], block_offset);
128            block_offset += 1;
129            out = &mut out[200..];
130        }
131        if !out.is_empty() {
132            let rate = self.squeeze_rate(block_offset);
133            out.copy_from_slice(&rate[..out.len()]);
134        }
135        Ok(())
136    }
137
138    /// Encrypt or decrypt the given buffer in place, given the offset.
139    ///
140    /// The buffer is modified in place.
141    /// The offset is in bytes.
142    ///
143    /// The key stream is deterministic: the same key, context and offset will always produce the same output.
144    /// This function is equivalent to calling `fill` and then XORing the output with the input.
145    ///
146    /// # Caveats
147    ///
148    /// * There is no integrity.
149    /// * An adversary can flip arbitrary bits in the ciphertext and the corresponding bits in the plaintext will be flipped when decrypted.
150    pub fn apply_keystream(
151        &self,
152        mut out: &mut [u8],
153        start_offset: u64,
154    ) -> Result<(), &'static str> {
155        if start_offset.checked_add(out.len() as u64).is_none() {
156            return Err("offset would overflow");
157        }
158        let mut block_offset = start_offset / 200;
159        let offset_in_first_block = (start_offset % 200) as usize;
160        if offset_in_first_block != 0 {
161            let bytes_to_copy = cmp::min(200 - offset_in_first_block, out.len());
162            if bytes_to_copy > 0 {
163                let rate = self.squeeze_rate(block_offset);
164                for i in 0..bytes_to_copy {
165                    out[i] ^= rate[offset_in_first_block + i];
166                }
167                out = &mut out[bytes_to_copy..];
168            }
169            block_offset += 1;
170        }
171        while out.len() >= 200 {
172            self.apply_rate(&mut out[..200], block_offset);
173            block_offset += 1;
174            out = &mut out[200..];
175        }
176        if !out.is_empty() {
177            let rate = self.squeeze_rate(block_offset);
178            for i in 0..out.len() {
179                out[i] ^= rate[i];
180            }
181        }
182        Ok(())
183    }
184
185    fn permute(&mut self) {
186        keccak::Keccak::new().with_p1600::<12>(|f| f(&mut self.st));
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn test_keccak() {
196        let mut key = [0u8; StreamCipher::KEY_LENGTH];
197        getrandom::fill(&mut key).unwrap();
198
199        let st = StreamCipher::new(&key, b"test");
200
201        let mut out = [0u8; 10000];
202        st.apply_keystream(&mut out, 10).unwrap();
203
204        let mut out2 = [0u8; 10000];
205        st.fill(&mut out2, 10).unwrap();
206
207        assert_eq!(out, out2);
208
209        st.fill(&mut out2, 11).unwrap();
210        assert_eq!(out[1..], out2[0..out2.len() - 1]);
211
212        out.fill(0);
213        st.apply_keystream(&mut out, 0).unwrap();
214        st.fill(&mut out2, 0).unwrap();
215        assert_eq!(out, out2);
216
217        st.fill(&mut out, 200).unwrap();
218        assert_eq!(out[..out2.len() - 200], out2[200..]);
219    }
220
221    #[test]
222    fn test_large_context() {
223        let mut key = [0u8; StreamCipher::KEY_LENGTH];
224        getrandom::fill(&mut key).unwrap();
225        let context = [0u8; 10000];
226        let _ = StreamCipher::new(&key, context);
227    }
228}