Skip to main content

keepass_db/protected_stream/
mod.rs

1use num_derive::{FromPrimitive, ToPrimitive};
2use num_traits::FromPrimitive;
3
4use hex_literal::hex;
5
6use ring::digest::{Context, SHA256, SHA512};
7use salsa20::cipher::{KeyIvInit, StreamCipher};
8use salsa20::Key as Salsa20_Key;
9use salsa20::Salsa20;
10
11use generic_array::GenericArray;
12// use chacha20::stream_cipher::{NewStreamCipher, SyncStreamCipher};
13use chacha20::ChaCha20;
14use chacha20::cipher::StreamCipherSeek;
15
16mod arc4variant;
17
18use arc4variant::ArcFourVariant;
19
20#[derive(FromPrimitive, ToPrimitive)]
21pub enum CipherType {
22    Null = 0,
23    ArcFourVariant = 1,
24    Salsa20 = 2,
25    ChaCha20 = 3,
26}
27
28pub enum CipherValue {
29    Null,
30    ArcFourVariant(ArcFourVariant),
31    Salsa20(Salsa20),
32    ChaCha20(ChaCha20),
33}
34
35impl CipherValue {
36    pub fn apply_keystream(&mut self, buf: &mut [u8]) {
37        match self {
38            Self::Null => (),
39            Self::ArcFourVariant(c) => c.gen(buf),
40            Self::Salsa20(c) => c.apply_keystream(buf),
41            Self::ChaCha20(c) => c.apply_keystream(buf),
42        }
43    }
44
45    pub fn apply_keystream_pos(&mut self, buf: &mut [u8], pos: usize) {
46        match self {
47            Self::Null => (),
48            Self::ArcFourVariant(c) => c.seek(pos),
49            Self::Salsa20(c) => c.try_seek(pos as u64).unwrap(),
50            Self::ChaCha20(c) => c.try_seek(pos as u64).unwrap(),
51        }
52        self.apply_keystream(buf);
53    }
54}
55
56impl Default for CipherValue {
57    fn default() -> Self {
58        Self::Null
59    }
60}
61
62#[derive(Debug)]
63pub enum Error {
64    InvalidCipher(u32),
65}
66
67pub fn new_stream(cipher: u32, key: &[u8]) -> Result<CipherValue, Error> {
68    let r#type = CipherType::from_u32(cipher).ok_or(Error::InvalidCipher(cipher))?;
69    Ok(match r#type {
70        CipherType::Null => CipherValue::Null,
71        CipherType::ArcFourVariant => CipherValue::ArcFourVariant(ArcFourVariant::new(key)),
72        CipherType::Salsa20 => {
73            let nonce = hex!("E830094B97205D2A");
74            let mut context = Context::new(&SHA256);
75            context.update(key);
76            let p2_key = context.finish().as_ref().to_owned();
77            let key = Salsa20_Key::from_slice(&p2_key[0..32]);
78            CipherValue::Salsa20(Salsa20::new(&key, &nonce.into()))
79        }
80        CipherType::ChaCha20 => {
81            let mut context = Context::new(&SHA512);
82            context.update(key);
83            let p2_key = context.finish().as_ref().to_owned();
84            let key = GenericArray::from_slice(&p2_key[0..32]);
85            let nonce = GenericArray::from_slice(&p2_key[32..32 + 12]);
86            CipherValue::ChaCha20(ChaCha20::new(&key, &nonce))
87        }
88    })
89}
90
91#[cfg(test)]
92mod tests {
93    use std::convert::TryInto;
94    use num_traits::ToPrimitive;
95
96    use super::*;
97
98    #[test]
99    fn test_null() {
100        let mut c = new_stream(CipherType::Null.to_u32().unwrap(), &[]).unwrap();
101        let mut ciphertext = [0x61, 0x62, 0x63, 0x64];
102        let expected = "abcd";
103        c.apply_keystream(&mut ciphertext);
104        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
105        assert_eq!(actual, expected);
106
107        c.apply_keystream_pos(&mut ciphertext, 10);
108        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
109        assert_eq!(actual, expected);
110
111        c.apply_keystream_pos(&mut ciphertext, 0);
112        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
113        assert_eq!(actual, expected);
114    }
115
116    const ARC4_VARIANT_KEY: [u8; 32] = hex!(
117        "db6fc5e8fc6b3d95497d52e4b215ed7d"
118        "e04824c12f52f8877762d09c276b3775");
119
120    const ARC4_VARIANT_OFFSET: usize = 0;
121    const ARC4_VARIANT_CIPHERTEXT: [u8; 5] = [
122        0x90, 0x21, 0xA1, 0x07, 0x53,
123    ];
124    const ARC4_VARIANT_PLAINTEXT: &str = "Notes";
125
126    const ARC4_VARIANT_CIPHERTEXT2: [u8; 8] = [
127        0x43, 0xE2, 0x7F, 0xA2, 0x1A, 0x75, 0x67, 0xEE,
128    ];
129    const ARC4_VARIANT_OFFSET2: usize = 5;
130    const ARC4_VARIANT_PLAINTEXT2: &str = "Password";
131
132    #[test]
133    fn test_arc4_variant() {
134        let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
135        let mut ciphertext = ARC4_VARIANT_CIPHERTEXT.clone();
136        let expected = ARC4_VARIANT_PLAINTEXT;
137        c.apply_keystream(&mut ciphertext);
138        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
139        assert_eq!(actual, expected);
140    }
141
142    #[test]
143    fn test_arc4_variant_identity() {
144        let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
145        let mut actual = ARC4_VARIANT_CIPHERTEXT.clone();
146        c.apply_keystream_pos(&mut actual, 0);
147        c.apply_keystream_pos(&mut actual, 0);
148        assert_eq!(actual, ARC4_VARIANT_CIPHERTEXT);
149    }
150
151    #[test]
152    fn test_arc4_variant_offsets() {
153        let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
154        let mut ciphertext = ARC4_VARIANT_CIPHERTEXT2.clone();
155        let expected = ARC4_VARIANT_PLAINTEXT2;
156        // c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET2);
157        // let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
158        // assert_eq!(actual, expected);
159
160        // let mut c = new_stream(CipherType::ArcFourVariant.to_u32().unwrap(), &ARC4_VARIANT_KEY).unwrap();
161        let mut ciphertext = ARC4_VARIANT_CIPHERTEXT.clone();
162        let expected = ARC4_VARIANT_PLAINTEXT;
163        c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
164        c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
165        c.apply_keystream_pos(&mut ciphertext, ARC4_VARIANT_OFFSET);
166        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
167        assert_eq!(actual, expected);
168    }
169
170    const CHACHA20_KEY: [u8; 64] = hex!(
171        "47d3d820a2eb2b5b0b57e3397875c5fb"
172        "ef0676f0f9425b5f0a9ba9f32060134e"
173        "9a612a5b3be2366f0fab2c8f16980760"
174        "c82e194a800c0c60c2f9000d5a64daab");
175
176    const CHACHA20_OFFSET: usize = 0;
177    const CHACHA20_CIPHERTEXT: [u8; 8] = [
178        0x07,
179        0x69,
180        0xE8,
181        0xD6,
182        0x95,
183        0x5F,
184        0x4D,
185        0x82,
186    ];
187    const CHACHA20_PLAINTEXT: &str = "Password";
188
189    const CHACHA20_CIPHERTEXT2: [u8; 10] = [
190        0x3C,
191        0xBC,
192        0xB1,
193        0xB5,
194        0x08,
195        0xD3,
196        0x1A,
197        0x65,
198        0xD0,
199        0x52,
200    ];
201    const CHACHA20_OFFSET2: usize = 94;
202    const CHACHA20_PLAINTEXT2: &str = "don't tell";
203
204    #[test]
205    fn test_chacha20() {
206        let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
207        let mut ciphertext = CHACHA20_CIPHERTEXT.clone();
208        let expected = CHACHA20_PLAINTEXT;
209        c.apply_keystream(&mut ciphertext);
210        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
211        assert_eq!(actual, expected);
212    }
213
214    #[test]
215    fn test_chacha20_identity() {
216        let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
217        let mut actual = CHACHA20_CIPHERTEXT.clone();
218        c.apply_keystream_pos(&mut actual, 0);
219        c.apply_keystream_pos(&mut actual, 0);
220        assert_eq!(actual, CHACHA20_CIPHERTEXT);
221    }
222
223    #[test]
224    fn test_chacha20_offsets() {
225        let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
226        let mut ciphertext = CHACHA20_CIPHERTEXT2.clone();
227        let expected = CHACHA20_PLAINTEXT2;
228        c.apply_keystream_pos(&mut ciphertext, CHACHA20_OFFSET2);
229        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
230        assert_eq!(actual, expected);
231
232        // let mut c = new_stream(CipherType::ChaCha20.to_u32().unwrap(), &CHACHA20_KEY).unwrap();
233        let mut ciphertext = CHACHA20_CIPHERTEXT.clone();
234        let expected = CHACHA20_PLAINTEXT;
235        c.apply_keystream_pos(&mut ciphertext, CHACHA20_OFFSET);
236        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
237        assert_eq!(actual, expected);
238    }
239
240    const SALSA20_KEY: [u8; 32] = hex!(
241        "578b10cfc954562053f926dfdbfa26d1"
242        "7edc7c7e5f7bedeff88ecc22a8469a08");
243
244    const SALSA20_OFFSET: usize = 0;
245    const SALSA20_CIPHERTEXT: [u8; 6] = [
246        0x10,
247        0xE8,
248        0xFC,
249        0x22,
250        0xCF,
251        0xE4,
252    ];
253    const SALSA20_PLAINTEXT: &str = "hidden";
254
255    const SALSA20_CIPHERTEXT2: [u8; 5] = [
256        0x70,
257        0x8C,
258        0x76,
259        0xA0,
260        0xF8,
261    ];
262    const SALSA20_OFFSET2: usize = 12;
263    const SALSA20_PLAINTEXT2: &str = "value";
264
265    #[test]
266    fn test_salsa20() {
267        let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
268        let mut ciphertext = SALSA20_CIPHERTEXT.clone();
269        let expected = SALSA20_PLAINTEXT;
270        c.apply_keystream(&mut ciphertext);
271        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
272        assert_eq!(actual, expected);
273    }
274
275    #[test]
276    fn test_salsa20_identity() {
277        let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
278        let mut actual = SALSA20_CIPHERTEXT.clone();
279        c.apply_keystream_pos(&mut actual, 0);
280        c.apply_keystream_pos(&mut actual, 0);
281        assert_eq!(actual, SALSA20_CIPHERTEXT);
282    }
283
284    #[test]
285    fn test_salsa20_offsets() {
286        let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
287        let mut ciphertext = SALSA20_CIPHERTEXT2.clone();
288        let expected = SALSA20_PLAINTEXT2;
289        c.apply_keystream_pos(&mut ciphertext, SALSA20_OFFSET2);
290        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
291        assert_eq!(actual, expected);
292
293        // let mut c = new_stream(CipherType::Salsa20.to_u32().unwrap(), &SALSA20_KEY).unwrap();
294        let mut ciphertext = SALSA20_CIPHERTEXT.clone();
295        let expected = SALSA20_PLAINTEXT;
296        c.apply_keystream_pos(&mut ciphertext, SALSA20_OFFSET);
297        let actual = String::from_utf8(ciphertext.to_vec()).expect("Valid utf-8");
298        assert_eq!(actual, expected);
299    }
300}