use aes::{
cipher::{generic_array::GenericArray, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher},
Aes256,
};
use ctr::Ctr128BE;
use hkdf::Hkdf;
use hmac::{
digest::{FixedOutput, MacError},
Hmac, Mac as _,
};
use pbkdf2::pbkdf2;
use rand::{thread_rng, RngCore};
use sha2::{Sha256, Sha512};
use zeroize::{Zeroize, ZeroizeOnDrop};
pub(crate) const IV_SIZE: usize = 16;
pub(crate) const KEY_SIZE: usize = 32;
pub(crate) const SALT_SIZE: usize = 16;
pub(crate) const MAC_SIZE: usize = 32;
type Aes256Ctr = Ctr128BE<Aes256>;
type Aes256Key = GenericArray<u8, <Aes256Ctr as KeySizeUser>::KeySize>;
type Aes256Iv = GenericArray<u8, <Aes256Ctr as IvSizeUser>::IvSize>;
type HmacSha256Key = [u8; KEY_SIZE];
#[derive(Debug)]
pub(crate) struct HmacSha256Mac([u8; MAC_SIZE]);
impl HmacSha256Mac {
pub(crate) fn as_bytes(&self) -> &[u8; MAC_SIZE] {
&self.0
}
pub(crate) fn into_bytes(self) -> [u8; MAC_SIZE] {
self.0
}
pub(crate) fn from_slice(bytes: &[u8]) -> Option<Self> {
if bytes.len() != MAC_SIZE {
None
} else {
let mut mac = [0u8; MAC_SIZE];
mac.copy_from_slice(bytes);
Some(HmacSha256Mac(mac))
}
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub(crate) struct AesHmacSha2Key {
aes_key: Box<[u8; KEY_SIZE]>,
mac_key: Box<[u8; KEY_SIZE]>,
}
impl AesHmacSha2Key {
const ZERO_SALT: &'static [u8; 32] = &[0u8; 32];
pub(crate) fn from_secret_storage_key(
secret_storage_key: &[u8; KEY_SIZE],
secret_name: &str,
) -> Self {
let mut expanded_keys = [0u8; KEY_SIZE * 2];
let hkdf: Hkdf<Sha256> = Hkdf::new(Some(Self::ZERO_SALT), secret_storage_key);
hkdf.expand(secret_name.as_bytes(), &mut expanded_keys)
.expect("We should be able to expand 64 bytes of output key material.");
let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
expanded_keys.zeroize();
Self { aes_key, mac_key }
}
pub(crate) fn from_passphrase(
passphrase: &str,
pbkdf_rounds: u32,
salt: &[u8; SALT_SIZE],
) -> Self {
let mut expanded_keys = [0u8; KEY_SIZE * 2];
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt, pbkdf_rounds, &mut expanded_keys)
.expect(
"We should be able to expand a passphrase of any length due to \
HMAC being able to be initialized with any input size",
);
let (aes_key, mac_key) = Self::split_keys(&expanded_keys);
expanded_keys.zeroize();
Self { aes_key, mac_key }
}
pub(crate) fn encrypt(&self, plaintext: Vec<u8>) -> (Vec<u8>, [u8; IV_SIZE]) {
let initialization_vector = Self::generate_iv();
let ciphertext = self.apply_keystream(plaintext, &initialization_vector);
(ciphertext, initialization_vector)
}
pub(crate) fn apply_keystream(
&self,
mut plaintext: Vec<u8>,
initialization_vector: &[u8; IV_SIZE],
) -> Vec<u8> {
let mut cipher =
Aes256Ctr::new(self.aes_key(), Aes256Iv::from_slice(initialization_vector));
cipher.apply_keystream(&mut plaintext);
plaintext
}
pub(crate) fn create_mac_tag(&self, ciphertext: &[u8]) -> HmacSha256Mac {
let mut mac = [0u8; 32];
let mac_array = GenericArray::from_mut_slice(&mut mac);
let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
.expect("We should be able to create a new HMAC object from our 32 byte MAC key");
hmac.update(ciphertext);
hmac.finalize_into(mac_array);
HmacSha256Mac(mac)
}
pub(crate) fn verify_mac(&self, message: &[u8], mac: &[u8; MAC_SIZE]) -> Result<(), MacError> {
let mac_array = GenericArray::from_slice(mac);
let mut hmac = Hmac::<Sha256>::new_from_slice(self.mac_key())
.expect("We should be able to create a new HMAC object from our 32 byte MAC key");
hmac.update(message);
hmac.verify(mac_array)
}
pub(crate) fn decrypt(
&self,
ciphertext: Vec<u8>,
initialization_vector: &[u8; IV_SIZE],
) -> Vec<u8> {
self.apply_keystream(ciphertext, initialization_vector)
}
fn split_keys(
expanded_keys: &[u8; KEY_SIZE * 2],
) -> (Box<[u8; KEY_SIZE]>, Box<[u8; KEY_SIZE]>) {
let mut aes_key = Box::new([0u8; KEY_SIZE]);
let mut mac_key = Box::new([0u8; KEY_SIZE]);
aes_key.copy_from_slice(&expanded_keys[0..32]);
mac_key.copy_from_slice(&expanded_keys[32..64]);
(aes_key, mac_key)
}
fn generate_iv() -> [u8; IV_SIZE] {
let mut rng = thread_rng();
let mut iv = [0u8; IV_SIZE];
rng.fill_bytes(&mut iv);
Self::clamp_iv(iv)
}
fn clamp_iv(iv: [u8; 16]) -> [u8; IV_SIZE] {
let mut iv = u128::from_be_bytes(iv);
iv &= !(1 << 63);
iv.to_be_bytes()
}
fn aes_key(&self) -> &Aes256Key {
Aes256Key::from_slice(self.aes_key.as_slice())
}
fn mac_key(&self) -> &HmacSha256Key {
&self.mac_key
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn encryption_roundtrip() {
let plaintext = "It's a secret to everybody";
let salt = [0u8; SALT_SIZE];
let key = AesHmacSha2Key::from_passphrase("My passphrase", 10, &salt);
let (ciphertext, iv) = key.encrypt(plaintext.as_bytes().to_vec());
let mac = key.create_mac_tag(&ciphertext);
key.verify_mac(&ciphertext, mac.as_bytes())
.expect("The MAC tag should be successfully verified");
let decrypted = key.decrypt(ciphertext, &iv);
assert_eq!(
plaintext.as_bytes(),
decrypted,
"An encryption roundtrip should produce the same plaintext"
);
}
#[test]
fn mac_decoding() {
let invalid_mac = [0u8; 10];
assert!(
HmacSha256Mac::from_slice(&invalid_mac).is_none(),
"We should return an error if the MAC is too short"
);
let mac = [0u8; 32];
HmacSha256Mac::from_slice(&mac)
.expect("We should be able to create a MAC from a 32 byte long slice");
}
}