sjcl/
lib.rs

1//! # sjcl
2//! Simple decrypt-only SJCL library.
3//!
4//! Only supports AES-CCM so far, but OCB2 is deprecated AFAIK.
5//! To use you only need the result of a SJCL encrypted secret and the
6//! passphrase.
7//!
8//! ## Usage
9//!
10//! Decrypt a file loaded into a string:
11//! ```rust
12//! use sjcl::decrypt_json;
13//! use sjcl::SjclError;
14//!
15//! # fn main() -> Result<(), SjclError> {
16//! let data = "{\"iv\":\"nJu7KZF2eEqMv403U2oc3w==\", \"v\":1, \"iter\":10000, \"ks\":256, \"ts\":64, \"mode\":\"ccm\", \"adata\":\"\", \"cipher\":\"aes\", \"salt\":\"mMmxX6SipEM=\", \"ct\":\"VwnKwpW1ah5HmdvwuFBthx0=\"}".to_string();
17//! let password_phrase = "abcdefghi".to_string();
18//! let plaintext = decrypt_json(data, password_phrase, None)?;
19//! assert_eq!("test\ntest".to_string(), String::from_utf8(plaintext).unwrap());
20//! # Ok(())
21//! # }
22//! ```
23//!
24pub 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/// SJCL result contains all params as well as
50/// either the plaintext or the ciphertext in the last element.
51#[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/// Parameter used to encrypt a block.
94#[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
111/// Utility function to trim the initialization vector to the proper size of
112/// the nonce.
113/// (See: [SJCL/core.ccm.js](https://github.com/bitwiseshiftleft/sjcl/blob/master/core/ccm.js#L61))
114pub 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/// https://bitwiseshiftleft.github.io/sjcl/demo/
131#[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}