117 lines
3.3 KiB
Rust
117 lines
3.3 KiB
Rust
use std::fmt::{Debug, Formatter};
|
|
use argon2::{
|
|
Argon2,
|
|
Algorithm,
|
|
Version,
|
|
ParamsBuilder,
|
|
password_hash::rand_core::{RngCore, OsRng},
|
|
};
|
|
use chacha20poly1305::{
|
|
XChaCha20Poly1305,
|
|
XNonce,
|
|
aead::{
|
|
Aead,
|
|
AeadCore,
|
|
KeyInit,
|
|
generic_array::GenericArray,
|
|
},
|
|
};
|
|
|
|
use crate::errors::*;
|
|
|
|
|
|
#[derive(Clone)]
|
|
pub struct Crypto {
|
|
cipher: XChaCha20Poly1305,
|
|
}
|
|
|
|
impl Crypto {
|
|
/// Argon2 params rationale:
|
|
///
|
|
/// m_cost is measured in KiB, so 128 * 1024 gives us 128MiB.
|
|
/// This should roughly double the memory usage of the application
|
|
/// while deriving the key.
|
|
///
|
|
/// p_cost is irrelevant since (at present) there isn't any parallelism
|
|
/// implemented, so we leave it at 1.
|
|
///
|
|
/// With the above m_cost, t_cost = 8 results in about 800ms to derive
|
|
/// a key on my (somewhat older) CPU. This is probably overkill, but
|
|
/// given that it should only have to happen ~once a day for most
|
|
/// usage, it should be acceptable.
|
|
#[cfg(not(debug_assertions))]
|
|
const MEM_COST: u32 = 128 * 1024;
|
|
#[cfg(not(debug_assertions))]
|
|
const TIME_COST: u32 = 8;
|
|
|
|
/// But since this takes a million years without optimizations,
|
|
/// we turn it way down in debug builds.
|
|
#[cfg(debug_assertions)]
|
|
const MEM_COST: u32 = 48 * 1024;
|
|
#[cfg(debug_assertions)]
|
|
const TIME_COST: u32 = 1;
|
|
|
|
|
|
pub fn new(passphrase: &str, salt: &[u8]) -> argon2::Result<Crypto> {
|
|
let params = ParamsBuilder::new()
|
|
.m_cost(Self::MEM_COST)
|
|
.p_cost(1)
|
|
.t_cost(Self::TIME_COST)
|
|
.build()
|
|
.unwrap(); // only errors if the given params are invalid
|
|
|
|
let hasher = Argon2::new(
|
|
Algorithm::Argon2id,
|
|
Version::V0x13,
|
|
params,
|
|
);
|
|
|
|
let mut key = [0; 32];
|
|
hasher.hash_password_into(passphrase.as_bytes(), &salt, &mut key)?;
|
|
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
|
Ok(Crypto { cipher })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn random() -> Crypto {
|
|
// salt and key are the same length, so we can just use this
|
|
let key = Crypto::salt();
|
|
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
|
Crypto { cipher }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn fixed() -> Crypto {
|
|
let key = [
|
|
1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
|
|
];
|
|
|
|
let cipher = XChaCha20Poly1305::new(GenericArray::from_slice(&key));
|
|
Crypto { cipher }
|
|
}
|
|
|
|
pub fn salt() -> [u8; 32] {
|
|
let mut salt = [0; 32];
|
|
OsRng.fill_bytes(&mut salt);
|
|
salt
|
|
}
|
|
|
|
pub fn encrypt(&self, data: &[u8]) -> Result<(XNonce, Vec<u8>), CryptoError> {
|
|
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
|
|
let ciphertext = self.cipher.encrypt(&nonce, data)?;
|
|
Ok((nonce, ciphertext))
|
|
}
|
|
|
|
pub fn decrypt(&self, nonce: &XNonce, data: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
|
let plaintext = self.cipher.decrypt(nonce, data)?;
|
|
Ok(plaintext)
|
|
}
|
|
}
|
|
|
|
impl Debug for Crypto {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
write!(f, "Crypto {{ [...] }}")
|
|
}
|
|
}
|