diff --git a/Cargo.lock b/Cargo.lock index 529fc38..c32428d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ version = "0.6.0" dependencies = [ "hex-literal", "polyval", + "zeroize", ] [[package]] diff --git a/ghash/Cargo.toml b/ghash/Cargo.toml index e48ff2d..4e19546 100644 --- a/ghash/Cargo.toml +++ b/ghash/Cargo.toml @@ -17,9 +17,11 @@ as in the AES-GCM authenticated encryption cipher. [dependencies] polyval = { version = "0.7", features = ["hazmat"] } +zeroize = { version = "1", optional = true, default-features = false } [features] -zeroize = ["polyval/zeroize"] +hazmat = [] +zeroize = ["dep:zeroize", "polyval/zeroize"] [dev-dependencies] hex-literal = "1" diff --git a/ghash/src/field_element.rs b/ghash/src/field_element.rs new file mode 100644 index 0000000..1f24fe4 --- /dev/null +++ b/ghash/src/field_element.rs @@ -0,0 +1,183 @@ +//! GHASH field element implementation. +//! +//! This module implements GHASH's field in terms of POLYVAL's, which is its little endian +//! equivalent. Elements are stored in POLYVAL's representation, i.e. as +//! `mulX_POLYVAL(ByteReverse(a))` as described in [RFC 8452 Appendix A], the inverse conversion +//! being `ByteReverse(divX_POLYVAL(a))`. +//! +//! This representation preserves both addition and multiplication (the latter because POLYVAL's +//! multiplication includes a Montgomery factor of `x^-128`, which cancels out the `x^127` +//! introduced by the byte reversal), so the arithmetic is a direct delegation to +//! [`polyval::hazmat::FieldElement`]. +//! +//! [RFC 8452 Appendix A]: https://tools.ietf.org/html/rfc8452#appendix-A + +use crate::Block; +use core::{ + fmt::{self, Debug}, + ops::{Add, Mul, MulAssign}, +}; +use polyval::{BLOCK_SIZE, hazmat::FieldElement as PolyvalElement}; + +#[cfg(feature = "zeroize")] +use zeroize::Zeroize; + +/// An element in GHASH's field. +/// +/// This type represents an element of the binary field GF(2^128) modulo the irreducible polynomial +/// `x^128 + x^7 + x^2 + x + 1` as described in [NIST SP 800-38D §6.3]. +/// +/// Arithmetic in GHASH's field has the following properties: +/// - All arithmetic operations are performed modulo the polynomial above. +/// - Addition is equivalent to the XOR operation applied to the two field elements +/// - Multiplication is carryless +/// +/// Note that elements are stored internally in POLYVAL's field (see the module-level +/// documentation), and thus converting to and from a byte representation is not free. +/// +/// [NIST SP 800-38D §6.3]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct FieldElement(PolyvalElement); + +impl Debug for FieldElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(&self.0, f) + } +} + +impl From for FieldElement { + #[inline] + fn from(block: Block) -> Self { + Self::from(<[u8; BLOCK_SIZE]>::from(block)) + } +} + +impl From<&Block> for FieldElement { + #[inline] + fn from(block: &Block) -> Self { + Self::from(*block) + } +} + +impl From for Block { + #[inline] + fn from(fe: FieldElement) -> Self { + <[u8; BLOCK_SIZE]>::from(fe).into() + } +} + +impl From<&FieldElement> for Block { + #[inline] + fn from(fe: &FieldElement) -> Self { + Self::from(*fe) + } +} + +impl From<[u8; BLOCK_SIZE]> for FieldElement { + /// Convert a GHASH field element into POLYVAL's representation. + #[inline] + fn from(bytes: [u8; BLOCK_SIZE]) -> Self { + Self(PolyvalElement::from(bytes).reverse().mulx()) + } +} + +impl From for [u8; BLOCK_SIZE] { + /// Convert a POLYVAL field element back into GHASH's representation. + #[inline] + fn from(fe: FieldElement) -> Self { + fe.0.divx().reverse().into() + } +} + +impl From for FieldElement { + #[inline] + fn from(x: u128) -> Self { + Self::from(x.to_be_bytes()) + } +} + +impl From for u128 { + #[inline] + fn from(fe: FieldElement) -> Self { + u128::from_be_bytes(fe.into()) + } +} + +impl Add for FieldElement { + type Output = Self; + + /// Adds two GHASH field elements. + #[inline] + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Mul for FieldElement { + type Output = Self; + + /// Perform carryless multiplication within GHASH's field modulo its polynomial. + #[inline] + fn mul(self, rhs: Self) -> Self { + Self(self.0 * rhs.0) + } +} + +impl MulAssign for FieldElement { + #[inline] + fn mul_assign(&mut self, rhs: Self) { + *self = *self * rhs; + } +} + +#[cfg(feature = "zeroize")] +impl Zeroize for FieldElement { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +#[cfg(test)] +impl PartialEq for FieldElement { + fn eq(&self, other: &Self) -> bool { + <[u8; BLOCK_SIZE]>::from(*self) == <[u8; BLOCK_SIZE]>::from(*other) + } +} + +#[cfg(test)] +mod tests { + use super::FieldElement; + use hex_literal::hex; + + // Test vectors for GHASH from RFC 8452 Appendix A + // https://tools.ietf.org/html/rfc8452#appendix-A + + const H: [u8; 16] = hex!("25629347589242761d31f826ba4b757b"); + const X_1: [u8; 16] = hex!("4f4f95668c83dfb6401762bb2d01a262"); + const X_2: [u8; 16] = hex!("d1a24ddd2721d006bbe45f20d3c9f362"); + + /// GHASH(H, X_1, X_2) + const GHASH_RESULT: [u8; 16] = hex!("bd9b3997046731fb96251b91f9c99d7a"); + + /// Converting to POLYVAL's field and back is the identity. + #[test] + fn roundtrip() { + assert_eq!(<[u8; 16]>::from(FieldElement::from(H)), H); + } + + /// Addition is the XOR of the GHASH representations. + #[test] + fn fe_add() { + let expected = FieldElement::from(hex!("9eedd8bbaba20fb0fbf33d9bfec85100")); + assert_eq!(FieldElement::from(X_1) + FieldElement::from(X_2), expected); + } + + /// GHASH is `((X_1 * H) + X_2) * H`. + #[test] + fn fe_mul() { + let h = FieldElement::from(H); + let y = (FieldElement::from(X_1) * h + FieldElement::from(X_2)) * h; + assert_eq!(<[u8; 16]>::from(y), GHASH_RESULT); + } +} diff --git a/ghash/src/hazmat.rs b/ghash/src/hazmat.rs new file mode 100644 index 0000000..2b1a8d5 --- /dev/null +++ b/ghash/src/hazmat.rs @@ -0,0 +1,8 @@ +//! Hazardous materials: functionality which can be misused and needs to be used with care. +//! +//!
+//! Functionality provided in this module is low-level and intended for constructing higher-level +//! primitives as opposed to being used directly. +//!
+ +pub use crate::field_element::FieldElement; diff --git a/ghash/src/lib.rs b/ghash/src/lib.rs index d24a991..1198476 100644 --- a/ghash/src/lib.rs +++ b/ghash/src/lib.rs @@ -6,6 +6,12 @@ )] #![warn(missing_docs)] +#[cfg(feature = "hazmat")] +pub mod hazmat; + +#[cfg(feature = "hazmat")] +mod field_element; + pub use polyval::universal_hash; use polyval::{Polyval, hazmat::FieldElement};