#![warn(clippy::pedantic)]
use aes_gcm::{Aes256Gcm, Key, KeyInit, Nonce, aead::Aead};
use argon2::{Algorithm::Argon2id, Argon2, ParamsBuilder, Version::V0x13};
use rand::{RngExt, rng};
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
use crate::Error::ConversionError;
#[cfg(test)]
mod tests;
const MEM_COST: u32 = 20000;
const PARALLELISM: u32 = 4;
const ITERATION_COST: u32 = 4;
pub struct EncryptedData {
pub bytes: Vec<u8>,
pub nonce: GcmNonce,
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Gcm256Key([u8; 32]);
impl Gcm256Key {
#[must_use]
fn from_slice(slice: &[u8; 32]) -> Self {
Self(*slice)
}
#[must_use]
fn as_slice(&self) -> &[u8; 32] {
&self.0
}
pub fn expose_with_kek(&self, kek: &[u8; 32]) -> Result<(Vec<u8>, GcmNonce), Error> {
let mut randgen = rng();
let nonce = GcmNonce::from_slice(&randgen.random());
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*kek));
Ok((
cipher.encrypt(&Nonce::from(nonce.0), self.0.as_slice())?,
nonce,
))
}
pub fn recover_with_kek(
source: &[u8; 48],
kek: &[u8; 32],
nonce: &GcmNonce,
) -> Result<Gcm256Key, Error> {
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*kek));
let plaintext = Zeroizing::new(cipher.decrypt(&Nonce::from(nonce.0), source.as_slice())?);
Ok(Gcm256Key::from_slice(
plaintext
.as_slice()
.try_into()
.map_err(|_| ConversionError)?,
))
}
}
pub struct GcmNonce([u8; 12]);
impl GcmNonce {
#[must_use]
pub fn from_slice(slice: &[u8; 12]) -> Self {
Self(*slice)
}
#[must_use]
pub fn as_slice(&self) -> &[u8; 12] {
&self.0
}
}
pub struct Salt([u8; 16]);
impl Salt {
#[must_use]
pub fn from_slice(slice: &[u8; 16]) -> Self {
Self(*slice)
}
#[must_use]
pub fn as_slice(&self) -> &[u8; 16] {
&self.0
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
Aes,
ArgonParams,
Argon2Hashing,
ConversionError,
}
impl From<aes_gcm::Error> for Error {
fn from(_: aes_gcm::Error) -> Self {
Self::Aes
}
}
pub fn encrypt_with_random_key(bytes: &[u8]) -> Result<(EncryptedData, Gcm256Key), Error> {
let mut randgen = rng();
let rawkey = Gcm256Key::from_slice(&randgen.random());
let key = Key::<Aes256Gcm>::from(*rawkey.as_slice());
let cipher = Aes256Gcm::new(&key);
let rawnonce = GcmNonce::from_slice(&randgen.random());
let nonce = Nonce::from(*rawnonce.as_slice());
Ok((
EncryptedData {
bytes: cipher.encrypt(&nonce, bytes)?,
nonce: rawnonce,
},
rawkey,
))
}
pub fn decrypt_with_key(bytes: &[u8], key: &Gcm256Key, nonce: &GcmNonce) -> Result<Vec<u8>, Error> {
let key = Key::<Aes256Gcm>::from(*key.as_slice());
let cipher = Aes256Gcm::new(&key);
let nonce = Nonce::from(*nonce.as_slice());
Ok(cipher.decrypt(&nonce, bytes)?)
}
pub fn encrypt_with_password(bytes: &[u8], password: &str) -> Result<(EncryptedData, Salt), Error> {
let mut randgen = rng();
let mut params = ParamsBuilder::new();
params.m_cost(MEM_COST);
params.p_cost(PARALLELISM);
params.t_cost(ITERATION_COST);
let hasher = Argon2::new(
Argon2id,
V0x13,
params.build().map_err(|_| Error::ArgonParams)?,
);
let salt = Salt::from_slice(&randgen.random());
let mut rawkey = Gcm256Key::from_slice(&[0u8; 32]);
hasher
.hash_password_into(password.as_bytes(), salt.as_slice(), &mut rawkey.0)
.map_err(|_| Error::Argon2Hashing)?;
let key = Key::<Aes256Gcm>::from(*rawkey.as_slice());
let cipher = Aes256Gcm::new(&key);
let rawnonce = GcmNonce::from_slice(&randgen.random());
let nonce = Nonce::from(*rawnonce.as_slice());
Ok((
EncryptedData {
bytes: cipher.encrypt(&nonce, bytes)?,
nonce: rawnonce,
},
salt,
))
}
pub fn decrypt_with_password(
bytes: &[u8],
password: &str,
salt: &Salt,
nonce: &GcmNonce,
) -> Result<Vec<u8>, Error> {
let mut params = ParamsBuilder::new();
params.m_cost(MEM_COST);
params.p_cost(PARALLELISM);
params.t_cost(ITERATION_COST);
let hasher = Argon2::new(
Argon2id,
V0x13,
params.build().map_err(|_| Error::ArgonParams)?,
);
let mut rawkey = Gcm256Key::from_slice(&[0u8; 32]);
hasher
.hash_password_into(password.as_bytes(), salt.as_slice(), &mut rawkey.0)
.map_err(|_| Error::Argon2Hashing)?;
let key = Key::<Aes256Gcm>::from(*rawkey.as_slice());
let cipher = Aes256Gcm::new(&key);
let nonce = Nonce::from(*nonce.as_slice());
Ok(cipher.decrypt(&nonce, bytes)?)
}