eme2 0.3.0

EME2 (ECB-Mask-ECB) wide-block cipher mode of operation
Documentation
//! Independent reference implementation, transcribed line-by-line from
//! IEEE P1619.2 Table 1 (encryption) / Table 2 (decryption) — see
//! `docs/eme2-ieee.pdf`. Deliberately naive, allocating, and 1-indexed to
//! mirror the standard's pseudocode as closely as possible, and shares no
//! code with `src/eme2.rs`. This exists because there are no publicly
//! published EME2-AES test vectors to validate against; cross-checking two
//! independently-written implementations against the same specification text
//! is the next best thing to third-party known-answer vectors.

use aes::Aes256;
use cipher::{BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};

type B = [u8; 16];

fn mult_by_alpha(x: &B) -> B {
    let mut y = [0u8; 16];
    for i in 0..16 {
        y[i] = x[i].wrapping_mul(2);
        if i > 0 && x[i - 1] > 127 {
            y[i] = y[i].wrapping_add(1);
        }
    }
    if x[15] > 127 {
        y[0] ^= 0x87;
    }
    y
}

fn xor(a: &B, b: &B) -> B {
    let mut o = [0u8; 16];
    for i in 0..16 {
        o[i] = a[i] ^ b[i];
    }
    o
}

fn enc(c: &Aes256, b: &B) -> B {
    let mut t = (*b).into();
    c.encrypt_block(&mut t);
    t.into()
}

fn dec(c: &Aes256, b: &B) -> B {
    let mut t = (*b).into();
    c.decrypt_block(&mut t);
    t.into()
}

/// IEEE Table 1/2, lines 1-11: hash the associated data into `T_star`.
fn t_star(c: &Aes256, key3: &B, t: &[u8]) -> B {
    if t.is_empty() {
        return enc(c, key3);
    }
    let mut k3 = mult_by_alpha(key3);
    let blocks: Vec<&[u8]> = t.chunks(16).collect();
    let r = blocks.len();
    let mut acc = [0u8; 16];
    for block in blocks.iter().take(r - 1) {
        let mut ti = [0u8; 16];
        ti.copy_from_slice(block);
        acc = xor(&acc, &xor(&enc(c, &xor(&k3, &ti)), &k3));
        k3 = mult_by_alpha(&k3);
    }
    let last = blocks[r - 1];
    let mut tr = [0u8; 16];
    if last.len() < 16 {
        tr[..last.len()].copy_from_slice(last);
        tr[last.len()] = 0x80;
        k3 = mult_by_alpha(&k3);
    } else {
        tr.copy_from_slice(last);
    }
    acc = xor(&acc, &xor(&enc(c, &xor(&k3, &tr)), &k3));
    acc
}

/// IEEE Table 1, lines 12-45.
// Single-letter names (c, m, p, t) and index-based loops mirror the spec's own
// notation on purpose, to keep this transcription checkable line-by-line
// against Table 1.
#[allow(clippy::many_single_char_names, clippy::needless_range_loop)]
pub fn eme2_encrypt(c: &Aes256, key2: &B, key3: &B, t: &[u8], p: &[u8]) -> Vec<u8> {
    let ts = t_star(c, key3, t);
    let n = p.len();
    let m = n.div_ceil(16);
    let lm = if n.is_multiple_of(16) { 16 } else { n % 16 };

    let blk = |i: usize| -> &[u8] {
        let s = (i - 1) * 16;
        &p[s..core::cmp::min(s + 16, n)]
    };

    let mut ppp: Vec<B> = vec![[0u8; 16]; m + 1];
    let mut l = *key2;
    for i in 1..m {
        let mut pi = [0u8; 16];
        pi.copy_from_slice(blk(i));
        ppp[i] = enc(c, &xor(&l, &pi));
        l = mult_by_alpha(&l);
    }
    if lm < 16 {
        ppp[m][..lm].copy_from_slice(blk(m));
        ppp[m][lm] = 0x80;
    } else {
        let mut pm = [0u8; 16];
        pm.copy_from_slice(blk(m));
        ppp[m] = enc(c, &xor(&l, &pm));
    }

    let mut mp = ts;
    for pi in ppp.iter().take(m + 1).skip(1) {
        mp = xor(&mp, pi);
    }

    let (mm, mc1) = if lm < 16 {
        let mm = enc(c, &mp);
        let mc1 = enc(c, &mm);
        (mm, mc1)
    } else {
        ([0u8; 16], enc(c, &mp))
    };
    let m1 = xor(&mp, &mc1);
    let mut mrun = m1;

    let mut ccc: Vec<B> = vec![[0u8; 16]; m + 1];
    let mut cm = [0u8; 16];
    for i in 2..=m.saturating_sub(1) {
        if (i - 1) % 128 > 0 {
            mrun = mult_by_alpha(&mrun);
            ccc[i] = xor(&ppp[i], &mrun);
        } else {
            let mpj = xor(&ppp[i], &m1);
            let mcj = enc(c, &mpj);
            mrun = xor(&mpj, &mcj);
            ccc[i] = xor(&mcj, &m1);
        }
    }
    if lm < 16 {
        for i in 0..lm {
            cm[i] = blk(m)[i] ^ mm[i];
        }
        ccc[m][..lm].copy_from_slice(&cm[..lm]);
        ccc[m][lm] = 0x80;
    } else if !(m - 1).is_multiple_of(128) {
        mrun = mult_by_alpha(&mrun);
        ccc[m] = xor(&ppp[m], &mrun);
    } else {
        ccc[m] = xor(&enc(c, &xor(&m1, &ppp[m])), &m1);
    }
    let mut c1 = xor(&mc1, &ts);
    for cc in ccc.iter().take(m + 1).skip(2) {
        c1 = xor(&c1, cc);
    }
    ccc[1] = c1;

    let mut out = vec![0u8; n];
    let mut l = *key2;
    for i in 1..m {
        let ci = xor(&enc(c, &ccc[i]), &l);
        out[(i - 1) * 16..i * 16].copy_from_slice(&ci);
        l = mult_by_alpha(&l);
    }
    if lm == 16 {
        let cmf = xor(&enc(c, &ccc[m]), &l);
        out[(m - 1) * 16..].copy_from_slice(&cmf);
    } else {
        out[(m - 1) * 16..].copy_from_slice(&cm[..lm]);
    }
    out
}

/// IEEE Table 2: identical structure to Table 1 with `AES-Enc` -> `AES-Dec`.
#[allow(clippy::many_single_char_names, clippy::needless_range_loop)]
pub fn eme2_decrypt(c: &Aes256, key2: &B, key3: &B, t: &[u8], ct: &[u8]) -> Vec<u8> {
    let ts = t_star(c, key3, t);
    let n = ct.len();
    let m = n.div_ceil(16);
    let lm = if n.is_multiple_of(16) { 16 } else { n % 16 };
    let blk = |i: usize| -> &[u8] {
        let s = (i - 1) * 16;
        &ct[s..core::cmp::min(s + 16, n)]
    };

    let mut ccc: Vec<B> = vec![[0u8; 16]; m + 1];
    let mut l = *key2;
    for i in 1..m {
        let mut ci = [0u8; 16];
        ci.copy_from_slice(blk(i));
        ccc[i] = dec(c, &xor(&l, &ci));
        l = mult_by_alpha(&l);
    }
    if lm < 16 {
        ccc[m][..lm].copy_from_slice(blk(m));
        ccc[m][lm] = 0x80;
    } else {
        let mut cmb = [0u8; 16];
        cmb.copy_from_slice(blk(m));
        ccc[m] = dec(c, &xor(&l, &cmb));
    }

    let mut mc = ts;
    for cc in ccc.iter().take(m + 1).skip(1) {
        mc = xor(&mc, cc);
    }

    let (mm, mp1) = if lm < 16 {
        let mm = dec(c, &mc);
        let mp1 = dec(c, &mm);
        (mm, mp1)
    } else {
        ([0u8; 16], dec(c, &mc))
    };
    let m1 = xor(&mp1, &mc);
    let mut mrun = m1;

    let mut ppp: Vec<B> = vec![[0u8; 16]; m + 1];
    let mut pm = [0u8; 16];
    for i in 2..=m.saturating_sub(1) {
        if (i - 1) % 128 > 0 {
            mrun = mult_by_alpha(&mrun);
            ppp[i] = xor(&ccc[i], &mrun);
        } else {
            let mcj = xor(&ccc[i], &m1);
            let mpj = dec(c, &mcj);
            mrun = xor(&mpj, &mcj);
            ppp[i] = xor(&mpj, &m1);
        }
    }
    if lm < 16 {
        for i in 0..lm {
            pm[i] = blk(m)[i] ^ mm[i];
        }
        ppp[m][..lm].copy_from_slice(&pm[..lm]);
        ppp[m][lm] = 0x80;
    } else if !(m - 1).is_multiple_of(128) {
        mrun = mult_by_alpha(&mrun);
        ppp[m] = xor(&ccc[m], &mrun);
    } else {
        ppp[m] = xor(&dec(c, &xor(&m1, &ccc[m])), &m1);
    }
    let mut p1 = xor(&mp1, &ts);
    for pi in ppp.iter().take(m + 1).skip(2) {
        p1 = xor(&p1, pi);
    }
    ppp[1] = p1;

    let mut out = vec![0u8; n];
    let mut l = *key2;
    for i in 1..m {
        let pi = xor(&dec(c, &ppp[i]), &l);
        out[(i - 1) * 16..i * 16].copy_from_slice(&pi);
        l = mult_by_alpha(&l);
    }
    if lm == 16 {
        let pmf = xor(&dec(c, &ppp[m]), &l);
        out[(m - 1) * 16..].copy_from_slice(&pmf);
    } else {
        out[(m - 1) * 16..].copy_from_slice(&pm[..lm]);
    }
    out
}

pub fn raw_cipher(key1: &[u8]) -> Aes256 {
    Aes256::new_from_slice(key1).expect("key1 is 32 bytes")
}