#![warn(clippy::pedantic)]
use aes_gcm::{
Aes256Gcm, Key, KeyInit, Nonce,
aead::{Aead, Payload},
};
use argon2::{Algorithm::Argon2id, Argon2, ParamsBuilder, Version::V0x13};
use rand::{RngExt, rng};
use std::io::{Read, Write};
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;
const TAG_LEN: usize = 16;
pub const DEFAULT_CHUNK_SIZE: u32 = 32 * 1024 * 1024;
const MAX_CHUNK_SIZE: u32 = 256 * 1024 * 1024;
pub struct EncryptedData {
pub bytes: Vec<u8>,
pub nonce: GcmNonce,
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct Gcm256Key([u8; 32]);
impl Gcm256Key {
#[must_use]
pub fn random() -> Self {
Self(rng().random())
}
#[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 random() -> Self {
Self(rng().random())
}
#[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 random() -> Self {
Self(rng().random())
}
#[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,
Io(std::io::Error),
InvalidChunkSize,
}
impl From<aes_gcm::Error> for Error {
fn from(_: aes_gcm::Error) -> Self {
Self::Aes
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
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 derive_key_from_password(password: &str, salt: &Salt) -> Result<Gcm256Key, 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)?;
Ok(rawkey)
}
pub fn encrypt_with_password(bytes: &[u8], password: &str) -> Result<(EncryptedData, Salt), Error> {
let salt = Salt::random();
let rawkey = derive_key_from_password(password, &salt)?;
let key = Key::<Aes256Gcm>::from(*rawkey.as_slice());
let cipher = Aes256Gcm::new(&key);
let rawnonce = GcmNonce::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 rawkey = derive_key_from_password(password, salt)?;
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)?)
}
fn counter_to_bytes(counter: u64) -> [u8; 12] {
let mut bytes = [0u8; 12];
bytes[4..].copy_from_slice(&counter.to_be_bytes());
bytes
}
fn chunk_nonce(root: &GcmNonce, counter: u64) -> GcmNonce {
let offset = counter_to_bytes(counter);
let mut bytes = *root.as_slice();
bytes
.iter_mut()
.zip(offset.iter())
.for_each(|(b, o)| *b ^= *o);
GcmNonce::from_slice(&bytes)
}
fn chunk_aad(counter: u64, is_final: bool) -> [u8; 13] {
let mut aad = [0u8; 13];
aad[..12].copy_from_slice(&counter_to_bytes(counter));
aad[12] = u8::from(is_final);
aad
}
fn fill_chunk<R: Read>(reader: &mut R, mut buf: Vec<u8>) -> Result<Vec<u8>, Error> {
let capacity = buf.len();
let mut filled = 0;
while filled < capacity {
let n = reader.read(&mut buf[filled..])?;
if n == 0 {
break;
}
filled += n;
}
buf.truncate(filled);
Ok(buf)
}
pub fn encrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Gcm256Key,
root_nonce: &GcmNonce,
chunk_size: u32,
) -> Result<(), Error> {
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*key.as_slice()));
let size = chunk_size as usize;
let mut counter: u64 = 0;
let mut current = fill_chunk(reader, vec![0u8; size])?;
loop {
let next = fill_chunk(reader, vec![0u8; size])?;
let is_final = next.is_empty();
let nonce = chunk_nonce(root_nonce, counter);
let aad = chunk_aad(counter, is_final);
let sealed = cipher.encrypt(
&Nonce::from(*nonce.as_slice()),
Payload {
msg: ¤t,
aad: &aad,
},
)?;
writer.write_all(&sealed)?;
counter += 1;
if is_final {
break;
}
current = next;
}
Ok(())
}
pub fn decrypt_stream<R: Read, W: Write>(
reader: &mut R,
writer: &mut W,
key: &Gcm256Key,
root_nonce: &GcmNonce,
chunk_size: u32,
) -> Result<(), Error> {
if chunk_size == 0 || chunk_size > MAX_CHUNK_SIZE {
return Err(Error::InvalidChunkSize);
}
let cipher = Aes256Gcm::new(&Key::<Aes256Gcm>::from(*key.as_slice()));
let size = chunk_size as usize + TAG_LEN;
let mut counter: u64 = 0;
let mut current = fill_chunk(reader, vec![0u8; size])?;
loop {
let next = fill_chunk(reader, vec![0u8; size])?;
let is_final = next.is_empty();
let nonce = chunk_nonce(root_nonce, counter);
let aad = chunk_aad(counter, is_final);
let plaintext = cipher.decrypt(
&Nonce::from(*nonce.as_slice()),
Payload {
msg: ¤t,
aad: &aad,
},
)?;
writer.write_all(&plaintext)?;
counter += 1;
if is_final {
break;
}
current = next;
}
Ok(())
}