2021-11-13 19:08:28 +01:00
|
|
|
use std::{convert::TryFrom, fmt::{self, Display, Formatter}, io::{self, Read, Write}};
|
2021-11-14 12:08:40 +01:00
|
|
|
use blake2::{Blake2b, VarBlake2b, digest::{Update, VariableOutput}};
|
2021-06-27 20:35:23 +02:00
|
|
|
use num_enum::TryFromPrimitive;
|
|
|
|
use chacha20::XChaCha20;
|
2021-06-25 23:01:50 +02:00
|
|
|
use aes::{Aes256Ctr, cipher::{NewCipher, StreamCipher}};
|
2021-11-14 12:08:40 +01:00
|
|
|
use subtle::ConstantTimeEq;
|
2021-06-25 23:01:50 +02:00
|
|
|
use rand::{Rng, rngs::OsRng};
|
|
|
|
use argon2::{Argon2, Version, Algorithm};
|
|
|
|
use hkdf::Hkdf;
|
|
|
|
use zeroize::Zeroize;
|
|
|
|
|
2021-07-08 12:21:14 +02:00
|
|
|
pub const SALT_LEN: usize = 64;
|
2021-06-27 20:35:23 +02:00
|
|
|
const AES_NONCE_LEN: usize = 16;
|
|
|
|
const XCHACHA20_NONCE_LEN: usize = 24;
|
2021-11-14 12:08:40 +01:00
|
|
|
pub const HMAC_LEN: usize = 32;
|
2021-07-08 12:21:14 +02:00
|
|
|
const KEY_LEN: usize = 32;
|
2021-06-25 23:01:50 +02:00
|
|
|
|
2021-06-30 14:36:49 +02:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
|
2021-06-27 20:35:23 +02:00
|
|
|
#[repr(u8)]
|
|
|
|
pub enum CipherAlgorithm {
|
|
|
|
AesCtr = 0,
|
|
|
|
XChaCha20 = 1,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CipherAlgorithm {
|
2021-07-05 12:43:04 +02:00
|
|
|
pub fn get_nonce_size(&self) -> usize {
|
2021-06-27 20:35:23 +02:00
|
|
|
match self {
|
|
|
|
CipherAlgorithm::AesCtr => AES_NONCE_LEN,
|
|
|
|
CipherAlgorithm::XChaCha20 => XCHACHA20_NONCE_LEN,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 15:24:15 +02:00
|
|
|
impl Display for CipherAlgorithm {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str(match self {
|
|
|
|
CipherAlgorithm::AesCtr => "AES-CTR",
|
|
|
|
CipherAlgorithm::XChaCha20 => "XChaCha20",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 14:36:49 +02:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2021-06-25 23:01:50 +02:00
|
|
|
pub struct EncryptionParams {
|
2021-07-08 12:21:14 +02:00
|
|
|
salt: [u8; SALT_LEN],
|
2021-11-13 19:08:28 +01:00
|
|
|
pub argon2: argon2::Params,
|
2021-06-30 15:24:15 +02:00
|
|
|
pub cipher: CipherAlgorithm,
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EncryptionParams {
|
2021-11-13 19:08:28 +01:00
|
|
|
pub const LEN: usize = SALT_LEN + 4*3 + 1;
|
2021-06-25 23:01:50 +02:00
|
|
|
|
2021-11-13 19:08:28 +01:00
|
|
|
pub fn new(argon2_params: argon2::Params, cipher: CipherAlgorithm) -> EncryptionParams {
|
2021-07-08 12:21:14 +02:00
|
|
|
let mut salt = [0; SALT_LEN];
|
|
|
|
OsRng.fill(&mut salt);
|
2021-06-25 23:01:50 +02:00
|
|
|
EncryptionParams {
|
2021-07-08 12:21:14 +02:00
|
|
|
salt,
|
2021-06-25 23:01:50 +02:00
|
|
|
argon2: argon2_params,
|
2021-06-27 20:35:23 +02:00
|
|
|
cipher,
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-31 13:06:45 +02:00
|
|
|
|
2021-06-25 23:01:50 +02:00
|
|
|
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
2021-07-08 12:21:14 +02:00
|
|
|
writer.write_all(&self.salt)?;
|
2021-11-13 19:08:28 +01:00
|
|
|
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())?;
|
2021-06-27 20:35:23 +02:00
|
|
|
writer.write_all(&(self.cipher as u8).to_be_bytes())?;
|
2021-06-25 23:01:50 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-08-31 13:06:45 +02:00
|
|
|
|
2021-06-27 20:35:23 +02:00
|
|
|
pub fn read<R: Read>(reader: &mut R) -> io::Result<Option<Self>> {
|
2021-07-08 12:21:14 +02:00
|
|
|
let mut salt = [0; SALT_LEN];
|
|
|
|
reader.read_exact(&mut salt)?;
|
2021-06-27 20:35:23 +02:00
|
|
|
let mut t_cost = [0; 4];
|
|
|
|
reader.read_exact(&mut t_cost)?;
|
|
|
|
let mut m_cost = [0; 4];
|
|
|
|
reader.read_exact(&mut m_cost)?;
|
2021-11-13 19:08:28 +01:00
|
|
|
let mut p_cost = [0; 4];
|
|
|
|
reader.read_exact(&mut p_cost)?;
|
2021-06-27 20:35:23 +02:00
|
|
|
let mut cipher_buff = [0; 1];
|
|
|
|
reader.read_exact(&mut cipher_buff)?;
|
2021-11-13 19:08:28 +01:00
|
|
|
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 {
|
2021-07-08 12:21:14 +02:00
|
|
|
salt,
|
2021-06-27 20:35:23 +02:00
|
|
|
argon2: argon2_params,
|
|
|
|
cipher,
|
2021-11-13 19:08:28 +01:00
|
|
|
}));
|
2021-06-27 20:35:23 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-13 19:08:28 +01:00
|
|
|
Ok(None)
|
2021-07-04 16:24:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-27 20:35:23 +02:00
|
|
|
pub struct DobyCipher {
|
|
|
|
cipher: Box<dyn StreamCipher>,
|
2021-11-14 12:08:40 +01:00
|
|
|
hasher: VarBlake2b,
|
2021-06-25 23:01:50 +02:00
|
|
|
buffer: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
2021-06-27 20:35:23 +02:00
|
|
|
impl DobyCipher {
|
2021-11-13 19:08:28 +01:00
|
|
|
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();
|
2021-11-14 12:08:40 +01:00
|
|
|
let hkdf = Hkdf::<Blake2b>::new(Some(¶ms.salt), &master_key);
|
2021-11-13 19:08:28 +01:00
|
|
|
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();
|
2021-11-14 12:08:40 +01:00
|
|
|
let mut hasher = VarBlake2b::new_keyed(&authentication_key, HMAC_LEN);
|
2021-11-13 19:08:28 +01:00
|
|
|
authentication_key.zeroize();
|
2021-11-14 12:08:40 +01:00
|
|
|
hasher.update(&encoded_params);
|
2021-11-13 19:08:28 +01:00
|
|
|
|
|
|
|
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();
|
2021-07-04 16:24:44 +02:00
|
|
|
|
2021-11-13 19:08:28 +01:00
|
|
|
Self {
|
|
|
|
cipher,
|
2021-11-14 12:08:40 +01:00
|
|
|
hasher,
|
2021-11-13 19:08:28 +01:00
|
|
|
buffer: Vec::new(),
|
2021-07-04 16:24:44 +02:00
|
|
|
}
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn encrypt_chunk<W: Write>(&mut self, buff: &mut [u8], writer: &mut W) -> io::Result<()> {
|
|
|
|
self.cipher.apply_keystream(buff);
|
2021-11-14 12:08:40 +01:00
|
|
|
self.hasher.update(&buff);
|
2021-06-25 23:01:50 +02:00
|
|
|
writer.write_all(buff)
|
|
|
|
}
|
|
|
|
|
2021-11-14 12:08:40 +01:00
|
|
|
pub fn write_hmac<W: Write>(self, writer: &mut W) -> io::Result<()> {
|
|
|
|
writer.write_all(&self.hasher.finalize_boxed())
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|
|
|
|
|
2021-06-30 14:36:49 +02:00
|
|
|
//buff size must be > to HASH_LEN
|
2021-06-25 23:01:50 +02:00
|
|
|
pub fn decrypt_chunk<R: Read>(&mut self, reader: &mut R, buff: &mut [u8]) -> io::Result<usize> {
|
|
|
|
let buffer_len = self.buffer.len();
|
|
|
|
buff[..buffer_len].clone_from_slice(&self.buffer);
|
|
|
|
let read = reader.read(&mut buff[buffer_len..])?;
|
|
|
|
|
2021-11-14 12:08:40 +01:00
|
|
|
let n = if buffer_len + read >= HMAC_LEN {
|
2021-06-27 20:35:23 +02:00
|
|
|
self.buffer.clear();
|
2021-11-14 12:08:40 +01:00
|
|
|
buffer_len + read - HMAC_LEN
|
2021-06-25 23:01:50 +02:00
|
|
|
} else {
|
|
|
|
0
|
|
|
|
};
|
|
|
|
self.buffer.extend_from_slice(&buff[n..buffer_len+read]);
|
|
|
|
|
2021-11-14 12:08:40 +01:00
|
|
|
self.hasher.update(&buff[..n]);
|
2021-06-25 23:01:50 +02:00
|
|
|
self.cipher.apply_keystream(&mut buff[..n]);
|
|
|
|
Ok(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify_hmac(self) -> bool {
|
2021-11-14 12:08:40 +01:00
|
|
|
self.hasher.finalize_boxed().ct_eq(&self.buffer).into()
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|
2021-06-30 14:36:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-11-14 12:08:40 +01:00
|
|
|
use super::{CipherAlgorithm, EncryptionParams, DobyCipher, HMAC_LEN};
|
2021-06-30 14:36:49 +02:00
|
|
|
#[test]
|
|
|
|
fn encryption_params() {
|
2021-11-13 19:08:28 +01:00
|
|
|
let params = EncryptionParams::new(
|
|
|
|
argon2::Params::new(8, 1, 1, None).unwrap(),
|
|
|
|
CipherAlgorithm::XChaCha20
|
|
|
|
);
|
2021-06-30 14:36:49 +02:00
|
|
|
|
2021-11-13 19:08:28 +01:00
|
|
|
assert_eq!(EncryptionParams::LEN, 77);
|
2021-06-30 14:36:49 +02:00
|
|
|
|
2021-08-30 16:06:55 +02:00
|
|
|
let mut buff = Vec::with_capacity(74);
|
2021-06-30 14:36:49 +02:00
|
|
|
params.write(&mut buff).unwrap();
|
2021-07-08 12:21:14 +02:00
|
|
|
assert_eq!(buff[..64], params.salt);
|
2021-06-30 14:36:49 +02:00
|
|
|
assert_eq!(buff[64..68], vec![0, 0, 0, 0x01]); //t_cost
|
|
|
|
assert_eq!(buff[68..72], vec![0, 0, 0, 0x08]); //m_cost
|
2021-11-13 19:08:28 +01:00
|
|
|
assert_eq!(buff[72..76], vec![0, 0, 0, 0x01]); //p_cost
|
|
|
|
assert_eq!(buff[76], CipherAlgorithm::XChaCha20 as u8);
|
2021-06-30 14:36:49 +02:00
|
|
|
|
|
|
|
let new_params = EncryptionParams::read(&mut buff.as_slice()).unwrap().unwrap();
|
|
|
|
assert_eq!(new_params, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn doby_cipher() {
|
2021-11-13 19:08:28 +01:00
|
|
|
let params = EncryptionParams::new(
|
|
|
|
argon2::Params::new(8, 1, 1, None).unwrap(),
|
|
|
|
CipherAlgorithm::AesCtr
|
|
|
|
);
|
2021-07-04 16:24:44 +02:00
|
|
|
let password = "I like spaghetti";
|
2021-06-30 14:36:49 +02:00
|
|
|
let plaintext = b"but I love so much to listen to HARDCORE music on big subwoofer";
|
|
|
|
let mut buff: [u8; 63] = *plaintext;
|
2021-11-14 12:08:40 +01:00
|
|
|
let mut vec = Vec::with_capacity(buff.len()+HMAC_LEN);
|
2021-06-30 14:36:49 +02:00
|
|
|
|
2021-11-13 19:08:28 +01:00
|
|
|
let mut enc_cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
2021-06-30 14:36:49 +02:00
|
|
|
enc_cipher.encrypt_chunk(&mut buff, &mut vec).unwrap();
|
|
|
|
assert_ne!(buff, *plaintext);
|
|
|
|
assert_eq!(buff, vec.as_slice());
|
2021-11-14 12:08:40 +01:00
|
|
|
assert!(enc_cipher.write_hmac(&mut vec).is_ok());
|
|
|
|
assert_eq!(vec.len(), buff.len()+HMAC_LEN);
|
2021-06-30 14:36:49 +02:00
|
|
|
|
2021-11-13 19:08:28 +01:00
|
|
|
let mut dec_cipher = DobyCipher::new(password.as_bytes(), ¶ms);
|
2021-11-14 12:08:40 +01:00
|
|
|
let mut decrypted = vec![0; buff.len()+HMAC_LEN];
|
2021-06-30 14:36:49 +02:00
|
|
|
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();
|
|
|
|
assert_eq!(n, 0);
|
|
|
|
assert_eq!(decrypted[..buff.len()], *plaintext);
|
|
|
|
assert_eq!(dec_cipher.verify_hmac(), true);
|
|
|
|
}
|
2021-06-25 23:01:50 +02:00
|
|
|
}
|