1pub mod decryption;
25pub mod encryption;
26
27pub use decryption::{decrypt, decrypt_json};
28pub use encryption::encrypt;
29
30use aes::{Aes128, Aes192, Aes256};
31use ccm::{
32 consts::{U13, U8},
33 Ccm,
34};
35
36use serde::{Deserialize, Serialize};
37use thiserror::Error;
38
39#[derive(Error, Debug)]
40pub enum SjclError {
41 #[error("Failed to decrypt chunk: {message:?}")]
42 DecryptionError { message: String },
43 #[error("Failed to encrypt: {message:?}")]
44 EncryptionError { message: String },
45 #[error("Method is not implemented")]
46 NotImplementedError,
47}
48
49#[derive(Debug, Deserialize, Serialize, PartialEq)]
52pub struct SjclBlock {
53 iv: String,
54 v: u32,
55 iter: u32,
56 ks: usize,
57 ts: usize,
58 mode: String,
59 adata: String,
60 cipher: String,
61 salt: String,
62 ct: String,
63}
64
65impl SjclBlock {
66 pub fn new(
67 iv: String,
68 v: u32,
69 iter: u32,
70 ks: usize,
71 ts: usize,
72 mode: String,
73 adata: String,
74 cipher: String,
75 salt: String,
76 ct: String,
77 ) -> SjclBlock {
78 SjclBlock {
79 iv,
80 v,
81 iter,
82 ks,
83 ts,
84 mode,
85 adata,
86 cipher,
87 salt,
88 ct,
89 }
90 }
91}
92
93#[derive(Debug, Deserialize)]
95pub struct SjclParams {
96 pub iv: Vec<u8>,
97 pub v: u32,
98 pub iter: u32,
99 pub ks: usize,
100 pub ts: usize,
101 pub mode: String,
102 pub adata: Vec<u8>,
103 pub cipher: String,
104 pub salt: Vec<u8>,
105}
106
107pub type AesCcm256 = Ccm<Aes256, U8, U13>;
108pub type AesCcm128 = Ccm<Aes128, U8, U13>;
109pub type AesCcm192 = Ccm<Aes192, U8, U13>;
110
111pub fn truncate_iv(mut iv: Vec<u8>, output_size: usize, tag_size: usize) -> Vec<u8> {
115 let iv_size = iv.len();
116 let output_size = (output_size - tag_size) / 8;
117
118 let mut l = 2;
119 while l < 4 && ((output_size >> (8 * l)) > 0) {
120 l += 1
121 }
122 if iv_size <= 15 && l < 15 - iv_size {
123 l = 15 - iv_size
124 }
125
126 let _ = iv.split_off(15 - l);
127 iv
128}
129
130#[cfg(test)]
132mod tests {
133 use crate::decryption::{decrypt, decrypt_json};
134 use crate::encryption::encrypt;
135 use crate::{SjclBlock, SjclParams};
136
137 use serde_json;
138
139 #[test]
140 fn test_decrypt_256bit_end_to_end() {
141 let data = "{\"iv\":\"nJu7KZF2eEqMv403U2oc3w==\", \"v\":1, \"iter\":10000, \"ks\":256, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"mMmxX6SipEM=\", \"ct\":\"VwnKwpW1ah5HmdvwuFBthx0=\"}".to_string();
142 let password_phrase = "abcdefghi".to_string();
143
144 let plaintext = "test\ntest".to_string();
145
146 assert_eq!(
147 String::from_utf8(decrypt_json(data, password_phrase, None).unwrap()).unwrap(),
148 plaintext
149 );
150 }
151
152 #[test]
153 fn test_decrypt_256bit_with_struct() {
154 let data = SjclBlock {
155 iv: "nJu7KZF2eEqMv403U2oc3w".to_string(),
156 v: 1,
157 iter: 10000,
158 ks: 256,
159 ts: 64,
160 mode: "ccm".to_string(),
161 adata: "".to_string(),
162 cipher: "aes".to_string(),
163 salt: "mMmxX6SipEM".to_string(),
164 ct: "VwnKwpW1ah5HmdvwuFBthx0=".to_string(),
165 };
166 let password_phrase = "abcdefghi".to_string();
167
168 let plaintext = "test\ntest".to_string();
169
170 assert_eq!(
171 String::from_utf8(decrypt(data, password_phrase, None).unwrap()).unwrap(),
172 plaintext
173 );
174 }
175
176 #[test]
177 fn test_decrypt_192bit_end_to_end() {
178 let data = "{\"iv\":\"rUeOzcoSOAmbJIZ4o7wZzA==\", \"v\":1, \"iter\":1000, \"ks\":192, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"qpVeWJh4g1I=\", \"ct\":\"QJx31ojP+TW25eYZSFnjrG85dOZY\"}".to_string();
179 let password_phrase = "abcdefghi".to_string();
180
181 let plaintext = "cats are cute".to_string();
182
183 assert_eq!(
184 String::from_utf8(decrypt_json(data, password_phrase, None).unwrap()).unwrap(),
185 plaintext
186 );
187 }
188
189 #[test]
190 fn test_decrypt_192bit_with_struct() {
191 let data = SjclBlock {
192 iv: "rUeOzcoSOAmbJIZ4o7wZzA==".to_string(),
193 v: 1,
194 iter: 1000,
195 ks: 192,
196 ts: 64,
197 mode: "ccm".to_string(),
198 adata: "".to_string(),
199 cipher: "aes".to_string(),
200 salt: "qpVeWJh4g1I=".to_string(),
201 ct: "QJx31ojP+TW25eYZSFnjrG85dOZY".to_string(),
202 };
203 let password_phrase = "abcdefghi".to_string();
204
205 let plaintext = "cats are cute".to_string();
206
207 assert_eq!(
208 String::from_utf8(decrypt(data, password_phrase, None).unwrap()).unwrap(),
209 plaintext
210 );
211 }
212
213 #[test]
214 fn test_decrypt_128bit_end_to_end() {
215 let data = "{\"iv\":\"aDvOWpwgcF0S7YDvu3TrTQ==\", \"v\":1, \"iter\":1000, \"ks\":128, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"qpVeWJh4g1I=\", \"ct\":\"3F6gxac5V5k39iUNHubqEOHrxuZJqoX2zyws9nU=\"}".to_string();
216 let password_phrase = "abcdefghi".to_string();
217
218 let plaintext = "but dogs are the best".to_string();
219
220 assert_eq!(
221 String::from_utf8(decrypt_json(data, password_phrase, None).unwrap()).unwrap(),
222 plaintext
223 );
224 }
225
226 #[test]
227 fn test_decrypt_128bit_with_struct() {
228 let data = SjclBlock {
229 iv: "aDvOWpwgcF0S7YDvu3TrTQ==".to_string(),
230 v: 1,
231 iter: 1000,
232 ks: 128,
233 ts: 64,
234 mode: "ccm".to_string(),
235 adata: "".to_string(),
236 cipher: "aes".to_string(),
237 salt: "qpVeWJh4g1I=".to_string(),
238 ct: "3F6gxac5V5k39iUNHubqEOHrxuZJqoX2zyws9nU=".to_string(),
239 };
240 let password_phrase = "abcdefghi".to_string();
241
242 let plaintext = "but dogs are the best".to_string();
243
244 assert_eq!(
245 String::from_utf8(decrypt(data, password_phrase, None).unwrap()).unwrap(),
246 plaintext
247 );
248 }
249
250 #[test]
251 fn test_encrypt_128bit() {
252 let iv = vec![
253 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
254 0x03, 0x50,
255 ];
256 let v = 1;
257 let iter = 1000;
258 let ks = 128;
259 let ts = 64;
260 let mode = "ccm".to_string();
261 let adata = vec![];
262 let cipher = "aes".to_string();
263 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
264 let params = SjclParams {
265 iv: iv.clone(),
266 v: v.clone(),
267 iter: iter.clone(),
268 ks: ks.clone(),
269 ts: ts.clone(),
270 mode: mode.clone(),
271 adata: adata.clone(),
272 cipher: cipher.clone(),
273 salt: salt.clone(),
274 };
275 let plaintext = "everybody loves cute dogs";
276 let password = "super_secret_password".to_string();
277 let expected = "{\"iv\":\"Da6jp9ADdn894GUWw24DUA==\", \"v\":1, \"iter\":1000, \"ks\":128, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"iwaME9RFNOY=\", \"ct\":\"OtTxhmTDYC2hRoICx6M6wDvhJPnNPHSGyxnri7gvxSHx\"}";
278
279 let result_block = encrypt(plaintext.as_bytes().to_vec(), params, password);
280 assert_eq!(
281 result_block.unwrap(),
282 serde_json::from_str(&expected).unwrap()
283 );
284 }
285
286 #[test]
287 fn test_encrypt_192bit() {
288 let iv = vec![
289 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
290 0x03, 0x50,
291 ];
292 let v = 1;
293 let iter = 1000;
294 let ks = 192;
295 let ts = 64;
296 let mode = "ccm".to_string();
297 let adata = vec![];
298 let cipher = "aes".to_string();
299 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
300 let params = SjclParams {
301 iv: iv.clone(),
302 v: v.clone(),
303 iter: iter.clone(),
304 ks: ks.clone(),
305 ts: ts.clone(),
306 mode: mode.clone(),
307 adata: adata.clone(),
308 cipher: cipher.clone(),
309 salt: salt.clone(),
310 };
311 let plaintext = "kate loves to cuddle with dogs";
312 let password = "super_secret_password".to_string();
313 let expected = "{\"iv\":\"Da6jp9ADdn894GUWw24DUA==\", \"v\":1, \"iter\":1000, \"ks\":192, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"iwaME9RFNOY=\", \"ct\":\"Qz5UG1Vc6Gv46tF9Dd4inj+NS7tu15JQaThn4zS49vlJoW94rj0=\"}";
314
315 let result_block = encrypt(plaintext.as_bytes().to_vec(), params, password);
316 assert_eq!(
317 result_block.unwrap(),
318 serde_json::from_str(&expected).unwrap()
319 );
320 }
321
322 #[test]
323 fn test_encrypt_256bit() {
324 let iv = vec![
325 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
326 0x03, 0x50,
327 ];
328 let v = 1;
329 let iter = 1000;
330 let ks = 256;
331 let ts = 64;
332 let mode = "ccm".to_string();
333 let adata = vec![];
334 let cipher = "aes".to_string();
335 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
336 let params = SjclParams {
337 iv: iv.clone(),
338 v: v.clone(),
339 iter: iter.clone(),
340 ks: ks.clone(),
341 ts: ts.clone(),
342 mode: mode.clone(),
343 adata: adata.clone(),
344 cipher: cipher.clone(),
345 salt: salt.clone(),
346 };
347 let plaintext = "why sleep when you can mope around and drink lotsa coffee";
348 let password = "super_secret_password".to_string();
349 let expected = "{\"iv\":\"Da6jp9ADdn894GUWw24DUA==\", \"v\":1, \"iter\":1000, \"ks\":256, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"iwaME9RFNOY=\", \"ct\":\"ah/9K1ngL1FO62yGoInnFAcD0bXdJ53K9Bqr4J28K5WpXu70pdABhZBr2pdJ5LSI0ni+KkQP/Y3UmHVEWA3PcFU=\"}";
350
351 let result_block = encrypt(plaintext.as_bytes().to_vec(), params, password);
352 assert_eq!(
353 result_block.unwrap(),
354 serde_json::from_str(&expected).unwrap()
355 );
356 }
357
358 #[test]
359 fn test_encrypt_then_decrypt_128() {
360 let iv = vec![
361 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
362 0x03, 0x50,
363 ];
364 let v = 1;
365 let iter = 1000;
366 let ks = 128;
367 let ts = 64;
368 let mode = "ccm".to_string();
369 let adata: Vec<u8> = vec![];
370 let cipher = "aes".to_string();
371 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
372 let params = SjclParams {
373 iv: iv.clone(),
374 v: v.clone(),
375 iter: iter.clone(),
376 ks: ks.clone(),
377 ts: ts.clone(),
378 mode: mode.clone(),
379 adata: adata.clone(),
380 cipher: cipher.clone(),
381 salt: salt.clone(),
382 };
383
384 let plaintext = "the answer is 42!";
385 let password = "super_secret_password".to_string();
386
387 let encrypted = encrypt(plaintext.as_bytes().to_vec(), params, password.clone()).unwrap();
388 let decrypted =
389 decrypt(encrypted, password, Some(String::from_utf8(adata).unwrap())).unwrap();
390
391 assert_eq!(plaintext, String::from_utf8(decrypted).unwrap());
392 }
393
394 #[test]
395 fn test_encrypt_then_decrypt_192() {
396 let iv = vec![
397 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
398 0x03, 0x50,
399 ];
400 let v = 1;
401 let iter = 1000;
402 let ks = 192;
403 let ts = 64;
404 let mode = "ccm".to_string();
405 let adata: Vec<u8> = vec![];
406 let cipher = "aes".to_string();
407 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
408 let params = SjclParams {
409 iv: iv.clone(),
410 v: v.clone(),
411 iter: iter.clone(),
412 ks: ks.clone(),
413 ts: ts.clone(),
414 mode: mode.clone(),
415 adata: adata.clone(),
416 cipher: cipher.clone(),
417 salt: salt.clone(),
418 };
419
420 let plaintext = "the answer is 42!";
421 let password = "super_secret_password".to_string();
422
423 let encrypted = encrypt(plaintext.as_bytes().to_vec(), params, password.clone()).unwrap();
424 let decrypted =
425 decrypt(encrypted, password, Some(String::from_utf8(adata).unwrap())).unwrap();
426
427 assert_eq!(plaintext, String::from_utf8(decrypted).unwrap());
428 }
429
430 #[test]
431 fn test_encrypt_then_decrypt_256() {
432 let iv = vec![
433 0x0D, 0xAE, 0xA3, 0xA7, 0xD0, 0x03, 0x76, 0x7F, 0x3D, 0xE0, 0x65, 0x16, 0xC3, 0x6E,
434 0x03, 0x50,
435 ];
436 let v = 1;
437 let iter = 1000;
438 let ks = 256;
439 let ts = 64;
440 let mode = "ccm".to_string();
441 let adata: Vec<u8> = vec![];
442 let cipher = "aes".to_string();
443 let salt = vec![0x8B, 0x06, 0x8C, 0x13, 0xD4, 0x45, 0x34, 0xE6];
444 let params = SjclParams {
445 iv: iv.clone(),
446 v: v.clone(),
447 iter: iter.clone(),
448 ks: ks.clone(),
449 ts: ts.clone(),
450 mode: mode.clone(),
451 adata: adata.clone(),
452 cipher: cipher.clone(),
453 salt: salt.clone(),
454 };
455
456 let plaintext = "the answer is 42!";
457 let password = "super_secret_password".to_string();
458
459 let encrypted = encrypt(plaintext.as_bytes().to_vec(), params, password.clone()).unwrap();
460 let decrypted =
461 decrypt(encrypted, password, Some(String::from_utf8(adata).unwrap())).unwrap();
462
463 assert_eq!(plaintext, String::from_utf8(decrypted).unwrap());
464 }
465}