Skip to content

Commit

Permalink
reimpl: galois multiplication
Browse files Browse the repository at this point in the history
This commit contains 2 main changes:

Updates to documentation,
Using 7-degree polynomials of `BinaryField`s to represent bytes to do
a multiplication instead of doing carry-less multiplication.
  • Loading branch information
eightfilms committed Jul 8, 2024
1 parent 9433a57 commit 4a97c58
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 33 deletions.
97 changes: 65 additions & 32 deletions src/encryption/symmetric/aes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
//! and decryption.
#![cfg_attr(not(doctest), doc = include_str!("./README.md"))]

use std::ops::{Mul, Rem};

use itertools::Itertools;

pub mod sbox;
#[cfg(test)] pub mod tests;

use super::SymmetricEncryption;
use crate::encryption::symmetric::aes::sbox::{INVERSE_SBOX, SBOX};
use crate::{
encryption::symmetric::aes::sbox::{INVERSE_SBOX, SBOX},
field::{binary_towers::BinaryField, FiniteField},
polynomial::{Monomial, Polynomial},
};

/// A block in AES represents a 128-bit sized message data.
pub type Block = [u8; 16];
Expand Down Expand Up @@ -136,29 +142,56 @@ pub struct AES<const N: usize> {}
#[derive(Debug, Default, Clone, Copy, PartialEq)]
struct State([[u8; 4]; 4]);

/// Multiplies a 8-bit number in the Galois field GF(2^8). This is done using "carry-less
/// multiplication" or "bitwise multiplication", where the binary representation of each
/// element is treated as a polynomial.
/// Multiplies a 8-bit number in the Galois field GF(2^8).
///
/// NOTE: this multiplication is not commutative - ie. A * B may not equal B * A.
fn galois_multiplication(mut col: u8, mut multiplicant: usize) -> u8 {
let mut product = 0;

for _ in 0..8 {
if multiplicant & 1 == 1 {
product ^= col;
}

let hi_bit = col & 0x80;
col <<= 1;
if hi_bit == 0x80 {
col ^= 0x1B; // This XOR brings the value back into the field if an overflow occurs
// (ie. hi_bit is set)
}

/// This is defined on two bytes in two steps:
///
/// 1) The two polynomials that represent the bytes are multiplied as polynomials,
/// 2) The resulting polynomial is reduced modulo the following fixed polynomial:
///
/// m(x) = x^8 + x^4 + x^3 + x + 1
///
/// Note that in most AES implementations, this is done using "carry-less" multiplication -
/// to see how this works in field terms, this implementation uses an actual polynomial
/// implementation (a [`Polynomial`] of [`BinaryField`]s)
fn galois_multiplication(mut col: u8, mut multiplicant: u8) -> u8 {
// Decompose bits into degree-7 polynomials.
let mut col_bits = [BinaryField::new(0); 8];
let mut mult_bits = [BinaryField::new(0); 8];
for i in 0..8 {
col_bits[i] = BinaryField::new(col & 1);
mult_bits[i] = BinaryField::new(multiplicant & 1);
col >>= 1;
multiplicant >>= 1;
}
product

let col_poly = Polynomial::<Monomial, BinaryField, 8>::new(col_bits);
let mult_poly = Polynomial::<Monomial, BinaryField, 8>::new(mult_bits);
// m(x) = x^8 + x^4 + x^3 + x + 1
let reducer = Polynomial::<Monomial, BinaryField, 9>::new([
BinaryField::ONE,
BinaryField::ONE,
BinaryField::ZERO,
BinaryField::ONE,
BinaryField::ONE,
BinaryField::ZERO,
BinaryField::ZERO,
BinaryField::ZERO,
BinaryField::ONE,
]);

let res = col_poly.mul(mult_poly);
let result = res.rem(reducer); // reduce resulting polynomial modulo a fixed polynomial

assert!(result.degree() < 8, "did not get a u8 out of multiplication in GF(2^8)");
// Recompose polynomial into a u8.
let mut fin: u8 = 0;
for i in 0..8 {
let coeff: u8 = result.coefficients[i].into();
fin += coeff * (2_i16.pow(i as u32)) as u8;
}

fin
}

impl<const N: usize> AES<N>
Expand Down Expand Up @@ -339,12 +372,13 @@ where [(); N / 8]:
.unwrap();
}

/// Applies the same linear transformation to each of the four columns of the state.
///
/// Mix columns is done as such:
/// Mixes the data in each of the 4 columns with a single fixed matrix, with its entries taken
/// from the word [a_0, a_1, a_2, a_3] = [{02}, {01}, {01}, {03}] (hex) (or [2, 1, 1, 3] in
/// decimal).
///
/// Each column of bytes is treated as a 4-term polynomial, multiplied modulo x^4 + 1 with a fixed
/// polynomial a(x) = 3x^3 + x^2 + x + 2. This is done using matrix multiplication.
/// This is done by interpreting both the byte from the state and the byte from the fixed matrix
/// as degree-7 polynomials and doing multiplication in the GF(2^8) field. For details, see
/// [`galois_multiplication`].
fn mix_columns(state: &mut State) {
for col in state.0.iter_mut() {
let tmp = *col;
Expand All @@ -366,13 +400,12 @@ where [(); N / 8]:

/// The inverse of [`Self::mix_columns`].
///
/// Applies the same linear transformation to each of the four columns of the state.
///
/// Mix columns is done as such:
/// Mixes the data in each of the 4 columns with a single fixed matrix, with its entries taken
/// from the word [a_0, a_1, a_2, a_3] = [{0e}, {09}, {0d}, {0b}] (or [14, 9, 13, 11] in decimal).
///
/// Each column of bytes is treated as a 4-term polynomial, multiplied modulo x^4 + 1 with a
/// fixed polynomial a^-1(x) = 11x^3 + 13x^2 + 9x + 14, which is the inverse of the polynomial
/// used in [`Self::mix_columns`]. This is done using matrix multiplication.
/// This is done by interpreting both the byte from the state and the byte from the fixed matrix
/// as degree-7 polynomials and doing multiplication in the GF(2^8) field. For details, see
/// [`galois_multiplication`].
fn inv_mix_columns(state: &mut State) {
for col in state.0.iter_mut() {
let tmp = *col;
Expand Down
4 changes: 4 additions & 0 deletions src/field/binary_towers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ impl From<usize> for BinaryField {
fn from(value: usize) -> Self { Self::new(value as u8) }
}

impl From<BinaryField> for u8 {
fn from(value: BinaryField) -> u8 { value.0 }
}

impl Add for BinaryField {
type Output = Self;

Expand Down
2 changes: 1 addition & 1 deletion src/polynomial/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl<F: FiniteField, const D: usize> Polynomial<Monomial, F, D> {
pub fn new(coefficients: [F; D]) -> Self { Self { coefficients, basis: Monomial } }

/// Helper method to remove leading zeros from coefficients
fn trim_zeros(coefficients: &mut Vec<F>) {
pub fn trim_zeros(coefficients: &mut Vec<F>) {
while coefficients.last().cloned() == Some(F::ZERO) {
coefficients.pop();
}
Expand Down

0 comments on commit 4a97c58

Please sign in to comment.