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