Compare commits
4 Commits
382ce3c389
...
10153f6316
Author | SHA1 | Date |
---|---|---|
Matéo Duparc | 10153f6316 | |
Matéo Duparc | 1f50973381 | |
Matéo Duparc | 311677d195 | |
Matéo Duparc | 2c8ab7e8ad |
|
@ -220,10 +220,10 @@ dependencies = [
|
|||
"clap",
|
||||
"cpufeatures 0.2.1",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"num_enum",
|
||||
"rand",
|
||||
"rpassword",
|
||||
"subtle",
|
||||
"tempfile",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
|
@ -21,7 +21,7 @@ num_enum = "0.5"
|
|||
cpufeatures = "0.2"
|
||||
aes = { version = "0.7", features = ["ctr"] }
|
||||
chacha20 = "0.8"
|
||||
hmac = "0.11"
|
||||
subtle = "2.4"
|
||||
blake2 = "0.9"
|
||||
hkdf = "0.11"
|
||||
argon2 = "0.3"
|
||||
|
|
25
README.md
25
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 142 bytes
|
||||
* Increase the plaintext size of only 113 bytes
|
||||
* Encryption from STDIN/STDOUT or from files
|
||||
* Adjustable performance & secuity parameters
|
||||
|
||||
|
@ -42,17 +42,17 @@ cat my-super-secret-logs-file.log | doby - logs.doby
|
|||
|
||||
Speicfy password from the command line:
|
||||
```bash
|
||||
doby -p "A super very ultra strong passphrase" my-super-secret-document.pdf document.doby
|
||||
doby --password "A super very ultra strong passphrase" my-super-secret-document.pdf document.doby
|
||||
```
|
||||
|
||||
Double encryption:
|
||||
```bash
|
||||
doby -p "first password" my-super-secret-database.db | doby -f - double-encrypted.doby
|
||||
doby --password "first password" my-super-secret-database.db | doby -f - double-encrypted.doby
|
||||
```
|
||||
|
||||
Increase password brute-force resistance:
|
||||
```bash
|
||||
echo "you-will-never-break-this" | doby --memory-cost 524288 --threads 16 --iterations 40 > my-super-secret-password.doby
|
||||
echo "you-will-never-break-this" | doby --memory-cost 524288 --parallelism 16 --time-cost 40 > my-super-secret-password.doby
|
||||
```
|
||||
|
||||
## Full Options
|
||||
|
@ -63,14 +63,15 @@ USAGE:
|
|||
|
||||
FLAGS:
|
||||
-f, --force-encrypt Encrypt even if doby format is recognized
|
||||
-i, --interactive Prompt before overwriting files
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-p, --password <password> Password used to derive encryption keys
|
||||
-i, --iterations <iterations> Argon2 time cost [default: 10]
|
||||
-m, --memory-cost <memory cost> Argon2 memory cost (in kilobytes) [default: 4096]
|
||||
-t, --threads <threads> Argon2 parallelism (between 1 and 255) [default: 4]
|
||||
--password <password> Password used to derive encryption keys
|
||||
-t, --time-cost <iterations> Argon2 time cost [default: 10]
|
||||
-m, --memory-cost <memory size> Argon2 memory cost (in kilobytes) [default: 4096]
|
||||
-p, --parallelism <threads> Argon2 parallelism cost [default: 4]
|
||||
-b, --block-size <blocksize> Size of the I/O buffer (in bytes) [default: 65536]
|
||||
-c, --cipher <cipher> Encryption cipher to use [possible values: aes, xchacha20]
|
||||
|
||||
|
@ -151,9 +152,9 @@ NOTE: To reduce the size of the header, the `nonce` is derived from the `master_
|
|||
Next, doby initializes a [BLAKE2b](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) HMAC with `authentication_key` and add all public encryption parameters to it.
|
||||
|
||||
```rust
|
||||
let hmac = Hmac::new(
|
||||
let hmac = Blake2b::new_keyed(
|
||||
authentication_key,
|
||||
blake2b, //hash function
|
||||
32, //digest size
|
||||
);
|
||||
hmac.update(random_salt);
|
||||
//integers are encoded in big-endian
|
||||
|
@ -216,7 +217,7 @@ So here is what an encrypted file layout looks like:
|
|||
</tr>
|
||||
<tr>
|
||||
<th align="left">HMAC</th>
|
||||
<td>64 bytes</td>
|
||||
<td>32 bytes</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -253,7 +254,7 @@ while n != 0 {
|
|||
Once the whole ciphertext is decrypted, doby computes and verifies the HMAC.
|
||||
|
||||
```rust
|
||||
hmac.digest() == last_64_bytes_read // the default blake2b output size is 64 bytes
|
||||
hmac.digest() == last_32_bytes_read
|
||||
```
|
||||
|
||||
If the verification success, the file is successfully decrypted and authenticated.
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
};
|
||||
use doby::{
|
||||
encrypt, decrypt,
|
||||
crypto::{ArgonParams, EncryptionParams, CipherAlgorithm, DobyCipher}
|
||||
crypto::{EncryptionParams, CipherAlgorithm, DobyCipher}
|
||||
};
|
||||
|
||||
const MAX_BLOCK_SIZE: usize = 1_073_741_824; //1GB
|
||||
|
@ -33,11 +33,10 @@ fn main() -> io::Result<()> {
|
|||
let input = File::open(&args[1])?;
|
||||
let output = OpenOptions::new().create(true).truncate(true).write(true).open(&args[2])?;
|
||||
|
||||
let params = EncryptionParams::new(ArgonParams{
|
||||
t_cost: 1,
|
||||
m_cost: 8,
|
||||
parallelism: 1,
|
||||
}, CipherAlgorithm::AesCtr);
|
||||
let params = EncryptionParams::new(
|
||||
argon2::Params::new(8, 1, 1, None).unwrap(),
|
||||
CipherAlgorithm::AesCtr
|
||||
);
|
||||
|
||||
let mut best_encrypt_time = None;
|
||||
let mut best_encrypt_block_size = None;
|
||||
|
@ -49,7 +48,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.into(), ¶ms).unwrap();
|
||||
let cipher = DobyCipher::new(PASSWORD.as_bytes(), ¶ms);
|
||||
let t_encrypt = Instant::now();
|
||||
encrypt(&mut reader, &mut writer, ¶ms, cipher, block_size, None)?;
|
||||
writer.flush()?;
|
||||
|
@ -59,7 +58,7 @@ fn main() -> io::Result<()> {
|
|||
reset(&mut reader)?;
|
||||
reset(&mut writer)?;
|
||||
|
||||
let cipher = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap();
|
||||
let cipher = DobyCipher::new(PASSWORD.as_bytes(), ¶ms);
|
||||
let t_decrypt = Instant::now();
|
||||
decrypt(&mut reader, &mut writer, cipher, block_size)?;
|
||||
writer.flush()?;
|
||||
|
|
|
@ -10,12 +10,12 @@ fn main() -> io::Result<()> {
|
|||
if magic_bytes == MAGIC_BYTES {
|
||||
match EncryptionParams::read(&mut file)? {
|
||||
Some(params) => {
|
||||
println!("Argon2 time cost: {}", params.argon2.t_cost);
|
||||
println!("Argon2 memory cost: {}KB", params.argon2.m_cost);
|
||||
println!("Argon2 parallelism: {}", params.argon2.parallelism);
|
||||
println!("Argon2 time cost: {}", params.argon2.t_cost());
|
||||
println!("Argon2 memory cost: {}KB", params.argon2.m_cost());
|
||||
println!("Argon2 parallelism cost: {}", params.argon2.p_cost());
|
||||
println!("Encryption cihpher: {}", params.cipher);
|
||||
}
|
||||
None => eprintln!("Invalid cipher")
|
||||
None => eprintln!("Invalid parameters")
|
||||
}
|
||||
} else {
|
||||
eprintln!("Doby format not recognized.");
|
||||
|
|
106
src/cli.rs
106
src/cli.rs
|
@ -1,25 +1,37 @@
|
|||
use std::{
|
||||
path::Path,
|
||||
fs::File,
|
||||
str::FromStr,
|
||||
io::{stdin, stdout, Read},
|
||||
};
|
||||
use std::{fs::File, io::{self, Read, stdin, stdout}, path::Path, str::FromStr};
|
||||
use clap::{crate_name, crate_version, App, Arg, AppSettings};
|
||||
use crate::{LazyWriter, Password, crypto::{ArgonParams, CipherAlgorithm}};
|
||||
use crate::{WrappedWriter, WrappedPassword, crypto::CipherAlgorithm};
|
||||
|
||||
cpufeatures::new!(aes_ni, "aes");
|
||||
|
||||
pub struct CliArgs {
|
||||
pub password: Password,
|
||||
pub password: WrappedPassword,
|
||||
pub force_encrypt: bool,
|
||||
pub argon2_params: ArgonParams,
|
||||
pub argon2_params: argon2::Params,
|
||||
pub cipher: CipherAlgorithm,
|
||||
pub block_size: usize,
|
||||
pub reader: Box<dyn Read>,
|
||||
pub writer: LazyWriter<String>,
|
||||
pub writer: WrappedWriter<String>,
|
||||
}
|
||||
|
||||
pub fn parse() -> Option<CliArgs> {
|
||||
pub struct ParseResult {
|
||||
pub error: bool,
|
||||
pub cli_args: Option<CliArgs>,
|
||||
}
|
||||
|
||||
impl ParseResult {
|
||||
fn exited() -> Self {
|
||||
Self { error: false, cli_args: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CliArgs> for ParseResult {
|
||||
fn from(args: CliArgs) -> Self {
|
||||
ParseResult { error: false, cli_args: Some(args) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse() -> Option<ParseResult> {
|
||||
let app = App::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
|
@ -27,22 +39,27 @@ pub fn parse() -> Option<CliArgs> {
|
|||
.arg(Arg::with_name("INPUT").help("<PATH> | \"-\" or empty for stdin"))
|
||||
.arg(Arg::with_name("OUTPUT").help("<PATH> | \"-\" or empty for stdout"))
|
||||
.arg(
|
||||
Arg::with_name("force-encrypt")
|
||||
Arg::with_name("1_force_encrypt")
|
||||
.short("f")
|
||||
.long("force-encrypt")
|
||||
.help(&format!("Encrypt even if {} format is recognized", crate_name!()))
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("2_interactive")
|
||||
.short("i")
|
||||
.long("interactive")
|
||||
.help("Prompt before overwriting files")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("1_password")
|
||||
.short("p")
|
||||
.long("password")
|
||||
.value_name("password")
|
||||
.help("Password used to derive encryption keys")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("2_t_cost")
|
||||
.short("i")
|
||||
.long("iterations")
|
||||
.short("t")
|
||||
.long("time-cost")
|
||||
.value_name("iterations")
|
||||
.help("Argon2 time cost")
|
||||
.default_value("10")
|
||||
|
@ -51,16 +68,16 @@ pub fn parse() -> Option<CliArgs> {
|
|||
Arg::with_name("3_m_cost")
|
||||
.short("m")
|
||||
.long("memory-cost")
|
||||
.value_name("memory cost")
|
||||
.value_name("memory size")
|
||||
.help("Argon2 memory cost (in kilobytes)")
|
||||
.default_value("4096")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("4_parallelism")
|
||||
.short("t")
|
||||
.long("threads")
|
||||
Arg::with_name("4_p_cost")
|
||||
.short("p")
|
||||
.long("parallelism")
|
||||
.value_name("threads")
|
||||
.help("Argon2 parallelism (between 1 and 255)")
|
||||
.help("Argon2 parallelism cost")
|
||||
.default_value("4")
|
||||
)
|
||||
.arg(
|
||||
|
@ -85,14 +102,16 @@ pub fn parse() -> Option<CliArgs> {
|
|||
let params = {
|
||||
let t_cost = number(app.value_of("2_t_cost").unwrap())?;
|
||||
let m_cost = number(app.value_of("3_m_cost").unwrap())?;
|
||||
let parallelism = number(app.value_of("4_parallelism").unwrap())?;
|
||||
let p_cost = number(app.value_of("4_p_cost").unwrap())?;
|
||||
|
||||
ArgonParams {
|
||||
t_cost,
|
||||
m_cost,
|
||||
parallelism,
|
||||
match argon2::Params::new(m_cost, t_cost, p_cost, None) {
|
||||
Ok(params) => Some(params),
|
||||
Err(e) => {
|
||||
eprintln!("Invalid Argon2 parameters: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
}?;
|
||||
|
||||
let cipher = app
|
||||
.value_of("cipher")
|
||||
|
@ -125,28 +144,37 @@ pub fn parse() -> Option<CliArgs> {
|
|||
None => Box::new(stdin())
|
||||
};
|
||||
|
||||
let output = app
|
||||
let wrapped_writer = match app
|
||||
.value_of("OUTPUT")
|
||||
.and_then(|s| if s == "-" { None } else { Some(s) })
|
||||
.map(|s| {
|
||||
if Path::new(s).exists() {
|
||||
eprintln!("WARNING: {} already exists", s);
|
||||
None
|
||||
} else {
|
||||
Some(LazyWriter::from_path(s.to_owned()))
|
||||
.and_then(|s| if s == "-" { None } else { Some(s) }) {
|
||||
Some(path) => {
|
||||
if {
|
||||
if app.is_present("2_interactive") && Path::new(path).exists() {
|
||||
eprint!("Warning: {} already exists. Overwrite [y/N]? ", path);
|
||||
let mut c = String::with_capacity(2);
|
||||
io::stdin().read_line(&mut c).unwrap();
|
||||
!c.is_empty() && c.chars().nth(0).unwrap() == 'y'
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} {
|
||||
WrappedWriter::from_path(path.to_string())
|
||||
} else {
|
||||
return Some(ParseResult::exited())
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| Some(LazyWriter::from_writer(stdout())))?;
|
||||
None => WrappedWriter::from_writer(stdout())
|
||||
};
|
||||
|
||||
Some(CliArgs {
|
||||
password: app.value_of("1_password").into(),
|
||||
force_encrypt: app.is_present("force-encrypt"),
|
||||
force_encrypt: app.is_present("1_force_encrypt"),
|
||||
argon2_params: params,
|
||||
cipher,
|
||||
block_size,
|
||||
reader: input,
|
||||
writer: output,
|
||||
})
|
||||
writer: wrapped_writer,
|
||||
}.into())
|
||||
}
|
||||
|
||||
fn number<T: FromStr>(val: &str) -> Option<T> {
|
||||
|
|
186
src/crypto.rs
186
src/crypto.rs
|
@ -1,34 +1,20 @@
|
|||
use std::{convert::{TryFrom, TryInto}, fmt::{self, Display, Formatter}, io::{self, Read, Write}};
|
||||
use std::{convert::TryFrom, fmt::{self, Display, Formatter}, io::{self, Read, Write}};
|
||||
use blake2::{Blake2b, VarBlake2b, digest::{Update, VariableOutput}};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use chacha20::XChaCha20;
|
||||
use aes::{Aes256Ctr, cipher::{NewCipher, StreamCipher}};
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use subtle::ConstantTimeEq;
|
||||
use rand::{Rng, rngs::OsRng};
|
||||
use argon2::{Argon2, Version, Algorithm};
|
||||
use hkdf::Hkdf;
|
||||
use zeroize::Zeroize;
|
||||
use crate::Password;
|
||||
|
||||
pub const SALT_LEN: usize = 64;
|
||||
const AES_NONCE_LEN: usize = 16;
|
||||
const XCHACHA20_NONCE_LEN: usize = 24;
|
||||
pub const HASH_LEN: usize = 64;
|
||||
pub const HMAC_LEN: usize = 32;
|
||||
const KEY_LEN: usize = 32;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ArgonParams {
|
||||
pub t_cost: u32,
|
||||
pub m_cost: u32,
|
||||
pub parallelism: u8,
|
||||
}
|
||||
|
||||
impl TryFrom<ArgonParams> for argon2::Params {
|
||||
type Error = argon2::Error;
|
||||
fn try_from(params: ArgonParams) -> Result<Self, Self::Error> {
|
||||
argon2::Params::new(params.m_cost, params.t_cost, params.parallelism.into(), None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum CipherAlgorithm {
|
||||
|
@ -57,14 +43,14 @@ impl Display for CipherAlgorithm {
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct EncryptionParams {
|
||||
salt: [u8; SALT_LEN],
|
||||
pub argon2: ArgonParams,
|
||||
pub argon2: argon2::Params,
|
||||
pub cipher: CipherAlgorithm,
|
||||
}
|
||||
|
||||
impl EncryptionParams {
|
||||
pub const LEN: usize = SALT_LEN + 4*2 + 2;
|
||||
pub const LEN: usize = SALT_LEN + 4*3 + 1;
|
||||
|
||||
pub fn new(argon2_params: ArgonParams, cipher: CipherAlgorithm) -> EncryptionParams {
|
||||
pub fn new(argon2_params: argon2::Params, cipher: CipherAlgorithm) -> EncryptionParams {
|
||||
let mut salt = [0; SALT_LEN];
|
||||
OsRng.fill(&mut salt);
|
||||
EncryptionParams {
|
||||
|
@ -76,9 +62,9 @@ impl EncryptionParams {
|
|||
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(&self.salt)?;
|
||||
writer.write_all(&self.argon2.t_cost.to_be_bytes())?;
|
||||
writer.write_all(&self.argon2.m_cost.to_be_bytes())?;
|
||||
writer.write_all(&self.argon2.parallelism.to_be_bytes())?;
|
||||
writer.write_all(&self.argon2.t_cost().to_be_bytes())?;
|
||||
writer.write_all(&self.argon2.m_cost().to_be_bytes())?;
|
||||
writer.write_all(&self.argon2.p_cost().to_be_bytes())?;
|
||||
writer.write_all(&(self.cipher as u8).to_be_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -90,97 +76,75 @@ impl EncryptionParams {
|
|||
reader.read_exact(&mut t_cost)?;
|
||||
let mut m_cost = [0; 4];
|
||||
reader.read_exact(&mut m_cost)?;
|
||||
let mut parallelism = [0; 1];
|
||||
reader.read_exact(&mut parallelism)?;
|
||||
let mut p_cost = [0; 4];
|
||||
reader.read_exact(&mut p_cost)?;
|
||||
let mut cipher_buff = [0; 1];
|
||||
reader.read_exact(&mut cipher_buff)?;
|
||||
match CipherAlgorithm::try_from(cipher_buff[0]) {
|
||||
Ok(cipher) => {
|
||||
let argon2_params = ArgonParams {
|
||||
t_cost: u32::from_be_bytes(t_cost),
|
||||
m_cost: u32::from_be_bytes(m_cost),
|
||||
parallelism: u8::from_be_bytes(parallelism),
|
||||
};
|
||||
|
||||
Ok(Some(EncryptionParams {
|
||||
if let Ok(cipher) = CipherAlgorithm::try_from(cipher_buff[0]) {
|
||||
if let Ok(argon2_params) = argon2::Params::new(
|
||||
u32::from_be_bytes(m_cost),
|
||||
u32::from_be_bytes(t_cost),
|
||||
u32::from_be_bytes(p_cost),
|
||||
None
|
||||
) {
|
||||
return Ok(Some(EncryptionParams {
|
||||
salt,
|
||||
argon2: argon2_params,
|
||||
cipher,
|
||||
}))
|
||||
}));
|
||||
}
|
||||
Err(_) => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DobyCipher {
|
||||
cipher: Box<dyn StreamCipher>,
|
||||
hmac: Hmac<blake2::Blake2b>,
|
||||
hasher: VarBlake2b,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl DobyCipher {
|
||||
pub fn new(mut password: Password, params: &EncryptionParams) -> Result<Self, argon2::Error> {
|
||||
match params.argon2.try_into() {
|
||||
Ok(argon2_params) => {
|
||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params);
|
||||
let mut master_key = [0; KEY_LEN];
|
||||
let password = password.unwrap_or_ask();
|
||||
argon2.hash_password_into(password.as_bytes(), ¶ms.salt, &mut master_key).zeroize(password)?;
|
||||
let hkdf = Hkdf::<blake2::Blake2b>::new(Some(¶ms.salt), &master_key);
|
||||
master_key.zeroize();
|
||||
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];
|
||||
hkdf.expand(b"doby_authentication_key", &mut authentication_key).unwrap();
|
||||
pub fn new(password: &[u8], params: &EncryptionParams) -> Self {
|
||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params.argon2.clone());
|
||||
let mut master_key = [0; KEY_LEN];
|
||||
argon2.hash_password_into(password, ¶ms.salt, &mut master_key).unwrap();
|
||||
let hkdf = Hkdf::<Blake2b>::new(Some(¶ms.salt), &master_key);
|
||||
master_key.zeroize();
|
||||
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];
|
||||
hkdf.expand(b"doby_authentication_key", &mut authentication_key).unwrap();
|
||||
|
||||
let mut encoded_params = Vec::with_capacity(EncryptionParams::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(EncryptionParams::LEN);
|
||||
params.write(&mut encoded_params).unwrap();
|
||||
let mut hasher = VarBlake2b::new_keyed(&authentication_key, HMAC_LEN);
|
||||
authentication_key.zeroize();
|
||||
hasher.update(&encoded_params);
|
||||
|
||||
let cipher: Box<dyn StreamCipher> = match params.cipher {
|
||||
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();
|
||||
let cipher: Box<dyn StreamCipher> = match params.cipher {
|
||||
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();
|
||||
|
||||
Ok(Self {
|
||||
cipher,
|
||||
hmac,
|
||||
buffer: Vec::new(),
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
password.zeroize();
|
||||
Err(e)
|
||||
}
|
||||
Self {
|
||||
cipher,
|
||||
hasher,
|
||||
buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt_chunk<W: Write>(&mut self, buff: &mut [u8], writer: &mut W) -> io::Result<()> {
|
||||
self.cipher.apply_keystream(buff);
|
||||
self.hmac.update(buff);
|
||||
self.hasher.update(&buff);
|
||||
writer.write_all(buff)
|
||||
}
|
||||
|
||||
pub fn write_hmac<W: Write>(self, writer: &mut W) -> io::Result<usize> {
|
||||
let tag = self.hmac.finalize().into_bytes();
|
||||
writer.write(&tag)
|
||||
pub fn write_hmac<W: Write>(self, writer: &mut W) -> io::Result<()> {
|
||||
writer.write_all(&self.hasher.finalize_boxed())
|
||||
}
|
||||
|
||||
//buff size must be > to HASH_LEN
|
||||
|
@ -189,44 +153,43 @@ impl DobyCipher {
|
|||
buff[..buffer_len].clone_from_slice(&self.buffer);
|
||||
let read = reader.read(&mut buff[buffer_len..])?;
|
||||
|
||||
let n = if buffer_len + read >= HASH_LEN {
|
||||
let n = if buffer_len + read >= HMAC_LEN {
|
||||
self.buffer.clear();
|
||||
buffer_len + read - HASH_LEN
|
||||
buffer_len + read - HMAC_LEN
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.buffer.extend_from_slice(&buff[n..buffer_len+read]);
|
||||
|
||||
self.hmac.update(&buff[..n]);
|
||||
self.hasher.update(&buff[..n]);
|
||||
self.cipher.apply_keystream(&mut buff[..n]);
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
pub fn verify_hmac(self) -> bool {
|
||||
self.hmac.verify(&self.buffer).is_ok()
|
||||
self.hasher.finalize_boxed().ct_eq(&self.buffer).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ArgonParams, CipherAlgorithm, EncryptionParams, DobyCipher, HASH_LEN};
|
||||
use super::{CipherAlgorithm, EncryptionParams, DobyCipher, HMAC_LEN};
|
||||
#[test]
|
||||
fn encryption_params() {
|
||||
let params = EncryptionParams::new(ArgonParams {
|
||||
t_cost: 1,
|
||||
m_cost: 8,
|
||||
parallelism: 1,
|
||||
}, CipherAlgorithm::XChaCha20);
|
||||
let params = EncryptionParams::new(
|
||||
argon2::Params::new(8, 1, 1, None).unwrap(),
|
||||
CipherAlgorithm::XChaCha20
|
||||
);
|
||||
|
||||
assert_eq!(EncryptionParams::LEN, 74);
|
||||
assert_eq!(EncryptionParams::LEN, 77);
|
||||
|
||||
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[72..76], vec![0, 0, 0, 0x01]); //p_cost
|
||||
assert_eq!(buff[76], CipherAlgorithm::XChaCha20 as u8);
|
||||
|
||||
let new_params = EncryptionParams::read(&mut buff.as_slice()).unwrap().unwrap();
|
||||
assert_eq!(new_params, params);
|
||||
|
@ -234,25 +197,24 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn doby_cipher() {
|
||||
let params = EncryptionParams::new(ArgonParams {
|
||||
t_cost: 1,
|
||||
m_cost: 8,
|
||||
parallelism: 1,
|
||||
}, CipherAlgorithm::AesCtr);
|
||||
let params = EncryptionParams::new(
|
||||
argon2::Params::new(8, 1, 1, None).unwrap(),
|
||||
CipherAlgorithm::AesCtr
|
||||
);
|
||||
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 vec = Vec::with_capacity(buff.len()+HMAC_LEN);
|
||||
|
||||
let mut enc_cipher = DobyCipher::new(password.into(), ¶ms).unwrap();
|
||||
let mut enc_cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
||||
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);
|
||||
assert!(enc_cipher.write_hmac(&mut vec).is_ok());
|
||||
assert_eq!(vec.len(), buff.len()+HMAC_LEN);
|
||||
|
||||
let mut dec_cipher = DobyCipher::new(password.into(), ¶ms).unwrap();
|
||||
let mut decrypted = vec![0; buff.len()+HASH_LEN];
|
||||
let mut dec_cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
||||
let mut decrypted = vec![0; buff.len()+HMAC_LEN];
|
||||
let mut n = dec_cipher.decrypt_chunk(&mut vec.as_slice(), &mut decrypted[..]).unwrap();
|
||||
assert_eq!(n, buff.len());
|
||||
n = dec_cipher.decrypt_chunk(&mut &vec[n..], &mut decrypted[n..]).unwrap();
|
||||
|
|
82
src/lib.rs
82
src/lib.rs
|
@ -1,68 +1,68 @@
|
|||
pub mod cli;
|
||||
pub mod crypto;
|
||||
|
||||
use std::{fs::File, path::Path, io::{self, Read, Write}};
|
||||
use std::{fmt::Display, fs::OpenOptions, io::{self, BufWriter, Read, Write}, path::Path};
|
||||
use crypto::{DobyCipher, EncryptionParams};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub const MAGIC_BYTES: &[u8; 4] = b"DOBY";
|
||||
|
||||
pub struct Password(Option<String>);
|
||||
pub struct WrappedPassword(Option<String>);
|
||||
|
||||
impl Password {
|
||||
fn unwrap_or_ask(self) -> String {
|
||||
self.0.unwrap_or_else(|| rpassword::read_password_from_tty(Some("Password: ")).unwrap())
|
||||
impl WrappedPassword {
|
||||
pub fn get(self, ask_confirm: bool) -> Option<String> {
|
||||
self.0.or_else(|| {
|
||||
let mut password = rpassword::read_password_from_tty(Some("Password: ")).ok()?;
|
||||
if ask_confirm {
|
||||
let mut password_confirm = rpassword::read_password_from_tty(Some("Password (confirm): ")).ok()?;
|
||||
if password == password_confirm {
|
||||
password_confirm.zeroize();
|
||||
Some(password)
|
||||
} else {
|
||||
password.zeroize();
|
||||
password_confirm.zeroize();
|
||||
eprintln!("Passwords don't match");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(password)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&str>> for Password {
|
||||
impl From<Option<&str>> for WrappedPassword {
|
||||
fn from(s: Option<&str>) -> Self {
|
||||
Self(s.map(String::from))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Password {
|
||||
fn from(s: &str) -> Self {
|
||||
Some(s).into()
|
||||
pub enum WrappedWriter<P: AsRef<Path>> {
|
||||
PATH {
|
||||
path: P
|
||||
},
|
||||
WRITER {
|
||||
writer: Box<dyn Write>
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Password {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LazyWriter<P: AsRef<Path>> {
|
||||
path: Option<P>,
|
||||
writer: Option<Box<dyn Write>>,
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>> LazyWriter<P> {
|
||||
impl<P: AsRef<Path> + Display> WrappedWriter<P> {
|
||||
fn from_path(path: P) -> Self {
|
||||
Self {
|
||||
path: Some(path),
|
||||
writer: None,
|
||||
}
|
||||
Self::PATH { path }
|
||||
}
|
||||
|
||||
fn from_writer<T: 'static + Write>(writer: T) -> Self {
|
||||
Self {
|
||||
path: None,
|
||||
writer: Some(Box::new(writer)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: AsRef<Path>> Write for LazyWriter<P> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
if self.writer.is_none() {
|
||||
self.writer = Some(Box::new(File::create(self.path.as_ref().unwrap()).unwrap()));
|
||||
}
|
||||
self.writer.as_mut().unwrap().write(buf)
|
||||
Self::WRITER { writer: Box::new(writer) }
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.writer.as_mut().unwrap().flush()
|
||||
pub fn into_buf_writer(self) -> Option<BufWriter<Box<dyn Write>>> {
|
||||
Some(BufWriter::new(match self {
|
||||
Self::PATH { path } => Box::new(
|
||||
OpenOptions::new().write(true).create(true).truncate(true).open(path.as_ref())
|
||||
.map_err(|e| eprintln!("{}: {}", path, e))
|
||||
.ok()?
|
||||
) as Box<dyn Write>,
|
||||
Self::WRITER { writer } => writer,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
83
src/main.rs
83
src/main.rs
|
@ -1,4 +1,4 @@
|
|||
use std::{process, io::{BufWriter, BufReader, Read}};
|
||||
use std::{process, io::{BufReader, Read}};
|
||||
use doby::{
|
||||
cli,
|
||||
crypto::{EncryptionParams, DobyCipher},
|
||||
|
@ -6,63 +6,72 @@ use doby::{
|
|||
decrypt,
|
||||
encrypt,
|
||||
};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
fn run() -> bool {
|
||||
let mut success = false;
|
||||
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);
|
||||
if let Some(result) = cli::parse() {
|
||||
if let Some(cli_args) = result.cli_args {
|
||||
let mut reader = BufReader::new(cli_args.reader);
|
||||
|
||||
let mut magic_bytes = vec![0; MAGIC_BYTES.len()];
|
||||
match reader.read(&mut magic_bytes) {
|
||||
Ok(n) => {
|
||||
if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt
|
||||
match EncryptionParams::read(&mut reader) {
|
||||
Ok(params) => {
|
||||
match params {
|
||||
Some(params) => {
|
||||
match DobyCipher::new(cli_args.password, ¶ms) {
|
||||
Ok(cipher) => {
|
||||
let mut magic_bytes = vec![0; MAGIC_BYTES.len()];
|
||||
match reader.read(&mut magic_bytes) {
|
||||
Ok(n) => {
|
||||
if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt
|
||||
match EncryptionParams::read(&mut reader) {
|
||||
Ok(params) => {
|
||||
if let Some(params) = params {
|
||||
if let Some(mut password) = cli_args.password.get(false) {
|
||||
if let Some(mut writer) = cli_args.writer.into_buf_writer() {
|
||||
let cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
||||
password.zeroize();
|
||||
match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) {
|
||||
Ok(verified) => {
|
||||
if verified {
|
||||
success = true
|
||||
} else {
|
||||
eprintln!("WARNING: HMAC verification failed !\nEither your password is incorrect or the ciphertext has been corrupted.\nBe careful, the data could have been altered by an attacker.");
|
||||
eprintln!("Warning: HMAC verification failed !\nEither your password is incorrect or the ciphertext has been corrupted.\nBe careful, the data could have been altered by an attacker.");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while decrypting: {}", e)
|
||||
}
|
||||
} else {
|
||||
password.zeroize();
|
||||
}
|
||||
Err(e) => eprintln!("Invalid argon2 params: {}", e)
|
||||
}
|
||||
} else {
|
||||
eprintln!("Invalid parameters")
|
||||
}
|
||||
None => eprintln!("Invalid cipher")
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while reading headers: {}", e)
|
||||
}
|
||||
} else { //otherwise, encrypt
|
||||
let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher);
|
||||
if let Some(mut password) = cli_args.password.get(true) {
|
||||
if let Some(mut writer) = cli_args.writer.into_buf_writer() {
|
||||
let cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
||||
password.zeroize();
|
||||
match encrypt(
|
||||
&mut reader,
|
||||
&mut writer,
|
||||
¶ms,
|
||||
cipher,
|
||||
cli_args.block_size,
|
||||
Some(&magic_bytes[..n])
|
||||
) {
|
||||
Ok(_) => success = true,
|
||||
Err(e) => eprintln!("I/O error while encrypting: {}", e)
|
||||
}
|
||||
} else {
|
||||
password.zeroize();
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while reading headers: {}", e)
|
||||
}
|
||||
} else { //otherwise, encrypt
|
||||
let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher);
|
||||
match DobyCipher::new(cli_args.password, ¶ms) {
|
||||
Ok(cipher) => {
|
||||
match encrypt(
|
||||
&mut reader,
|
||||
&mut writer,
|
||||
¶ms,
|
||||
cipher,
|
||||
cli_args.block_size,
|
||||
Some(&magic_bytes[..n])
|
||||
) {
|
||||
Ok(_) => success = true,
|
||||
Err(e) => eprintln!("I/O error while encrypting: {}", e)
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Invalid argon2 params: {}", e)
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while reading magic bytes: {}", e),
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while reading magic bytes: {}", e),
|
||||
} else {
|
||||
success = !result.error;
|
||||
}
|
||||
}
|
||||
success
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use rand::Rng;
|
||||
use doby::{
|
||||
crypto::{
|
||||
ArgonParams,
|
||||
CipherAlgorithm,
|
||||
EncryptionParams,
|
||||
DobyCipher,
|
||||
|
@ -19,15 +18,14 @@ fn different_elements<T: Eq>(v1: &Vec<T>, v2: &Vec<T>) -> usize {
|
|||
fn authentication() {
|
||||
const BLOCK_SIZE: usize = 65536;
|
||||
const PLAINTEXT: &[u8; 13] = b"the plaintext";
|
||||
const CIPHERTEXT_SIZE: usize = PLAINTEXT.len()+142;
|
||||
const CIPHERTEXT_SIZE: usize = PLAINTEXT.len()+113;
|
||||
const PASSWORD: &str = "the password";
|
||||
let params = EncryptionParams::new(ArgonParams {
|
||||
t_cost: 1,
|
||||
m_cost: 8,
|
||||
parallelism: 1,
|
||||
}, CipherAlgorithm::AesCtr);
|
||||
let params = EncryptionParams::new(
|
||||
argon2::Params::new(8, 1, 1, None).unwrap(),
|
||||
CipherAlgorithm::AesCtr
|
||||
);
|
||||
|
||||
let encrypter = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap();
|
||||
let encrypter = DobyCipher::new(PASSWORD.as_bytes(), ¶ms);
|
||||
let mut ciphertext = Vec::with_capacity(CIPHERTEXT_SIZE);
|
||||
encrypt(&mut &PLAINTEXT[..], &mut ciphertext, ¶ms, encrypter, BLOCK_SIZE, None).unwrap();
|
||||
assert_eq!(ciphertext.len(), CIPHERTEXT_SIZE);
|
||||
|
@ -38,13 +36,13 @@ fn authentication() {
|
|||
compromised[i] = rand::thread_rng().gen();
|
||||
}
|
||||
assert_eq!(different_elements(&compromised, &ciphertext), 1);
|
||||
let decrypter = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap();
|
||||
let decrypter = DobyCipher::new(PASSWORD.as_bytes(), ¶ms);
|
||||
let mut decrypted = Vec::with_capacity(PLAINTEXT.len());
|
||||
let verified = decrypt(&mut &compromised[..], &mut decrypted, decrypter, BLOCK_SIZE).unwrap();
|
||||
assert_eq!(verified, false);
|
||||
}
|
||||
|
||||
let decrypter = DobyCipher::new(PASSWORD.into(), ¶ms).unwrap();
|
||||
let decrypter = DobyCipher::new(PASSWORD.as_bytes(), ¶ms);
|
||||
let mut decrypted = Vec::with_capacity(PLAINTEXT.len());
|
||||
let verified = decrypt(&mut &ciphertext[4+EncryptionParams::LEN..], &mut decrypted, decrypter, BLOCK_SIZE).unwrap();
|
||||
assert_eq!(decrypted, PLAINTEXT);
|
||||
|
|
27
tests/cli.rs
27
tests/cli.rs
|
@ -1,7 +1,7 @@
|
|||
use std::{convert::TryInto, fs::{self, File, create_dir}, io::{self, Read, Write}, path::PathBuf};
|
||||
use assert_cmd::{Command, cargo::{CargoError, cargo_bin}};
|
||||
use tempfile::TempDir;
|
||||
use doby::crypto::{CipherAlgorithm, SALT_LEN, HASH_LEN};
|
||||
use doby::crypto::{CipherAlgorithm, SALT_LEN, HMAC_LEN};
|
||||
|
||||
const PLAINTEXT: &[u8] = b"the plaintext";
|
||||
const PASSWORD: &str = "the password";
|
||||
|
@ -22,7 +22,7 @@ fn setup_files<'a>() -> io::Result<(PathBuf, PathBuf, PathBuf)> {
|
|||
|
||||
fn doby_cmd() -> Result<Command, CargoError> {
|
||||
let mut cmd = Command::cargo_bin("doby")?;
|
||||
cmd.arg("-p").arg(PASSWORD);
|
||||
cmd.arg("--password").arg(PASSWORD);
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ fn files() -> io::Result<()> {
|
|||
fn stdout() -> io::Result<()> {
|
||||
let (_, tmp_plaintext, tmp_ciphertext) = setup_files()?;
|
||||
|
||||
let shell_cmd = format!("{} -p \"{}\" {} > {}", cargo_bin("doby").to_str().unwrap(), PASSWORD, tmp_plaintext.to_str().unwrap(), tmp_ciphertext.to_str().unwrap());
|
||||
let shell_cmd = format!("{} --password \"{}\" {} > {}", cargo_bin("doby").to_str().unwrap(), PASSWORD, tmp_plaintext.to_str().unwrap(), tmp_ciphertext.to_str().unwrap());
|
||||
bash_cmd().arg(shell_cmd).assert().success().stdout("").stderr("");
|
||||
|
||||
doby_cmd().unwrap().arg(tmp_ciphertext).assert().success().stdout(PLAINTEXT);
|
||||
|
@ -64,10 +64,10 @@ fn stdout() -> io::Result<()> {
|
|||
fn stdin() -> io::Result<()> {
|
||||
let (_, tmp_plaintext, tmp_ciphertext) = setup_files()?;
|
||||
|
||||
let mut shell_cmd = format!("cat {} | {} -p \"{}\" - {}", tmp_plaintext.to_str().unwrap(), cargo_bin("doby").to_str().unwrap(), PASSWORD, tmp_ciphertext.to_str().unwrap());
|
||||
let mut shell_cmd = format!("cat {} | {} --password \"{}\" - {}", tmp_plaintext.to_str().unwrap(), cargo_bin("doby").to_str().unwrap(), PASSWORD, tmp_ciphertext.to_str().unwrap());
|
||||
bash_cmd().arg(shell_cmd).assert().success().stdout("").stderr("");
|
||||
|
||||
shell_cmd = format!("cat {} | {} -p \"{}\"", tmp_ciphertext.to_str().unwrap(), cargo_bin("doby").to_str().unwrap(), PASSWORD);
|
||||
shell_cmd = format!("cat {} | {} --password \"{}\"", tmp_ciphertext.to_str().unwrap(), cargo_bin("doby").to_str().unwrap(), PASSWORD);
|
||||
bash_cmd().arg(shell_cmd).assert().success().stdout(PLAINTEXT);
|
||||
|
||||
Ok(())
|
||||
|
@ -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()+142);
|
||||
assert!(buff_ciphertext_2.len() >= buff_ciphertext_1.len()+113);
|
||||
|
||||
let tmp_decrypted_1 = tmp_path.join("decrypted_1");
|
||||
doby_cmd().unwrap().arg(tmp_ciphertext_2).arg(&tmp_decrypted_1).assert().success().stdout("").stderr("");
|
||||
|
@ -107,8 +107,8 @@ fn test_cipher(cipher_str: &str, cipher_algorithm: CipherAlgorithm) -> io::Resul
|
|||
doby_cmd().unwrap().arg("-c").arg(cipher_str).arg(tmp_plaintext).arg(&tmp_ciphertext).assert().success().stdout("").stderr("");
|
||||
|
||||
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);
|
||||
assert_eq!(ciphertext[4+SALT_LEN+4*3], cipher_algorithm as u8);
|
||||
assert_eq!(ciphertext.len(), PLAINTEXT.len()+17+SALT_LEN+HMAC_LEN);
|
||||
|
||||
doby_cmd().unwrap().arg(tmp_ciphertext).assert().success().stdout(PLAINTEXT).stderr("");
|
||||
|
||||
|
@ -127,17 +127,16 @@ fn aes_cipher() -> io::Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn argon2_params() -> io::Result<()> {
|
||||
Command::cargo_bin("doby").unwrap().arg("-i").arg("0").assert().failure().stderr("Invalid argon2 params: time cost is too small\n");
|
||||
Command::cargo_bin("doby").unwrap().arg("-m").arg("0").assert().failure().stderr("Invalid argon2 params: memory cost is too small\n");
|
||||
Command::cargo_bin("doby").unwrap().arg("-t").arg("0").assert().failure().stderr("Invalid argon2 params: not enough threads\n");
|
||||
Command::cargo_bin("doby").unwrap().arg("-t").arg("0").assert().failure().stderr("Invalid Argon2 parameters: time cost is too small\n");
|
||||
Command::cargo_bin("doby").unwrap().arg("-m").arg("0").assert().failure().stderr("Invalid Argon2 parameters: memory cost is too small\n");
|
||||
Command::cargo_bin("doby").unwrap().arg("-p").arg("0").assert().failure().stderr("Invalid Argon2 parameters: not enough threads\n");
|
||||
|
||||
let ciphertext = doby_cmd().unwrap().arg("-i").arg("8").arg("-m").arg("2048").arg("-t").arg("8").assert().success().stderr("").get_output().stdout.clone();
|
||||
let ciphertext = doby_cmd().unwrap().arg("-t").arg("8").arg("-m").arg("2048").arg("-p").arg("8").assert().success().stderr("").get_output().stdout.clone();
|
||||
assert_eq!(u32::from_be_bytes(ciphertext[4+SALT_LEN..4+SALT_LEN+4].try_into().unwrap()), 8); //time cost
|
||||
assert_eq!(u32::from_be_bytes(ciphertext[4+SALT_LEN+4..4+SALT_LEN+8].try_into().unwrap()), 2048); //memory cost
|
||||
assert_eq!(u8::from_be_bytes([ciphertext[4+SALT_LEN+8]]), 8); //parallelism
|
||||
assert_eq!(u32::from_be_bytes(ciphertext[4+SALT_LEN+8..4+SALT_LEN+12].try_into().unwrap()), 8); //parallelism
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue