use aes_gcm::{ aead::{Aead, KeyInit, OsRng}, Aes256Gcm, Nonce, }; use argon2::{ password_hash::{Salt, SaltString}, Argon2, PasswordHash, PasswordHasher, }; use derive_more::Display; use rand::Rng; use sha2::{Digest, Sha256}; use thiserror::Error; #[derive(Debug, Display, Error)] pub enum CrypterError { Encrypt(String), Decrypt(String), Decode(String), Hash(String), } pub struct Crypter { pub key: Vec, } impl Crypter { pub fn new(key: &[u8]) -> Self { Self { key: key.to_vec() } } pub fn encrypt(&self, data: &[u8]) -> Result, CrypterError> { let mut hasher = Sha256::new(); hasher.update(&self.key); let cipher = Aes256Gcm::new(&hasher.finalize()); let rbs = rand::thread_rng().gen::<[u8; 12]>(); let iv = Nonce::from_slice(&rbs); let crypt = cipher .encrypt(iv, data) .map_err(|e| CrypterError::Encrypt(e.to_string()))?; let mut msg = iv.to_vec(); msg.extend_from_slice(&crypt); Ok(msg) } pub fn decrypt(&self, data: &[u8]) -> Result, CrypterError> { let mut hasher = Sha256::new(); hasher.update(&self.key); let cipher = Aes256Gcm::new(&hasher.finalize()); let iv = Nonce::from_slice(&data[0..=11]); let data = &data[12..]; cipher .decrypt(iv, data) .map_err(|e| CrypterError::Decrypt(e.to_string())) } /// Hash data. /// If a candidate is provided, then use that candidate's salt, passed as a full phc string. pub fn hash(&self, data: &[u8], phc_candidate: Option<&str>) -> Result { let salt = match phc_candidate { None => SaltString::generate(&mut OsRng).as_str().to_owned(), Some(phc) => PasswordHash::new(phc) .map_err(|e| CrypterError::Hash(e.to_string()))? .salt.expect("unreachable fbacabb8-082a-423a-abff-06a961dc5828 [no salt found means a forced error since all resources, even those of high entropy, are salted]").as_str().to_owned(), }; let hasher = Argon2::default(); Ok(hasher .hash_password( data, Salt::from_b64(&salt).map_err(|e| CrypterError::Hash(e.to_string()))?, ) .map_err(|e| CrypterError::Hash(e.to_string()))? .to_string()) } pub fn weak_hash(&self, data: &[u8]) -> Result { let mut hasher = Sha256::new(); hasher.update(data); Ok(hex::encode(hasher.finalize())) } } #[cfg(test)] mod test { use super::*; #[test] fn encrypt_data_test() { let crypter = Crypter { key: "testkey".to_string().into_bytes(), }; let plaintext = "This is a secret."; let enc = crypter.encrypt(&plaintext.as_bytes()).unwrap(); let res = crypter.decrypt(&enc).unwrap(); assert_eq!(plaintext, std::str::from_utf8(&res).unwrap()); } }