use aes::cipher::{KeyIvInit, StreamCipher, generic_array::GenericArray};
use hmac::Mac;
use scrypt::Params;
use crate::{
Aes256Ctr128BE, HEADER_SIZE, HmacSha256, HmacSha256Key, HmacSha256Output, TAG_SIZE,
format::{DerivedKey, Header},
};
#[derive(Clone, Debug)]
pub struct Encryptor<'m> {
header: Header,
dk: DerivedKey,
plaintext: &'m [u8],
}
impl<'m> Encryptor<'m> {
#[inline]
pub fn new(plaintext: &'m impl AsRef<[u8]>, passphrase: impl AsRef<[u8]>) -> Self {
Self::with_params(plaintext, passphrase, Params::default())
}
#[allow(clippy::missing_panics_doc)]
pub fn with_params(
plaintext: &'m impl AsRef<[u8]>,
passphrase: impl AsRef<[u8]>,
params: Params,
) -> Self {
let inner = |plaintext: &'m [u8], passphrase: &[u8], params: Params| -> Self {
let mut header = Header::new(params);
let mut dk = [u8::default(); DerivedKey::SIZE];
scrypt::scrypt(passphrase, &header.salt(), &header.params().into(), &mut dk)
.expect("derived key size should be 64 bytes");
let dk = DerivedKey::new(dk);
header.compute_checksum();
header.compute_mac(&dk.mac());
Self {
header,
dk,
plaintext,
}
};
inner(plaintext.as_ref(), passphrase.as_ref(), params)
}
pub fn encrypt(&self, buf: &mut (impl AsMut<[u8]> + ?Sized)) {
let inner = |encryptor: &Self, buf: &mut [u8]| {
fn compute_mac(data: &[u8], key: &HmacSha256Key) -> HmacSha256Output {
let mut mac = HmacSha256::new_from_slice(key)
.expect("HMAC-SHA-256 key size should be 256 bits");
mac.update(data);
mac.finalize().into_bytes()
}
let bound = (HEADER_SIZE, encryptor.out_len() - TAG_SIZE);
buf[..bound.0].copy_from_slice(&encryptor.header.as_bytes());
let body = &mut buf[bound.0..bound.1];
body.copy_from_slice(encryptor.plaintext);
let mut cipher = Aes256Ctr128BE::new(&encryptor.dk.encrypt(), &GenericArray::default());
cipher.apply_keystream(body);
let mac = compute_mac(&buf[..bound.1], &encryptor.dk.mac());
buf[bound.1..].copy_from_slice(&mac);
};
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]>) -> alloc::vec::Vec<u8> {
Encryptor::new(&plaintext, passphrase).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,
) -> alloc::vec::Vec<u8> {
Encryptor::with_params(&plaintext, passphrase, params).encrypt_to_vec()
}