keepass_db/protected_stream/
mod.rs1use 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;
12use 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 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 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 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}