use argon2::{Algorithm, Argon2, Params, Version};
use chacha20poly1305::{AeadInPlace, KeyInit, XChaCha20Poly1305};
use crate::{
AAD, Error, HEADER_SIZE, Result, TAG_SIZE,
format::{DerivedKey, Header},
};
#[derive(Clone, Debug)]
pub struct Encryptor<'m> {
header: Header,
dk: DerivedKey,
plaintext: &'m [u8],
}
impl<'m> Encryptor<'m> {
#[cfg(feature = "alloc")]
#[inline]
pub fn new(plaintext: &'m impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>) -> Result<Self> {
Self::with_params(plaintext, passphrase, Params::default())
}
#[inline]
pub fn with_params(
plaintext: &'m impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
params: Params,
) -> Result<Self> {
Self::with_context(
plaintext,
passphrase,
Algorithm::default(),
Version::default(),
params,
)
}
pub fn with_context(
plaintext: &'m impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
argon2_type: Algorithm,
argon2_version: Version,
params: Params,
) -> Result<Self> {
let inner = |plaintext: &'m [u8],
passphrase: &[u8],
argon2_type: Algorithm,
argon2_version: Version,
params: Params|
-> Result<Self> {
let mut header = Header::new(argon2_type, argon2_version, params);
let mut dk = [u8::default(); DerivedKey::SIZE];
let argon2 = Argon2::new(
header.argon2_type().into(),
header.argon2_version().into(),
header.params().into(),
);
#[cfg(feature = "alloc")]
argon2
.hash_password_into(passphrase, &header.salt(), &mut dk)
.map_err(Error::InvalidArgon2Context)?;
#[cfg(not(feature = "alloc"))]
{
let mut memory_blocks = crate::MEMORY_BLOCKS;
argon2
.hash_password_into_with_memory(
passphrase,
&header.salt(),
&mut dk,
&mut memory_blocks,
)
.map_err(Error::InvalidArgon2Context)?;
}
let dk = DerivedKey::new(dk);
header.compute_mac(&dk.mac());
Ok(Self {
header,
dk,
plaintext,
})
};
inner(
plaintext.as_ref(),
passphrase.as_ref(),
argon2_type,
argon2_version,
params,
)
}
pub fn encrypt(&self, buf: &mut (impl AsMut<[u8]> + ?Sized)) {
let inner = |encryptor: &Self, buf: &mut [u8]| {
buf[..HEADER_SIZE].copy_from_slice(&encryptor.header.as_bytes());
let payload = &mut buf[HEADER_SIZE..(self.out_len() - TAG_SIZE)];
payload.copy_from_slice(encryptor.plaintext);
let cipher = XChaCha20Poly1305::new(&encryptor.dk.encrypt());
let tag = cipher
.encrypt_in_place_detached(&encryptor.header.nonce(), AAD, payload)
.expect("data too long");
buf[(self.out_len() - TAG_SIZE)..].copy_from_slice(&tag);
};
inner(self, buf.as_mut());
}
#[cfg(feature = "alloc")]
#[must_use]
#[inline]
pub fn encrypt_to_vec(&self) -> alloc::vec::Vec<u8> {
let mut buf = vec![u8::default(); self.out_len()];
self.encrypt(&mut buf);
buf
}
#[must_use]
#[inline]
pub const fn out_len(&self) -> usize {
assert!(self.plaintext.len() <= (usize::MAX - HEADER_SIZE - TAG_SIZE));
HEADER_SIZE + self.plaintext.len() + TAG_SIZE
}
}
#[cfg(feature = "alloc")]
#[inline]
pub fn encrypt(
plaintext: impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
) -> Result<alloc::vec::Vec<u8>> {
Encryptor::new(&plaintext, passphrase).map(|c| c.encrypt_to_vec())
}
#[allow(clippy::module_name_repetitions)]
#[cfg(feature = "alloc")]
#[inline]
pub fn encrypt_with_params(
plaintext: impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
params: Params,
) -> Result<alloc::vec::Vec<u8>> {
Encryptor::with_params(&plaintext, passphrase, params).map(|c| c.encrypt_to_vec())
}
#[allow(clippy::module_name_repetitions)]
#[cfg(feature = "alloc")]
#[inline]
pub fn encrypt_with_context(
plaintext: impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
argon2_type: Algorithm,
argon2_version: Version,
params: Params,
) -> Result<alloc::vec::Vec<u8>> {
Encryptor::with_context(&plaintext, passphrase, argon2_type, argon2_version, params)
.map(|c| c.encrypt_to_vec())
}