use crate::error::{validate, Error, Result};
use dcrypt_internal::zeroing::{Zeroize, Zeroizing};
const GCM_BLOCK_SIZE: usize = 16;
#[derive(Clone)]
pub struct GHash {
h: [u8; GCM_BLOCK_SIZE],
y: [u8; GCM_BLOCK_SIZE],
}
impl Zeroize for GHash {
fn zeroize(&mut self) {
self.h.zeroize();
self.y.zeroize();
}
}
impl Drop for GHash {
fn drop(&mut self) {
self.zeroize();
}
}
impl GHash {
pub fn new(h: &[u8; GCM_BLOCK_SIZE]) -> Self {
let mut h_copy = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
h_copy.copy_from_slice(h);
let y = [0u8; GCM_BLOCK_SIZE];
Self {
h: h_copy.into_inner(),
y,
}
}
pub fn update(&mut self, data: &[u8]) -> Result<()> {
let mut offset = 0;
while offset + GCM_BLOCK_SIZE <= data.len() {
self.update_block(&data[offset..offset + GCM_BLOCK_SIZE], GCM_BLOCK_SIZE)?;
offset += GCM_BLOCK_SIZE;
}
if offset < data.len() {
let remaining = data.len() - offset;
self.update_block(&data[offset..], remaining)?;
}
Ok(())
}
pub fn update_block(&mut self, block: &[u8], block_len: usize) -> Result<()> {
validate::max_length("GHASH block", block_len, GCM_BLOCK_SIZE)?;
validate::min_length("GHASH block input", block.len(), block_len)?;
let mut temp_block = [0u8; GCM_BLOCK_SIZE];
for i in 0..GCM_BLOCK_SIZE {
let in_range = i.wrapping_sub(block_len) >> (usize::BITS - 1);
let mask = 0u8.wrapping_sub(in_range as u8);
let source_byte = if i < block_len { block[i] } else { 0 };
temp_block[i] = source_byte & mask;
}
for (y_byte, temp_byte) in self.y.iter_mut().zip(temp_block.iter()) {
*y_byte ^= temp_byte;
}
let product = Self::gf_multiply(&self.y, &self.h);
self.y.copy_from_slice(&*product);
Ok(())
}
pub fn update_lengths(&mut self, aad_len: u64, cipher_len: u64) -> Result<()> {
let mut length_block = [0u8; GCM_BLOCK_SIZE];
let aad_bits = aad_len.checked_mul(8).ok_or(Error::Processing {
operation: "GHASH length encoding",
details: "AAD length exceeds the GCM bit-length field",
})?;
let cipher_bits = cipher_len.checked_mul(8).ok_or(Error::Processing {
operation: "GHASH length encoding",
details: "ciphertext length exceeds the GCM bit-length field",
})?;
length_block[0..8].copy_from_slice(&aad_bits.to_be_bytes());
length_block[8..16].copy_from_slice(&cipher_bits.to_be_bytes());
self.update_block(&length_block, GCM_BLOCK_SIZE)
}
pub fn finalize(&self) -> [u8; GCM_BLOCK_SIZE] {
self.y
}
pub(crate) fn finalize_protected(&self) -> Zeroizing<[u8; GCM_BLOCK_SIZE]> {
Zeroizing::new(self.y)
}
#[inline(never)]
fn gf_multiply(x: &[u8; 16], y: &[u8; 16]) -> Zeroizing<[u8; 16]> {
const REDUCTION: u128 = 0xe100_0000_0000_0000_0000_0000_0000_0000;
let mut x_value = Zeroizing::new(u128::from_be_bytes(*x));
let mut v = Zeroizing::new(u128::from_be_bytes(*y));
let mut z = Zeroizing::new(0u128);
let mut x_mask = Zeroizing::new(0u128);
let mut reduction_mask = Zeroizing::new(0u128);
for _ in 0..128 {
*x_mask = 0u128.wrapping_sub(*x_value >> 127);
*z ^= *v & *x_mask;
*reduction_mask = 0u128.wrapping_sub(*v & 1);
*v = (*v >> 1) ^ (REDUCTION & *reduction_mask);
*x_value <<= 1;
}
Zeroizing::new(z.to_be_bytes())
}
}
pub fn process_ghash(
h: &[u8; GCM_BLOCK_SIZE],
aad: &[u8],
ciphertext: &[u8],
) -> Result<[u8; GCM_BLOCK_SIZE]> {
let mut ghash_instance = GHash::new(h);
ghash_instance.update(aad)?;
ghash_instance.update(ciphertext)?;
ghash_instance.update_lengths(aad.len() as u64, ciphertext.len() as u64)?;
Ok(ghash_instance.finalize())
}
#[cfg(test)]
mod tests;