XChaCha20 support
This commit is contained in:
parent
796a01376e
commit
4003185b8b
151
Cargo.lock
generated
151
Cargo.lock
generated
@ -15,6 +15,15 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.2.1"
|
||||
@ -37,6 +46,17 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.0.0"
|
||||
@ -93,6 +113,17 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@ -108,9 +139,13 @@ version = "2.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -157,6 +192,17 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
@ -173,9 +219,12 @@ dependencies = [
|
||||
"aes",
|
||||
"argon2",
|
||||
"blake3",
|
||||
"chacha20",
|
||||
"clap",
|
||||
"cpufeatures",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"num_enum",
|
||||
"rand",
|
||||
"rpassword",
|
||||
"zeroize",
|
||||
@ -202,6 +251,15 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hkdf"
|
||||
version = "0.11.0"
|
||||
@ -228,6 +286,28 @@ version = "0.2.97"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066"
|
||||
dependencies = [
|
||||
"derivative",
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
@ -251,6 +331,33 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
@ -301,12 +408,35 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
@ -316,6 +446,15 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
@ -328,6 +467,18 @@ version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
|
@ -15,9 +15,12 @@ codegen-units = 1
|
||||
opt-level = 3
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", default-features = false }
|
||||
clap = "2.33"
|
||||
rand = "0.8"
|
||||
aes = { version = "0.7", features = ["ctr", "armv8"] }
|
||||
num_enum = "0.5"
|
||||
cpufeatures = "0.1"
|
||||
aes = { version = "0.7", features = ["ctr"] }
|
||||
chacha20 = "0.7"
|
||||
hmac = "0.11"
|
||||
blake3 = "0.3"
|
||||
hkdf = "0.11"
|
||||
|
14
README.md
14
README.md
@ -5,7 +5,7 @@ Secure symmetric encryption from the command line
|
||||
|
||||
## Features
|
||||
|
||||
* 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))
|
||||
* 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)
|
||||
* Encryption from STDIN/STDOUT or from files
|
||||
@ -71,8 +71,9 @@ FLAGS:
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-p, --password <password>
|
||||
-p, --password <password> Password used to derive encryption keys
|
||||
-b, --block-size <blocksize> Size of file chunk (in bytes) [default: 65536]
|
||||
-c, --cipher <cipher> Encryption cipher to use [possible values: aes, xchacha20]
|
||||
-m, --memory-cost <memory cost> Argon2 memory cost (in kilobytes) [default: 4096]
|
||||
-t, --threads <threads> Argon2 parallelism (between 1 and 255) [default: 4]
|
||||
-i, --iterations <iterations> Argon2 time cost [default: 10]
|
||||
@ -141,15 +142,16 @@ hmac.update(argon2_time_cost);
|
||||
hmac.update(argon2_memory_cost);
|
||||
hmac.update(argon2_parallelism);
|
||||
hmac.update(random_hkdf_salt);
|
||||
hmac.update(random_nonce); //16 bytes random nonce used in AES-CTR cipher
|
||||
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 an AES-CTR cipher with `encryption_key` and `random_nonce` 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 `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.
|
||||
|
||||
```rust
|
||||
let cipher = Aes256Ctr::new(encryption_key, random_nonce);
|
||||
let cipher = Aes256Ctr::new(encryption_key, random_nonce); //example with AES-CTR
|
||||
let mut n = 1;
|
||||
let mut chunk: [u8; block_size] = [0; block_size];
|
||||
while n != 0 {
|
||||
@ -185,7 +187,7 @@ let master_key: [u8; 32] = argon2id(
|
||||
Then, doby starts decryption.
|
||||
|
||||
```rust
|
||||
let cipher = Aes256Ctr::new(encryption_key, nonce_read_from_input);
|
||||
let cipher = XChaCha20::new(encryption_key, nonce_read_from_input); //example with XChaCha20
|
||||
let mut n = 1;
|
||||
let mut chunk: [u8; block_size] = [0; block_size];
|
||||
while n != 0 {
|
||||
|
9
src/bin/aes-ni.rs
Normal file
9
src/bin/aes-ni.rs
Normal file
@ -0,0 +1,9 @@
|
||||
cpufeatures::new!(aes_ni, "aes");
|
||||
|
||||
fn main() {
|
||||
println!("AES-NI is {}supported on this device.", if aes_ni::get() {
|
||||
""
|
||||
} else {
|
||||
"NOT "
|
||||
});
|
||||
}
|
@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
use doby::{
|
||||
encrypt, decrypt,
|
||||
crypto::{ArgonParams, EncryptionParams, Cipher}
|
||||
crypto::{ArgonParams, EncryptionParams, CipherAlgorithm, DobyCipher}
|
||||
};
|
||||
|
||||
const MAX_BLOCK_SIZE: usize = 1_073_741_824; //1GB
|
||||
@ -37,7 +37,7 @@ fn main() -> io::Result<()> {
|
||||
t_cost: 1,
|
||||
m_cost: 8,
|
||||
parallelism: 1,
|
||||
});
|
||||
}, CipherAlgorithm::AesCtr);
|
||||
|
||||
let mut best_encrypt_time = None;
|
||||
let mut best_encrypt_block_size = None;
|
||||
@ -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 = Cipher::new(PASSWORD, ¶ms).unwrap();
|
||||
let cipher = DobyCipher::new(PASSWORD, ¶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 = Cipher::new(PASSWORD, ¶ms).unwrap();
|
||||
let cipher = DobyCipher::new(PASSWORD, ¶ms).unwrap();
|
||||
let t_decrypt = Instant::now();
|
||||
decrypt(&mut reader, &mut writer, cipher, block_size)?;
|
||||
writer.flush()?;
|
||||
|
39
src/cli.rs
39
src/cli.rs
@ -4,13 +4,16 @@ use std::{
|
||||
str::FromStr,
|
||||
io::{stdin, stdout, Read, Write},
|
||||
};
|
||||
use clap::{crate_name, crate_version, App, Arg};
|
||||
use crate::crypto::ArgonParams;
|
||||
use clap::{crate_name, crate_version, App, Arg, AppSettings};
|
||||
use crate::crypto::{ArgonParams, CipherAlgorithm};
|
||||
|
||||
cpufeatures::new!(aes_ni, "aes");
|
||||
|
||||
pub struct CliArgs {
|
||||
pub password: String,
|
||||
pub force_encrypt: bool,
|
||||
pub argon2_params: ArgonParams,
|
||||
pub cipher: CipherAlgorithm,
|
||||
pub block_size: usize,
|
||||
pub reader: Box<dyn Read>,
|
||||
pub writer: Box<dyn Write>,
|
||||
@ -19,6 +22,7 @@ pub struct CliArgs {
|
||||
pub fn parse() -> Option<CliArgs> {
|
||||
let app = App::new(crate_name!())
|
||||
.version(crate_version!())
|
||||
.setting(AppSettings::ColoredHelp)
|
||||
.arg(Arg::with_name("INPUT").help("<PATH> | \"-\" or empty for stdin"))
|
||||
.arg(Arg::with_name("OUTPUT").help("<PATH> | \"-\" or empty for stdout"))
|
||||
.arg(
|
||||
@ -32,6 +36,7 @@ pub fn parse() -> Option<CliArgs> {
|
||||
.short("p")
|
||||
.long("password")
|
||||
.value_name("password")
|
||||
.help("Password used to derive encryption keys")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("t_cost")
|
||||
@ -64,6 +69,16 @@ pub fn parse() -> Option<CliArgs> {
|
||||
.help("Size of file chunk (in bytes)")
|
||||
.default_value("65536")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("cipher")
|
||||
.short("c")
|
||||
.long("cipher")
|
||||
.value_name("cipher")
|
||||
.help("Encryption cipher to use")
|
||||
.long_help("Encryption cipher to use. By default, AES is selected if AES-NI is supported. Otherwise, XChaCha20 is used.")
|
||||
.possible_values(&["aes", "xchacha20"])
|
||||
.case_insensitive(true)
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let params = {
|
||||
@ -78,6 +93,21 @@ pub fn parse() -> Option<CliArgs> {
|
||||
}
|
||||
};
|
||||
|
||||
let cipher = app
|
||||
.value_of("cipher")
|
||||
.and_then(|s| Some(if s.to_lowercase() == "aes" {
|
||||
CipherAlgorithm::AesCtr
|
||||
} else {
|
||||
CipherAlgorithm::XChaCha20
|
||||
})
|
||||
)
|
||||
.unwrap_or_else(|| if aes_ni::get() {
|
||||
CipherAlgorithm::AesCtr
|
||||
} else {
|
||||
CipherAlgorithm::XChaCha20
|
||||
}
|
||||
);
|
||||
|
||||
let block_size = number(app.value_of("blocksize").unwrap())?;
|
||||
|
||||
let input = app
|
||||
@ -97,7 +127,7 @@ pub fn parse() -> Option<CliArgs> {
|
||||
Some(Box::new(File::create(s).unwrap()) as Box<dyn Write>)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| Some(Box::new(stdout())));
|
||||
.unwrap_or_else(|| Some(Box::new(stdout())))?;
|
||||
|
||||
let password = match app.value_of("password") {
|
||||
Some(s) => s.to_string(),
|
||||
@ -108,9 +138,10 @@ pub fn parse() -> Option<CliArgs> {
|
||||
password,
|
||||
force_encrypt: app.is_present("force-encrypt"),
|
||||
argon2_params: params,
|
||||
cipher,
|
||||
block_size,
|
||||
reader: input,
|
||||
writer: output?,
|
||||
writer: output,
|
||||
})
|
||||
}
|
||||
|
||||
|
107
src/crypto.rs
107
src/crypto.rs
@ -1,4 +1,6 @@
|
||||
use std::io::{self, Read, Write};
|
||||
use std::{convert::TryFrom, io::{self, Read, Write}};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use chacha20::XChaCha20;
|
||||
use aes::{Aes256Ctr, cipher::{NewCipher, StreamCipher}};
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand::{Rng, rngs::OsRng};
|
||||
@ -7,7 +9,8 @@ use hkdf::Hkdf;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
const SALT_LEN: usize = 64;
|
||||
const NONCE_LEN: usize = 16;
|
||||
const AES_NONCE_LEN: usize = 16;
|
||||
const XCHACHA20_NONCE_LEN: usize = 24;
|
||||
const HASH_LEN: usize = 32;
|
||||
const KEY_LEN: usize = HASH_LEN;
|
||||
|
||||
@ -17,28 +20,48 @@ pub struct ArgonParams {
|
||||
pub parallelism: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum CipherAlgorithm {
|
||||
AesCtr = 0,
|
||||
XChaCha20 = 1,
|
||||
}
|
||||
|
||||
impl CipherAlgorithm {
|
||||
fn get_nonce_size(&self) -> usize {
|
||||
match self {
|
||||
CipherAlgorithm::AesCtr => AES_NONCE_LEN,
|
||||
CipherAlgorithm::XChaCha20 => XCHACHA20_NONCE_LEN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EncryptionParams {
|
||||
password_salt: [u8; SALT_LEN],
|
||||
argon2: ArgonParams,
|
||||
hkdf_salt: [u8; SALT_LEN],
|
||||
nonce: [u8; NONCE_LEN],
|
||||
nonce: Vec<u8>,
|
||||
cipher: CipherAlgorithm,
|
||||
}
|
||||
|
||||
impl EncryptionParams {
|
||||
const PARAMS_LEN: usize = SALT_LEN*2 + 4*2 + 1 + NONCE_LEN;
|
||||
fn get_params_len(&self) -> usize {
|
||||
SALT_LEN*2 + 4*2 + 1 + self.cipher.get_nonce_size()
|
||||
}
|
||||
|
||||
pub fn new(argon2_params: ArgonParams) -> EncryptionParams {
|
||||
pub fn new(argon2_params: ArgonParams, cipher: CipherAlgorithm) -> EncryptionParams {
|
||||
let mut password_salt = [0; SALT_LEN];
|
||||
OsRng.fill(&mut password_salt);
|
||||
let mut hkdf_salt = [0; SALT_LEN];
|
||||
OsRng.fill(&mut hkdf_salt);
|
||||
let mut nonce = [0; NONCE_LEN];
|
||||
OsRng.fill(&mut nonce);
|
||||
let mut nonce = vec![0; cipher.get_nonce_size()];
|
||||
OsRng.fill(&mut nonce[..]);
|
||||
EncryptionParams {
|
||||
password_salt,
|
||||
argon2: argon2_params,
|
||||
hkdf_salt,
|
||||
nonce,
|
||||
cipher,
|
||||
}
|
||||
}
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
@ -47,45 +70,54 @@ 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.hkdf_salt)?;
|
||||
writer.write_all(&(self.cipher as u8).to_be_bytes())?;
|
||||
writer.write_all(&self.nonce)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
|
||||
pub fn read<R: Read>(reader: &mut R) -> io::Result<Option<Self>> {
|
||||
let mut password_salt = [0; SALT_LEN];
|
||||
reader.read_exact(&mut password_salt)?;
|
||||
let mut t_cost_buf = [0; 4];
|
||||
reader.read_exact(&mut t_cost_buf)?;
|
||||
let mut m_cost_buf = [0; 4];
|
||||
reader.read_exact(&mut m_cost_buf)?;
|
||||
let mut parallelism_buf = [0; 1];
|
||||
reader.read_exact(&mut parallelism_buf)?;
|
||||
let mut t_cost = [0; 4];
|
||||
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 hkdf_salt = [0; SALT_LEN];
|
||||
reader.read_exact(&mut hkdf_salt)?;
|
||||
let mut nonce = [0; NONCE_LEN];
|
||||
reader.read_exact(&mut nonce)?;
|
||||
|
||||
let argon2_params = ArgonParams {
|
||||
t_cost: u32::from_be_bytes(t_cost_buf),
|
||||
m_cost: u32::from_be_bytes(m_cost_buf),
|
||||
parallelism: u8::from_be_bytes(parallelism_buf),
|
||||
};
|
||||
|
||||
Ok(EncryptionParams {
|
||||
password_salt,
|
||||
argon2: argon2_params,
|
||||
hkdf_salt,
|
||||
nonce,
|
||||
})
|
||||
let mut cipher_buff = [0; 1];
|
||||
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),
|
||||
parallelism: u8::from_be_bytes(parallelism),
|
||||
};
|
||||
|
||||
Ok(Some(EncryptionParams {
|
||||
password_salt,
|
||||
argon2: argon2_params,
|
||||
hkdf_salt,
|
||||
nonce,
|
||||
cipher,
|
||||
}))
|
||||
}
|
||||
Err(_) => Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Cipher {
|
||||
cipher: Aes256Ctr,
|
||||
pub struct DobyCipher {
|
||||
cipher: Box<dyn StreamCipher>,
|
||||
hmac: Hmac<blake3::Hasher>,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Cipher {
|
||||
impl DobyCipher {
|
||||
pub fn new(password: &[u8], 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)?;
|
||||
@ -99,13 +131,16 @@ impl Cipher {
|
||||
hkdf.expand(b"doby_authentication_key", &mut authentication_key).unwrap();
|
||||
master_key.zeroize();
|
||||
|
||||
let mut encoded_params = Vec::with_capacity(EncryptionParams::PARAMS_LEN);
|
||||
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);
|
||||
|
||||
Ok(Cipher {
|
||||
cipher: Aes256Ctr::new_from_slices(&encryption_key, ¶ms.nonce).unwrap(),
|
||||
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(),
|
||||
})
|
||||
@ -127,8 +162,8 @@ impl Cipher {
|
||||
buff[..buffer_len].clone_from_slice(&self.buffer);
|
||||
let read = reader.read(&mut buff[buffer_len..])?;
|
||||
|
||||
self.buffer.clear();
|
||||
let n = if buffer_len + read >= HASH_LEN {
|
||||
self.buffer.clear();
|
||||
buffer_len + read - HASH_LEN
|
||||
} else {
|
||||
0
|
||||
|
@ -2,11 +2,11 @@ pub mod cli;
|
||||
pub mod crypto;
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
use crypto::{Cipher, EncryptionParams};
|
||||
use crypto::{DobyCipher, EncryptionParams};
|
||||
|
||||
pub const MAGIC_BYTES: &[u8; 4] = b"DOBY";
|
||||
|
||||
pub fn encrypt<R: Read, W: Write>(reader: &mut R, writer: &mut W, params: &EncryptionParams, mut cipher: Cipher, 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)?;
|
||||
params.write(writer)?;
|
||||
let mut buff = vec![0; block_size];
|
||||
@ -30,7 +30,7 @@ pub fn encrypt<R: Read, W: Write>(reader: &mut R, writer: &mut W, params: &Encry
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt<R: Read, W: Write>(reader: &mut R, writer: &mut W, mut cipher: Cipher, block_size: usize) -> io::Result<bool> {
|
||||
pub fn decrypt<R: Read, W: Write>(reader: &mut R, writer: &mut W, mut cipher: DobyCipher, block_size: usize) -> io::Result<bool> {
|
||||
let mut buff = vec![0; block_size];
|
||||
loop {
|
||||
let n = cipher.decrypt_chunk(reader, &mut buff)?;
|
||||
|
27
src/main.rs
27
src/main.rs
@ -2,7 +2,7 @@ use std::io::{BufWriter, BufReader, Read};
|
||||
use zeroize::Zeroize;
|
||||
use doby::{
|
||||
cli,
|
||||
crypto::{EncryptionParams, Cipher},
|
||||
crypto::{EncryptionParams, DobyCipher},
|
||||
MAGIC_BYTES,
|
||||
decrypt,
|
||||
encrypt,
|
||||
@ -22,25 +22,30 @@ fn main() {
|
||||
if magic_bytes == MAGIC_BYTES && !cli_args.force_encrypt { //we probably want to decrypt
|
||||
match EncryptionParams::read(&mut reader) {
|
||||
Ok(params) => {
|
||||
match Cipher::new(cli_args.password.as_bytes(), ¶ms) {
|
||||
Ok(cipher) => {
|
||||
match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) {
|
||||
Ok(verified) => {
|
||||
if !verified {
|
||||
eprintln!("WARNING: HMAC verification failed !\nEither your password is incorrect or the file has been corrupted.\nOpen with caution, it could have been infected by an attacker.");
|
||||
match params {
|
||||
Some(params) => {
|
||||
match DobyCipher::new(cli_args.password.as_bytes(), ¶ms) {
|
||||
Ok(cipher) => {
|
||||
match decrypt(&mut reader, &mut writer, cipher, cli_args.block_size) {
|
||||
Ok(verified) => {
|
||||
if !verified {
|
||||
eprintln!("WARNING: HMAC verification failed !\nEither your password is incorrect or the file has been corrupted.\nOpen with caution, it could have been infected by an attacker.");
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while decrypting: {}", e)
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("I/O error while decrypting: {}", e)
|
||||
Err(e) => eprintln!("Invalid argon2 params: {}", e)
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("Invalid argon2 params: {}", e)
|
||||
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);
|
||||
match Cipher::new(cli_args.password.as_bytes(), ¶ms) {
|
||||
let params = EncryptionParams::new(cli_args.argon2_params, cli_args.cipher);
|
||||
match DobyCipher::new(cli_args.password.as_bytes(), ¶ms) {
|
||||
Ok(cipher) => {
|
||||
if let Err(e) = encrypt(
|
||||
&mut reader,
|
||||
|
Loading…
Reference in New Issue
Block a user