diff --git a/README.md b/README.md index cc98fff..40d529c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ doby started as a fork of [aef](https://github.com/wyhaya/aef) by [wyhaya](https * Fast: written in [rust](https://www.rust-lang.org), encrypts with [AES-256-CTR](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)) or [XChaCha20](https://en.wikipedia.org/wiki/Salsa20#XChaCha) * [HMAC](https://en.wikipedia.org/wiki/HMAC) ciphertext authentication * Password brute-force resistance with [Argon2](https://en.wikipedia.org/wiki/Argon2) -* Increase the plaintext size of only 158 bytes +* Increase the plaintext size of only 142 bytes * Encryption from STDIN/STDOUT or from files * Adjustable performance & secuity parameters @@ -133,7 +133,7 @@ let master_key: [u8; 32] = argon2id( ); ``` -Then, doby uses [HKDF](https://en.wikipedia.org/wiki/HKDF) with the previous random salt to compute the `encryption_key` and the `authentication_key`. +Then, doby uses [HKDF](https://en.wikipedia.org/wiki/HKDF) with the previous random salt to compute the `nonce`, the `encryption_key` and the `authentication_key`. ```rust let hkdf = Hkdf::new( @@ -141,6 +141,7 @@ let hkdf = Hkdf::new( master_key, //ikm blake2b, //hash function ); +let nonce: [u8; 16] = hkdf.expand(b"doby_nonce"); //(16 bytes for AES-CTR, 24 for XChaCha20) let encryption_key: [u8; 32] = hkdf.expand(b"doby_encryption_key"); let authentication_key: [u8; 32] = hkdf.expand(b"doby_authentication_key"); ``` @@ -157,15 +158,14 @@ hmac.update(argon2_time_cost); hmac.update(argon2_memory_cost); hmac.update(argon2_parallelism); hmac.update(cipher); //1-byte representation of the symmetric cipher used to encrypt (either AES-CTR or XChaCha20) -hmac.update(random_nonce); //random nonce used for encryption (16 bytes for AES-CTR, 24 for XChaCha20) ``` All this parameters are also written in plain text in the header of the doby output. -Now, doby initializes a symmetric cipher with `encryption_key` and `random_nonce` (either AES-CTR or XChaCha20, based on the `--cipher` option) and starts the actual encryption. It reads chunks from the plaintext (according to the `--block-size` parameter), encrypts them with the cipher and updates the HMAC with the ciphertext. +Now, doby initializes a symmetric cipher with `encryption_key` and `nonce` (either AES-CTR or XChaCha20, based on the `--cipher` option) and starts the actual encryption. It reads chunks from the plaintext (according to the `--block-size` parameter), encrypts them with the cipher and updates the HMAC with the ciphertext. ```rust -let cipher = Aes256Ctr::new(encryption_key, random_nonce); //example with AES-CTR +let cipher = Aes256Ctr::new(encryption_key, nonce); //example with AES-CTR let mut n = 1; let mut chunk: [u8; block_size] = [0; block_size]; while n != 0 { @@ -196,12 +196,12 @@ let master_key: [u8; 32] = argon2id( ); ``` -`encryption_key` and `authentication_key` are computed from `master_key` in the same way as during encryption. The HMAC is also initialized and updated with the values read from the header. +`nonce`, `encryption_key` and `authentication_key` are computed from `master_key` in the same way as during encryption. The HMAC is also initialized and updated with the values read from the header. Then, doby starts decryption. ```rust -let cipher = XChaCha20::new(encryption_key, nonce_read_from_input); //example with XChaCha20 +let cipher = XChaCha20::new(encryption_key, nonce); //example with XChaCha20 let mut n = 1; let mut chunk: [u8; block_size] = [0; block_size]; while n != 0 { diff --git a/src/crypto.rs b/src/crypto.rs index d517797..9406b02 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -55,24 +55,20 @@ impl Display for CipherAlgorithm { pub struct EncryptionParams { salt: [u8; SALT_LEN], pub argon2: ArgonParams, - nonce: Vec, pub cipher: CipherAlgorithm, } impl EncryptionParams { pub fn get_params_len(&self) -> usize { - SALT_LEN + 4*2 + 2 + self.cipher.get_nonce_size() + SALT_LEN + 4*2 + 2 } pub fn new(argon2_params: ArgonParams, cipher: CipherAlgorithm) -> EncryptionParams { let mut salt = [0; SALT_LEN]; OsRng.fill(&mut salt); - let mut nonce = vec![0; cipher.get_nonce_size()]; - OsRng.fill(&mut nonce[..]); EncryptionParams { salt, argon2: argon2_params, - nonce, cipher, } } @@ -82,7 +78,6 @@ impl EncryptionParams { writer.write_all(&self.argon2.m_cost.to_be_bytes())?; writer.write_all(&self.argon2.parallelism.to_be_bytes())?; writer.write_all(&(self.cipher as u8).to_be_bytes())?; - writer.write_all(&self.nonce)?; Ok(()) } pub fn read(reader: &mut R) -> io::Result> { @@ -98,9 +93,6 @@ impl EncryptionParams { reader.read_exact(&mut cipher_buff)?; match CipherAlgorithm::try_from(cipher_buff[0]) { Ok(cipher) => { - let mut nonce = vec![0; cipher.get_nonce_size()]; - reader.read_exact(&mut nonce)?; - let argon2_params = ArgonParams { t_cost: u32::from_be_bytes(t_cost), m_cost: u32::from_be_bytes(m_cost), @@ -110,7 +102,6 @@ impl EncryptionParams { Ok(Some(EncryptionParams { salt, argon2: argon2_params, - nonce, cipher, })) } @@ -144,6 +135,8 @@ impl DobyCipher { let password = password.unwrap_or_ask(); argon2.hash_password_into(Algorithm::Argon2id, password.as_bytes(), ¶ms.salt, &[], &mut master_key).zeroize(password)?; let hkdf = Hkdf::::new(Some(¶ms.salt), &master_key); + let mut nonce = vec![0; params.cipher.get_nonce_size()]; + hkdf.expand(b"doby_nonce", &mut nonce).unwrap(); let mut encryption_key = [0; KEY_LEN]; hkdf.expand(b"doby_encryption_key", &mut encryption_key).unwrap(); let mut authentication_key = [0; KEY_LEN]; @@ -157,8 +150,8 @@ impl DobyCipher { hmac.update(&encoded_params); let cipher: Box = match params.cipher { - CipherAlgorithm::AesCtr => Box::new(Aes256Ctr::new_from_slices(&encryption_key, ¶ms.nonce).unwrap()), - CipherAlgorithm::XChaCha20 => Box::new(XChaCha20::new_from_slices(&encryption_key, ¶ms.nonce).unwrap()), + CipherAlgorithm::AesCtr => Box::new(Aes256Ctr::new_from_slices(&encryption_key, &nonce).unwrap()), + CipherAlgorithm::XChaCha20 => Box::new(XChaCha20::new_from_slices(&encryption_key, &nonce).unwrap()), }; encryption_key.zeroize(); @@ -221,16 +214,15 @@ mod tests { parallelism: 1, }, CipherAlgorithm::XChaCha20); - assert_eq!(params.get_params_len(), 98); + assert_eq!(params.get_params_len(), 74); - let mut buff = Vec::with_capacity(98); + let mut buff = Vec::with_capacity(74); params.write(&mut buff).unwrap(); assert_eq!(buff[..64], params.salt); assert_eq!(buff[64..68], vec![0, 0, 0, 0x01]); //t_cost assert_eq!(buff[68..72], vec![0, 0, 0, 0x08]); //m_cost assert_eq!(buff[72], 0x01); //parallelism assert_eq!(buff[73], CipherAlgorithm::XChaCha20 as u8); - assert_eq!(buff[74..], params.nonce); let new_params = EncryptionParams::read(&mut buff.as_slice()).unwrap().unwrap(); assert_eq!(new_params, params); diff --git a/tests/authentication.rs b/tests/authentication.rs index 1f52e6c..dc95aa8 100644 --- a/tests/authentication.rs +++ b/tests/authentication.rs @@ -19,6 +19,7 @@ fn different_elements(v1: &Vec, v2: &Vec) -> usize { fn authentication() { const BLOCK_SIZE: usize = 65536; const PLAINTEXT: &[u8; 13] = b"the plaintext"; + const CIPHERTEXT_SIZE: usize = PLAINTEXT.len()+142; const PASSWORD: &str = "the password"; let params = EncryptionParams::new(ArgonParams { t_cost: 1, @@ -27,9 +28,9 @@ fn authentication() { }, CipherAlgorithm::AesCtr); let encrypter = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap(); - let mut ciphertext = Vec::with_capacity(PLAINTEXT.len()+158); + let mut ciphertext = Vec::with_capacity(CIPHERTEXT_SIZE); encrypt(&mut &PLAINTEXT[..], &mut ciphertext, ¶ms, encrypter, BLOCK_SIZE, None).unwrap(); - assert_eq!(ciphertext.len(), PLAINTEXT.len()+158); + assert_eq!(ciphertext.len(), CIPHERTEXT_SIZE); for i in 0..ciphertext.len() { let mut compromised = ciphertext.clone(); diff --git a/tests/cli.rs b/tests/cli.rs index a355202..cb120d5 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -85,7 +85,7 @@ fn force_encrypt() -> io::Result<()> { let buff_ciphertext_2 = fs::read(&tmp_ciphertext_2)?; assert_ne!(buff_ciphertext_1, buff_ciphertext_2); assert_ne!(buff_ciphertext_2, PLAINTEXT); - assert!(buff_ciphertext_2.len() >= buff_ciphertext_1.len()+158); + assert!(buff_ciphertext_2.len() >= buff_ciphertext_1.len()+142); let tmp_decrypted_1 = tmp_path.join("decrypted_1"); doby_cmd().unwrap().arg(tmp_ciphertext_2).arg(&tmp_decrypted_1).assert().success().stdout("").stderr(""); @@ -108,7 +108,7 @@ fn test_cipher(cipher_str: &str, cipher_algorithm: CipherAlgorithm) -> io::Resul let ciphertext = fs::read(&tmp_ciphertext)?; assert_eq!(ciphertext[4+SALT_LEN+4*2+1], cipher_algorithm as u8); - assert_eq!(ciphertext.len(), PLAINTEXT.len()+14+SALT_LEN+HASH_LEN+cipher_algorithm.get_nonce_size()); + assert_eq!(ciphertext.len(), PLAINTEXT.len()+14+SALT_LEN+HASH_LEN); doby_cmd().unwrap().arg(tmp_ciphertext).assert().success().stdout(PLAINTEXT).stderr("");