pub mod envs;
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use anyhow::{bail, format_err, Result};
use argon2::password_hash::SaltString;
use argon2::{Argon2, Params};
use rand::rngs::OsRng;
use rand::Rng;
use ring::aead::Nonce;
pub use ring::aead::{Aad, LessSafeKey, UnboundKey, NONCE_LEN};
use crate::envs::FM_TEST_FAST_WEAK_CRYPTO_ENV;
pub fn get_random_nonce() -> ring::aead::Nonce {
Nonce::assume_unique_for_key(OsRng.gen())
}
pub fn encrypt(mut plaintext: Vec<u8>, key: &LessSafeKey) -> Result<Vec<u8>> {
let nonce = get_random_nonce();
let mut ciphertext: Vec<u8> = nonce.as_ref().to_vec();
key.seal_in_place_append_tag(nonce, Aad::empty(), &mut plaintext)
.map_err(|_| anyhow::format_err!("Encryption failed due to unspecified aead error"))?;
ciphertext.append(&mut plaintext);
Ok(ciphertext)
}
pub fn decrypt<'c>(ciphertext: &'c mut [u8], key: &LessSafeKey) -> Result<&'c [u8]> {
if ciphertext.len() < NONCE_LEN {
bail!("Ciphertext too short: {}", ciphertext.len());
}
let (nonce_bytes, encrypted_bytes) = ciphertext.split_at_mut(NONCE_LEN);
key.open_in_place(
Nonce::assume_unique_for_key(nonce_bytes.try_into().expect("nonce size known")),
Aad::empty(),
encrypted_bytes,
)
.map_err(|_| format_err!("Decryption failed due to unspecified aead error"))?;
Ok(&encrypted_bytes[..encrypted_bytes.len() - key.algorithm().tag_len()])
}
pub fn encrypted_write(data: Vec<u8>, key: &LessSafeKey, file: PathBuf) -> Result<()> {
Ok(fs::File::options()
.write(true)
.create_new(true)
.open(file)?
.write_all(hex::encode(encrypt(data, key)?).as_bytes())?)
}
pub fn encrypted_read(key: &LessSafeKey, file: PathBuf) -> Result<Vec<u8>> {
let hex = fs::read_to_string(file)?;
let mut bytes = hex::decode(hex)?;
Ok(decrypt(&mut bytes, key)?.to_vec())
}
pub fn get_encryption_key(password: &str, salt: &str) -> Result<LessSafeKey> {
let mut key = [0u8; ring::digest::SHA256_OUTPUT_LEN];
argon2()
.hash_password_into(password.as_bytes(), salt.as_bytes(), &mut key)
.map_err(|e| format_err!("could not hash password").context(e))?;
let key = UnboundKey::new(&ring::aead::CHACHA20_POLY1305, &key)
.map_err(|_| anyhow::Error::msg("Unable to create key"))?;
Ok(LessSafeKey::new(key))
}
pub fn random_salt() -> String {
SaltString::generate(OsRng).to_string()
}
fn argon2() -> Argon2<'static> {
let mut params = argon2::ParamsBuilder::default();
if let Ok("1") = std::env::var(FM_TEST_FAST_WEAK_CRYPTO_ENV).as_deref() {
params.m_cost(Params::MIN_M_COST);
}
Argon2::from(params.build().expect("valid params"))
}
#[cfg(test)]
mod tests {
use crate::{decrypt, encrypt, get_encryption_key};
#[test]
fn encrypts_and_decrypts() {
let password = "test123";
let salt = "salt1235";
let message = "hello world";
let key = get_encryption_key(password, salt).unwrap();
let mut cipher_text = encrypt(message.as_bytes().to_vec(), &key).unwrap();
let decrypted = decrypt(&mut cipher_text, &key).unwrap();
assert_eq!(decrypted, message.as_bytes());
}
}