diff --git a/src/bin/blocksize.rs b/src/bin/blocksize.rs index b26c2fd..5425f62 100644 --- a/src/bin/blocksize.rs +++ b/src/bin/blocksize.rs @@ -10,7 +10,7 @@ use doby::{ }; const MAX_BLOCK_SIZE: usize = 1_073_741_824; //1GB -const PASSWORD: &[u8] = b"HARDCORE music is the best music of all time"; +const PASSWORD: &str = "HARDCORE music is the best music of all time"; fn set_if_better(best_time: &mut Option, time: u128, best_block_size: &mut Option, block_size: usize) { let mut better = true; @@ -49,7 +49,7 @@ fn main() -> io::Result<()> { let mut reader = BufReader::with_capacity(block_size, &input); let mut writer = BufWriter::with_capacity(block_size, &output); - let cipher = DobyCipher::new(PASSWORD, ¶ms).unwrap(); + let cipher = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap(); let t_encrypt = Instant::now(); encrypt(&mut reader, &mut writer, ¶ms, cipher, block_size, None)?; writer.flush()?; @@ -59,7 +59,7 @@ fn main() -> io::Result<()> { reset(&mut reader)?; reset(&mut writer)?; - let cipher = DobyCipher::new(PASSWORD, ¶ms).unwrap(); + let cipher = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap(); let t_decrypt = Instant::now(); decrypt(&mut reader, &mut writer, cipher, block_size)?; writer.flush()?; diff --git a/src/cli.rs b/src/cli.rs index dc8dede..be9eaa0 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -5,12 +5,12 @@ use std::{ io::{stdin, stdout, Read, Write}, }; use clap::{crate_name, crate_version, App, Arg, AppSettings}; -use crate::crypto::{ArgonParams, CipherAlgorithm}; +use crate::{Password, crypto::{ArgonParams, CipherAlgorithm}}; cpufeatures::new!(aes_ni, "aes"); pub struct CliArgs { - pub password: String, + pub password: Password, pub force_encrypt: bool, pub argon2_params: ArgonParams, pub cipher: CipherAlgorithm, @@ -130,13 +130,8 @@ pub fn parse() -> Option { }) .unwrap_or_else(|| Some(Box::new(stdout())))?; - let password = match app.value_of("1_password") { - Some(s) => s.to_string(), - None => rpassword::read_password_from_tty(Some("Password: ")).unwrap(), - }; - Some(CliArgs { - password, + password: app.value_of("1_password").into(), force_encrypt: app.is_present("force-encrypt"), argon2_params: params, cipher, diff --git a/src/crypto.rs b/src/crypto.rs index ae2e022..8f16818 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -11,6 +11,7 @@ use rand::{Rng, rngs::OsRng}; use argon2::{Argon2, Version, Algorithm}; use hkdf::Hkdf; use zeroize::Zeroize; +use crate::Password; const SALT_LEN: usize = 64; const AES_NONCE_LEN: usize = 16; @@ -126,6 +127,17 @@ impl EncryptionParams { } } +trait ThenZeroize { + fn zeroize(self, v: T) -> Self; +} + +impl ThenZeroize for Result { + fn zeroize(self, mut v: T) -> Self { + v.zeroize(); + self + } +} + pub struct DobyCipher { cipher: Box, hmac: Hmac, @@ -133,31 +145,42 @@ pub struct DobyCipher { } impl DobyCipher { - pub fn new(password: &[u8], params: &EncryptionParams) -> Result { - let argon = Argon2::new(None, params.argon2.t_cost, params.argon2.m_cost, params.argon2.parallelism.into(), Version::V0x13)?; - let mut master_key = [0; KEY_LEN]; - argon.hash_password_into(Algorithm::Argon2id, password, ¶ms.password_salt, &[], &mut master_key)?; + pub fn new(mut password: Password, params: &EncryptionParams) -> Result { + match Argon2::new(None, params.argon2.t_cost, params.argon2.m_cost, params.argon2.parallelism.into(), Version::V0x13) { + Ok(argon2) => { + let mut master_key = [0; KEY_LEN]; + let password = password.unwrap_or_ask(); + argon2.hash_password_into(Algorithm::Argon2id, password.as_bytes(), ¶ms.password_salt, &[], &mut master_key).zeroize(password)?; + let hkdf = Hkdf::::new(Some(¶ms.hkdf_salt), &master_key); + let mut encryption_key = [0; KEY_LEN]; + hkdf.expand(b"doby_encryption_key", &mut encryption_key).unwrap(); + let mut authentication_key = [0; KEY_LEN]; + hkdf.expand(b"doby_authentication_key", &mut authentication_key).unwrap(); + master_key.zeroize(); - let hkdf = Hkdf::::new(Some(¶ms.hkdf_salt), &master_key); - let mut encryption_key = [0; KEY_LEN]; - hkdf.expand(b"doby_encryption_key", &mut encryption_key).unwrap(); - let mut authentication_key = [0; KEY_LEN]; - hkdf.expand(b"doby_authentication_key", &mut authentication_key).unwrap(); - master_key.zeroize(); + let mut encoded_params = Vec::with_capacity(params.get_params_len()); + params.write(&mut encoded_params).unwrap(); + let mut hmac = Hmac::new_from_slice(&authentication_key).unwrap(); + authentication_key.zeroize(); + hmac.update(&encoded_params); - let mut encoded_params = Vec::with_capacity(params.get_params_len()); - params.write(&mut encoded_params).unwrap(); - let mut hmac = Hmac::new_from_slice(&authentication_key).unwrap(); - 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()), + }; + encryption_key.zeroize(); - Ok(Self { - cipher: 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()), - }, - hmac, - buffer: Vec::new(), - }) + Ok(Self { + cipher, + hmac, + buffer: Vec::new(), + }) + } + Err(e) => { + password.zeroize(); + Err(e) + } + } } pub fn encrypt_chunk(&mut self, buff: &mut [u8], writer: &mut W) -> io::Result<()> { @@ -229,19 +252,19 @@ mod tests { m_cost: 8, parallelism: 1, }, CipherAlgorithm::AesCtr); - let password = b"I like spaghetti"; + let password = "I like spaghetti"; let plaintext = b"but I love so much to listen to HARDCORE music on big subwoofer"; let mut buff: [u8; 63] = *plaintext; let mut vec = Vec::with_capacity(buff.len()+HASH_LEN); - let mut enc_cipher = DobyCipher::new(password, ¶ms).unwrap(); + let mut enc_cipher = DobyCipher::new(password.into(), ¶ms).unwrap(); enc_cipher.encrypt_chunk(&mut buff, &mut vec).unwrap(); assert_ne!(buff, *plaintext); assert_eq!(buff, vec.as_slice()); assert_eq!(enc_cipher.write_hmac(&mut vec).unwrap(), HASH_LEN); assert_eq!(vec.len(), buff.len()+HASH_LEN); - let mut dec_cipher = DobyCipher::new(password, ¶ms).unwrap(); + let mut dec_cipher = DobyCipher::new(password.into(), ¶ms).unwrap(); let mut decrypted = vec![0; buff.len()+HASH_LEN]; let mut n = dec_cipher.decrypt_chunk(&mut vec.as_slice(), &mut decrypted[..]).unwrap(); assert_eq!(n, buff.len()); diff --git a/src/lib.rs b/src/lib.rs index 85b5303..d076a27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,36 @@ pub mod crypto; use std::io::{self, Read, Write}; use crypto::{DobyCipher, EncryptionParams}; +use zeroize::Zeroize; pub const MAGIC_BYTES: &[u8; 4] = b"DOBY"; +pub struct Password(Option); + +impl Password { + fn unwrap_or_ask(self) -> String { + self.0.unwrap_or_else(|| rpassword::read_password_from_tty(Some("Password: ")).unwrap()) + } +} + +impl From> for Password { + fn from(s: Option<&str>) -> Self { + Self(s.map(|s| String::from(s))) + } +} + +impl From<&str> for Password { + fn from(s: &str) -> Self { + Some(s).into() + } +} + +impl Zeroize for Password { + fn zeroize(&mut self) { + self.0.zeroize() + } +} + pub fn encrypt(reader: &mut R, writer: &mut W, params: &EncryptionParams, mut cipher: DobyCipher, block_size: usize, already_read: Option>) -> io::Result<()> { writer.write_all(MAGIC_BYTES)?; params.write(writer)?; diff --git a/src/main.rs b/src/main.rs index 9d7df01..b4023e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ use std::io::{BufWriter, BufReader, Read}; -use zeroize::Zeroize; use doby::{ cli, crypto::{EncryptionParams, DobyCipher}, @@ -9,10 +8,10 @@ use doby::{ }; fn main() { - if let Some(mut cli_args) = cli::parse() { + if let Some(cli_args) = cli::parse() { let mut reader = BufReader::with_capacity(cli_args.block_size, cli_args.reader); let mut writer = BufWriter::with_capacity(cli_args.block_size, cli_args.writer); - + let mut magic_bytes = vec![0; MAGIC_BYTES.len()]; match reader.read(&mut magic_bytes) { Ok(n) => { @@ -24,7 +23,7 @@ fn main() { Ok(params) => { match params { Some(params) => { - match DobyCipher::new(cli_args.password.as_bytes(), ¶ms) { + match DobyCipher::new(cli_args.password, ¶ms) { Ok(cipher) => { match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) { Ok(verified) => { @@ -45,7 +44,7 @@ fn main() { } } else { //otherwise, encrypt let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher); - match DobyCipher::new(cli_args.password.as_bytes(), ¶ms) { + match DobyCipher::new(cli_args.password, ¶ms) { Ok(cipher) => { if let Err(e) = encrypt( &mut reader, @@ -64,6 +63,5 @@ fn main() { } Err(e) => eprintln!("I/O error while reading magic bytes: {}", e), } - cli_args.password.zeroize(); } }