zears/
lib.rs

1#![cfg_attr(feature = "simd", feature(portable_simd))]
2//! AEZ *\[sic!\]* v5 encryption implemented in Rust.
3//!
4//! # ☣️ Cryptographic hazmat ☣️
5//!
6//! This crate is not battle tested, nor is it audited. Its usage for critical systems is strongly
7//! discouraged. It mainly exists as a learning exercise.
8//!
9//! # AEZ encryption
10//!
11//! [AEZ](https://www.cs.ucdavis.edu/~rogaway/aez/index.html) is an authenticated encryption
12//! scheme. It works in two steps:
13//!
14//! * First, a known authentication block (a fixed number of zeroes) is appended to the message.
15//! * Second, the message is enciphered with an arbitrary-length blockcipher.
16//!
17//! The blockcipher is tweaked with the key, the nonce and additional data.
18//!
19//! The [paper](https://www.cs.ucdavis.edu/~rogaway/aez/aez.pdf) explains the security concepts of
20//! AEZ in more detail.
21//!
22//! # AEZ encryption (for laypeople)
23//!
24//! The security property of encryption schemes says that an adversary without key must not learn
25//! the content of a message, but the adversary might still be able to modify the message. For
26//! example, in AES-CTR, flipping a bit in the ciphertext means that the same bit will be flipped
27//! in the plaintext once the message is decrypted.
28//!
29//! Authenticated encryption solves this problem by including a mechanism to detect changes. This
30//! can be done for example by including a MAC, or using a mode like GCM (Galois counter mode). In
31//! many cases, not only the integrity of the ciphertext can be verified, but additional data can
32//! be provided during encryption and decryption which will also be included in the integrity
33//! check. This results in an *authenticated encryption with associated data* scheme, AEAD for
34//! short.
35//!
36//! AEZ employs a nifty technique in order to realize an AEAD scheme: The core of AEZ is an
37//! enciphering scheme, which in addition to "hiding" its input is also very "unpredictable",
38//! similar to a hash function. That means that if a ciphertext is changed slightly (by flipping a
39//! bit), the resulting plaintext will be unpredictably and completely different.
40//!
41//! With this property, authenticated encryption can be realized implicitly: The message is padded
42//! with a known string before enciphering it. If, after deciphering, this known string is not
43//! present, the message has been tampered with. Since the enciphering scheme is parametrized by
44//! the key, a nonce and arbitrary additional data, we can verify the integrity of associated data
45//! as well.
46//!
47//! # Other implementations
48//!
49//! As this library is a learning exercise, if you want to use AEZ in practice, it is suggested to
50//! use the [`aez`](https://crates.io/crates/aez) crate which provides bindings to the C reference
51//! implementation of AEZ.
52//!
53//! `zears` differs from `aez` in that ...
54//!
55//! * it works on platforms without hardware AES support, using the "soft" backend of
56//!   [`aes`](https://crates.io/crates/aes).
57//! * it does not inherit the limitations of the reference implementation in regards to nonce
58//!   length, authentication tag length, or the maximum of one associated data item.
59//!
60//! `zears` is tested with test vectors generated from the reference implementation using [Nick
61//! Mathewson's tool](https://github.com/nmathewson/aez_test_vectors).
62//!
63//! # Example usage
64//!
65//! The core of this crate is the [Aez] struct, which provides the high-level API. There is usually
66//! not a lot more that you need:
67//!
68//! ```
69//! # use zears::*;
70//! let aez = Aez::new(b"my secret key!");
71//! let cipher = aez.encrypt(b"nonce", &[b"associated data"], 16, b"message");
72//! let plaintext = aez.decrypt(b"nonce", &[b"associated data"], 16, &cipher);
73//! assert_eq!(plaintext.unwrap(), b"message");
74//!
75//! // Flipping a bit leads to decryption failure
76//! let mut cipher = aez.encrypt(b"nonce", &[], 16, b"message");
77//! cipher[0] ^= 0x02;
78//! let plaintext = aez.decrypt(b"nonce", &[], 16, &cipher);
79//! assert!(plaintext.is_none());
80//!
81//! // Similarly, modifying the associated data leads to failure
82//! let cipher = aez.encrypt(b"nonce", &[b"foo"], 16, b"message");
83//! let plaintext = aez.decrypt(b"nonce", &[b"bar"], 16, &cipher);
84//! assert!(plaintext.is_none());
85//! ```
86//!
87//! # Feature flags & compilation hints
88//!
89//! * Enable feature `simd` (requires nightly due to the `portable_simd` Rust feature) to speed up
90//!   encryption and decryption by using SIMD instructions (if available).
91//! * Use `target-cpu=native` (e.g. by setting `RUSTFLAGS=-Ctarget-cpu=native`) to make the
92//!   compiler emit vectorized AES instructions (if available). This can speed up
93//!   encryption/decryption at the cost of producing less portable code.
94//!
95//! On my machine, this produces the following results (for the `encrypt_inplace/2048` benchmark):
96//!
97//! | Compilation setup        | Throughput   | Speedup  |
98//! |--------------------------|--------------|----------|
99//! | baseline                 | 488.78 MiB/s |          |
100//! | +simd                    | 967.91 MiB/s | +98.187% |
101//! | target-cpu=native        | 2.0062 GiB/s | +314.67% |
102//! | +simd, target-cpu=native | 3.3272 GiB/s | +592.01% |
103//! | `aez` crate              | 4.8996 GiB/s |          |
104
105use constant_time_eq::constant_time_eq;
106
107mod accessor;
108mod aesround;
109mod block;
110
111#[cfg(test)]
112mod testvectors;
113
114use accessor::BlockAccessor;
115use aesround::AesRound;
116use block::Block;
117type Key = [u8; 48];
118type Tweak<'a> = &'a [&'a [u8]];
119
120static ZEROES: [u8; 1024] = [0; 1024];
121
122#[derive(Clone, Copy, PartialEq, Eq)]
123enum Mode {
124    Encipher,
125    Decipher,
126}
127
128/// AEZ encryption scheme.
129pub struct Aez {
130    key_i: Block,
131    key_j: Block,
132    key_l: Block,
133    key_l_multiples: [Block; 8],
134    aes: aesround::AesImpl,
135}
136
137impl Aez {
138    /// Create a new AEZ instance.
139    ///
140    /// The key is expanded using Blake2b, according to the AEZ specification.
141    ///
142    /// If you provide a key of the correct length (48 bytes), no expansion is done and the key is
143    /// taken as-is.
144    pub fn new(key: &[u8]) -> Self {
145        let key = extract(key);
146        let (key_i, key_j, key_l) = split_key(&key);
147        let aes = aesround::AesImpl::new(key_i, key_j, key_l);
148        let key_l_multiples = [
149            key_l * 0,
150            key_l * 1,
151            key_l * 2,
152            key_l * 3,
153            key_l * 4,
154            key_l * 5,
155            key_l * 6,
156            key_l * 7,
157        ];
158        Aez {
159            key_i,
160            key_j,
161            key_l,
162            key_l_multiples,
163            aes,
164        }
165    }
166
167    /// Encrypt the given data.
168    ///
169    /// This is a convenience function that allocates a fresh buffer of the appropriate size and
170    /// copies the data.
171    ///
172    /// Parameters:
173    ///
174    /// * `nonce` -- the nonce to use. Each nonce should only be used once, as re-using the nonce
175    ///   (without chaning the key) will lead to the same ciphertext being produced, potentially
176    ///   making it re-identifiable.
177    /// * `associated_data` -- additional data to be included in the integrity check. Note that
178    ///   this data will *not* be contained in the ciphertext, but it must be provided on
179    ///   decryption.
180    /// * `tau` -- number of *bytes* (not bits) to use for integrity checking. A value of `tau =
181    ///   16` gives 128 bits of security. Passing a value of 0 is valid and leads to no integrity
182    ///   checking.
183    /// * `data` -- actual data to encrypt. Can be empty, in which case the returned ciphertext
184    ///   provides a "hash" that verifies the integrity of the associated data will be
185    ///
186    /// Returns the ciphertext, which will be of length `data.len() + tau`.
187    pub fn encrypt(
188        &self,
189        nonce: &[u8],
190        associated_data: &[&[u8]],
191        tau: u32,
192        data: &[u8],
193    ) -> Vec<u8> {
194        let mut buffer = Vec::from(data);
195        self.encrypt_vec(nonce, associated_data, tau, &mut buffer);
196        buffer
197    }
198
199    /// Encrypts the data in the given [`Vec`].
200    ///
201    /// This function extends the vector with enough space to hold `tau` bytes of authentication
202    /// data. Afterwards, the vector will hold the ciphertext.
203    ///
204    /// If `tau == 0`, the vector will not be expanded.
205    ///
206    /// The parameters are the same as for [`Aez::encrypt`].
207    pub fn encrypt_vec(
208        &self,
209        nonce: &[u8],
210        associated_data: &[&[u8]],
211        tau: u32,
212        data: &mut Vec<u8>,
213    ) {
214        data.resize(data.len() + tau as usize, 0);
215        encrypt(&self, nonce, associated_data, tau, data);
216    }
217
218    /// Encrypts the data inplace.
219    ///
220    /// This function will overwrite the last `tau` bytes of the given buffer with the
221    /// authentication block before encrypting the data.
222    ///
223    /// If the buffer is smaller than `tau`, this function panics.
224    pub fn encrypt_inplace(
225        &self,
226        nonce: &[u8],
227        associated_data: &[&[u8]],
228        tau: u32,
229        buffer: &mut [u8],
230    ) {
231        assert!(buffer.len() >= tau as usize);
232        let data_len = buffer.len() - tau as usize;
233        append_auth(data_len, buffer);
234        encrypt(&self, nonce, associated_data, tau as u32, buffer);
235    }
236
237    /// Encrypts the data in the given buffer, writing the output to the given output buffer.
238    ///
239    /// This function will infer `tau` from the size difference between input and output. If the
240    /// output is smaller than the input, this funcion will panic.
241    ///
242    /// The `nonce` and `associated_data` parameters are the same as for [`Aez::encrypt`].
243    pub fn encrypt_buffer(
244        &self,
245        nonce: &[u8],
246        associated_data: &[&[u8]],
247        input: &[u8],
248        output: &mut [u8],
249    ) {
250        assert!(output.len() >= input.len());
251        let tau = output.len() - input.len();
252        output[..input.len()].copy_from_slice(input);
253        append_auth(input.len(), output);
254        encrypt(&self, nonce, associated_data, tau as u32, output);
255    }
256
257    /// Decrypts the given ciphertext.
258    ///
259    /// This is a convenience function that returns an owned version of the plaintext. If the
260    /// original buffer may be modified, you can use [`Aez::decrypt_inplace`] to save an allocation.
261    ///
262    /// Parameters:
263    ///
264    /// * `nonce`, `associated_data` and `tau` are as for [`Aez::encrypt`].
265    /// * `data` -- the ciphertext to decrypt.
266    ///
267    /// Returns the decrypted content. If the integrity check fails, returns `None` instead. The
268    /// returned vector has length `data.len() - tau`.
269    pub fn decrypt(
270        &self,
271        nonce: &[u8],
272        associated_data: &[&[u8]],
273        tau: u32,
274        data: &[u8],
275    ) -> Option<Vec<u8>> {
276        let mut buffer = Vec::from(data);
277        let len = match decrypt(&self, nonce, associated_data, tau, &mut buffer) {
278            None => return None,
279            Some(m) => m.len(),
280        };
281        buffer.truncate(len);
282        Some(buffer)
283    }
284
285    /// Decrypt the given buffer in-place.
286    ///
287    /// Returns a slice to the valid plaintext subslice, or `None`.
288    ///
289    /// The parameters are the same as for [`Aez::decrypt`].
290    pub fn decrypt_inplace<'a>(
291        &self,
292        nonce: &[u8],
293        associated_data: &[&[u8]],
294        tau: u32,
295        data: &'a mut [u8],
296    ) -> Option<&'a [u8]> {
297        decrypt(&self, nonce, associated_data, tau, data)
298    }
299}
300
301fn extract(key: &[u8]) -> [u8; 48] {
302    if key.len() == 48 {
303        key.try_into().unwrap()
304    } else {
305        use blake2::Digest;
306        type Blake2b384 = blake2::Blake2b<blake2::digest::consts::U48>;
307        let mut hasher = Blake2b384::new();
308        hasher.update(key);
309        hasher.finalize().into()
310    }
311}
312
313fn append_auth(data_len: usize, buffer: &mut [u8]) {
314    let mut total_len = data_len;
315    while total_len < buffer.len() {
316        let block_size = ZEROES.len().min(buffer.len() - total_len);
317        buffer[total_len..total_len + block_size].copy_from_slice(&ZEROES[..block_size]);
318        total_len += block_size;
319    }
320}
321
322fn encrypt(aez: &Aez, nonce: &[u8], ad: &[&[u8]], tau: u32, buffer: &mut [u8]) {
323    // We treat tau as bytes, but according to the spec, tau is actually in bits.
324    let tau_block = Block::from_int(tau as u128 * 8);
325    let tau_bytes = tau_block.bytes();
326    let mut tweaks_vec;
327    // We optimize for the common case of having no associated data, or having one item of
328    // associated data (which is all the reference implementation supports anyway). If there's more
329    // associated data, we cave in and allocate a vec.
330    let tweaks = match ad.len() {
331        0 => &[&tau_bytes, nonce] as &[&[u8]],
332        1 => &[&tau_bytes, nonce, ad[0]],
333        _ => {
334            tweaks_vec = vec![&tau_bytes, nonce];
335            tweaks_vec.extend(ad);
336            &tweaks_vec
337        }
338    };
339    assert!(buffer.len() >= tau as usize);
340    if buffer.len() == tau as usize {
341        // As aez_prf only xor's the input in, we have to clear the buffer first
342        buffer.fill(0);
343        aez_prf(aez, &tweaks, buffer);
344    } else {
345        encipher(aez, &tweaks, buffer);
346    }
347}
348
349fn decrypt<'a>(
350    aez: &Aez,
351    nonce: &[u8],
352    ad: &[&[u8]],
353    tau: u32,
354    ciphertext: &'a mut [u8],
355) -> Option<&'a [u8]> {
356    if ciphertext.len() < tau as usize {
357        return None;
358    }
359
360    let tau_block = Block::from_int(tau * 8);
361    let tau_bytes = tau_block.bytes();
362    let mut tweaks = vec![&tau_bytes, nonce];
363    tweaks.extend(ad);
364
365    if ciphertext.len() == tau as usize {
366        aez_prf(aez, &tweaks, ciphertext);
367        if is_zeroes(&ciphertext) {
368            return Some(&[]);
369        } else {
370            return None;
371        }
372    }
373
374    decipher(aez, &tweaks, ciphertext);
375    let (m, auth) = ciphertext.split_at(ciphertext.len() - tau as usize);
376    assert!(auth.len() == tau as usize);
377
378    if is_zeroes(&auth) { Some(m) } else { None }
379}
380
381fn is_zeroes(data: &[u8]) -> bool {
382    let comparator = if data.len() <= ZEROES.len() {
383        &ZEROES[..data.len()]
384    } else {
385        // We should find a way to do this without allocating a separate buffer full of zeroes, but
386        // I don't want to hand-roll my constant-time-is-zeroes yet.
387        &vec![0; data.len()]
388    };
389    constant_time_eq(data, comparator)
390}
391
392fn encipher(aez: &Aez, tweaks: Tweak, message: &mut [u8]) {
393    if message.len() < 256 / 8 {
394        cipher_aez_tiny(Mode::Encipher, aez, tweaks, message)
395    } else {
396        cipher_aez_core(Mode::Encipher, aez, tweaks, message)
397    }
398}
399
400fn decipher(aez: &Aez, tweaks: Tweak, buffer: &mut [u8]) {
401    if buffer.len() < 256 / 8 {
402        cipher_aez_tiny(Mode::Decipher, aez, tweaks, buffer);
403    } else {
404        cipher_aez_core(Mode::Decipher, aez, tweaks, buffer);
405    }
406}
407
408fn cipher_aez_tiny(mode: Mode, aez: &Aez, tweaks: Tweak, message: &mut [u8]) {
409    let mu = message.len() * 8;
410    assert!(mu < 256);
411    let n = mu / 2;
412    let delta = aez_hash(aez, tweaks);
413    let round_count = match mu {
414        8 => 24u32,
415        16 => 16,
416        _ if mu < 128 => 10,
417        _ => 8,
418    };
419
420    if mode == Mode::Decipher && mu < 128 {
421        let mut c = Block::from_slice(message);
422        c = c ^ (e(0, 3, aez, delta ^ (c | Block::one())) & Block::one());
423        message.copy_from_slice(&c.bytes()[..mu / 8]);
424    }
425
426    let (mut left, mut right);
427    // We might end up having to split at a nibble, so manually adjust for that
428    if n % 8 == 0 {
429        left = Block::from_slice(&message[..n / 8]);
430        right = Block::from_slice(&message[n / 8..]);
431    } else {
432        assert!(n % 8 == 4);
433        left = Block::from_slice(&message[..n / 8 + 1]).clip(n);
434        right = Block::from_slice(&message[n / 8..]) << 4;
435    };
436
437    let i = if mu >= 128 { 6 } else { 7 };
438
439    if mode == Mode::Encipher {
440        for j in 0..round_count {
441            let right_ = (left ^ e(0, i, aez, delta ^ right.pad(n) ^ Block::from_int(j))).clip(n);
442            (left, right) = (right, right_);
443        }
444    } else {
445        for j in (0..round_count).rev() {
446            let right_ = (left ^ e(0, i, aez, delta ^ right.pad(n) ^ Block::from_int(j))).clip(n);
447            (left, right) = (right, right_);
448        }
449    }
450
451    if n % 8 == 0 {
452        message[..n / 8].copy_from_slice(&right.bytes()[..n / 8]);
453        message[n / 8..].copy_from_slice(&left.bytes()[..n / 8]);
454    } else {
455        let mut index = n / 8;
456        message[..index + 1].copy_from_slice(&right.bytes()[..index + 1]);
457        for byte in &left.bytes()[..n / 8 + 1] {
458            message[index] |= byte >> 4;
459            if index < message.len() - 1 {
460                message[index + 1] = (byte & 0x0f) << 4;
461            }
462            index += 1;
463        }
464    }
465
466    if mode == Mode::Encipher && mu < 128 {
467        let mut c = Block::from_slice(&message);
468        c = c ^ (e(0, 3, aez, delta ^ (c | Block::one())) & Block::one());
469        message.copy_from_slice(&c.bytes()[..mu / 8]);
470    }
471}
472
473fn cipher_aez_core(mode: Mode, aez: &Aez, tweaks: Tweak, message: &mut [u8]) {
474    assert!(message.len() >= 32);
475    let delta = aez_hash(aez, tweaks);
476    let mut blocks = BlockAccessor::new(message);
477    let (m_u, m_v, m_x, m_y, d) = (
478        blocks.m_u(),
479        blocks.m_v(),
480        blocks.m_x(),
481        blocks.m_y(),
482        blocks.m_uv_len(),
483    );
484    let len_v = d.saturating_sub(128);
485
486    let mut x = Block::null();
487    let mut e1_eval = E::new(1, 0, aez);
488    let e0_eval = E::new(0, 0, aez);
489
490    for (raw_mi, raw_mi_) in blocks.pairs_mut() {
491        e1_eval.advance();
492        let mi = Block::from(*raw_mi);
493        let mi_ = Block::from(*raw_mi_);
494        let wi = mi ^ e1_eval.eval(mi_);
495        let xi = mi_ ^ e0_eval.eval(wi);
496
497        wi.write_to(raw_mi);
498        xi.write_to(raw_mi_);
499
500        x = x ^ xi;
501    }
502
503    match d {
504        0 => (),
505        _ if d <= 127 => {
506            x = x ^ e(0, 4, aez, m_u.pad(d.into()));
507        }
508        _ => {
509            x = x ^ e(0, 4, aez, m_u);
510            x = x ^ e(0, 5, aez, m_v.pad(len_v.into()));
511        }
512    }
513
514    let (s_x, s_y);
515    match mode {
516        Mode::Encipher => {
517            s_x = m_x ^ delta ^ x ^ e(0, 1, aez, m_y);
518            s_y = m_y ^ e(-1, 1, aez, s_x);
519        }
520        Mode::Decipher => {
521            s_x = m_x ^ delta ^ x ^ e(0, 2, aez, m_y);
522            s_y = m_y ^ e(-1, 2, aez, s_x);
523        }
524    }
525    let s = s_x ^ s_y;
526
527    let mut y = Block::null();
528    let mut e2_eval = E::new(2, 0, aez);
529    let mut e1_eval = E::new(1, 0, aez);
530    for (raw_wi, raw_xi) in blocks.pairs_mut() {
531        e2_eval.advance();
532        e1_eval.advance();
533        let wi = Block::from(*raw_wi);
534        let xi = Block::from(*raw_xi);
535        let s_ = e2_eval.eval(s);
536        let yi = wi ^ s_;
537        let zi = xi ^ s_;
538        let ci_ = yi ^ e0_eval.eval(zi);
539        let ci = zi ^ e1_eval.eval(ci_);
540
541        ci.write_to(raw_wi);
542        ci_.write_to(raw_xi);
543
544        y = y ^ yi;
545    }
546
547    let mut c_u = Block::default();
548    let mut c_v = Block::default();
549
550    match d {
551        0 => (),
552        _ if d <= 127 => {
553            c_u = (m_u ^ e(-1, 4, aez, s)).clip(d.into());
554            y = y ^ e(0, 4, aez, c_u.pad(d.into()));
555        }
556        _ => {
557            c_u = m_u ^ e(-1, 4, aez, s);
558            c_v = (m_v ^ e(-1, 5, aez, s)).clip(len_v.into());
559            y = y ^ e(0, 4, aez, c_u);
560            y = y ^ e(0, 5, aez, c_v.pad(len_v.into()));
561        }
562    }
563
564    let (c_x, c_y);
565    match mode {
566        Mode::Encipher => {
567            c_y = s_x ^ e(-1, 2, aez, s_y);
568            c_x = s_y ^ delta ^ y ^ e(0, 2, aez, c_y);
569        }
570        Mode::Decipher => {
571            c_y = s_x ^ e(-1, 1, aez, s_y);
572            c_x = s_y ^ delta ^ y ^ e(0, 1, aez, c_y);
573        }
574    }
575
576    blocks.set_m_u(c_u);
577    blocks.set_m_v(c_v);
578    blocks.set_m_x(c_x);
579    blocks.set_m_y(c_y);
580}
581
582fn pad_to_blocks(value: &[u8]) -> Vec<Block> {
583    let mut blocks = Vec::new();
584    for chunk in value.chunks(16) {
585        if chunk.len() == 16 {
586            blocks.push(Block::from_slice(chunk));
587        } else {
588            blocks.push(Block::from_slice(chunk).pad(chunk.len() * 8));
589        }
590    }
591    blocks
592}
593
594fn aez_hash(aez: &Aez, tweaks: Tweak) -> Block {
595    let mut hash = Block::null();
596    for (i, tweak) in tweaks.iter().enumerate() {
597        // Adjust for zero-based vs one-based indexing
598        let j = i + 2 + 1;
599        let mut ej = E::new(j.try_into().unwrap(), 0, aez);
600        // This is somewhat implicit in the AEZ spec, but basically for an empty string we still
601        // set l = 1 and then xor E_K^{j, 0}(10*). We could modify the last if branch to cover this
602        // as well, but then we need to fiddle with getting an empty chunk from an empty iterator.
603        if tweak.is_empty() {
604            hash = hash ^ ej.eval(Block::one());
605        } else if tweak.len() % 16 == 0 {
606            for chunk in tweak.chunks(16) {
607                ej.advance();
608                hash = hash ^ ej.eval(Block::from_slice(chunk));
609            }
610        } else {
611            let blocks = pad_to_blocks(tweak);
612            for (l, chunk) in blocks.iter().enumerate() {
613                ej.advance();
614                if l == blocks.len() - 1 {
615                    hash = hash ^ e(j.try_into().unwrap(), 0, aez, *chunk);
616                } else {
617                    hash = hash ^ ej.eval(*chunk);
618                }
619            }
620        }
621    }
622    hash
623}
624
625/// XOR's the result of aez_prf into the given buffer
626fn aez_prf(aez: &Aez, tweaks: Tweak, buffer: &mut [u8]) {
627    let mut index = 0u128;
628    let delta = aez_hash(aez, tweaks);
629    for chunk in buffer.chunks_exact_mut(16) {
630        let chunk: &mut [u8; 16] = chunk.try_into().unwrap();
631        let block = e(-1, 3, aez, delta ^ Block::from_int(index));
632        (block ^ Block::from(*chunk)).write_to(chunk);
633        index += 1;
634    }
635    let suffix_start = buffer.len() - buffer.len() % 16;
636    let chunk = &mut buffer[suffix_start..];
637    let block = e(-1, 3, aez, delta ^ Block::from_int(index));
638    for (a, b) in chunk.iter_mut().zip(block.bytes().iter()) {
639        *a ^= *b;
640    }
641}
642
643/// Represents a computation of E_K^{j,i}.
644///
645/// As we usually need multiple values with a fixed j and ascending i, this struct saves the
646/// temporary values and makes it much faster to compute E_K^{j, i+1}, E_K^{j, i+2}, ...
647struct E<'a> {
648    aez: &'a Aez,
649    i: u32,
650    kj_t_j: Block,
651    ki_p_i: Block,
652}
653
654impl<'a> E<'a> {
655    /// Create a new "suspended" computation of E_K^{j,i}.
656    fn new(j: i32, i: u32, aez: &'a Aez) -> Self {
657        assert!(j >= 0);
658        let j: u32 = j.try_into().expect("j was negative");
659        let exponent = if i % 8 == 0 { i / 8 } else { i / 8 + 1 };
660        E {
661            aez,
662            i,
663            kj_t_j: aez.key_j * j,
664            ki_p_i: aez.key_i.exp(exponent),
665        }
666    }
667
668    /// Complete this computation to evaluate E_K^{j,i}(block).
669    fn eval(&self, block: Block) -> Block {
670        let delta = self.kj_t_j ^ self.ki_p_i ^ self.aez.key_l_multiples[self.i as usize % 8];
671        self.aez.aes.aes4(block ^ delta)
672    }
673
674    /// Advance this computation by going from i to i+1.
675    ///
676    /// Afterwards, this computation will represent E_K^{j, i+1}
677    fn advance(&mut self) {
678        // We need to advance ki_p_i if exponent = old_exponent + 1
679        // This happens exactly when the old exponent was just a multiple of 8, because the
680        // next exponent is then not a multiple anymore and will be rounded *up*.
681        if self.i % 8 == 0 {
682            self.ki_p_i = self.ki_p_i * 2
683        };
684        self.i += 1;
685    }
686}
687
688/// Shorthand to get E_K^{j,i}(block)
689fn e(j: i32, i: u32, aez: &Aez, block: Block) -> Block {
690    if j == -1 {
691        let delta = if i < 8 {
692            aez.key_l_multiples[i as usize]
693        } else {
694            aez.key_l * i
695        };
696        aez.aes.aes10(block ^ delta)
697    } else {
698        E::new(j, i, aez).eval(block)
699    }
700}
701
702fn split_key(key: &Key) -> (Block, Block, Block) {
703    (
704        Block::from_slice(&key[..16]),
705        Block::from_slice(&key[16..32]),
706        Block::from_slice(&key[32..]),
707    )
708}
709
710#[cfg(test)]
711mod test {
712    use super::*;
713
714    static PLAIN: &[u8] = include_bytes!("payload.txt");
715
716    #[test]
717    fn test_extract() {
718        for (a, b) in testvectors::EXTRACT_VECTORS {
719            let a = hex::decode(a).unwrap();
720            let b = hex::decode(b).unwrap();
721            assert_eq!(extract(&a), b.as_slice());
722        }
723    }
724
725    #[test]
726    fn test_e() {
727        for (k, j, i, a, b) in testvectors::E_VECTORS {
728            let name = format!("e({j}, {i}, {k}, {a})");
729            let k = hex::decode(k).unwrap();
730            let aez = Aez::new(k.as_slice());
731            let a = hex::decode(a).unwrap();
732            let a = Block::from_slice(&a);
733            let b = hex::decode(b).unwrap();
734            assert_eq!(&e(*j, *i, &aez, a).bytes(), b.as_slice(), "{name}");
735        }
736    }
737
738    #[test]
739    fn test_aez_hash() {
740        for (k, tau, tw, v) in testvectors::HASH_VECTORS {
741            let name = format!("aez_hash({k}, {tau}, {tw:?})");
742            let k = hex::decode(k).unwrap();
743            let aez = Aez::new(k.as_slice());
744            let v = hex::decode(v).unwrap();
745
746            let mut tweaks = vec![Vec::from(Block::from_int(*tau).bytes())];
747            for t in *tw {
748                tweaks.push(hex::decode(t).unwrap());
749            }
750            let tweaks = tweaks.iter().map(Vec::as_slice).collect::<Vec<_>>();
751
752            assert_eq!(&aez_hash(&aez, &tweaks).bytes(), v.as_slice(), "{name}");
753        }
754    }
755
756    fn vec_encrypt(key: &Key, nonce: &[u8], ad: &[&[u8]], tau: u32, message: &[u8]) -> Vec<u8> {
757        let aez = Aez::new(key);
758        let mut v = vec![0; message.len() + tau as usize];
759        v[..message.len()].copy_from_slice(message);
760        encrypt(&aez, nonce, ad, tau, &mut v);
761        v
762    }
763
764    fn vec_decrypt(
765        key: &Key,
766        nonce: &[u8],
767        ad: &[&[u8]],
768        tau: u32,
769        ciphertext: &[u8],
770    ) -> Option<Vec<u8>> {
771        let aez = Aez::new(key);
772        let mut v = Vec::from(ciphertext);
773        let len = match decrypt(&aez, nonce, ad, tau, &mut v) {
774            None => return None,
775            Some(m) => m.len(),
776        };
777        v.truncate(len);
778        Some(v)
779    }
780
781    #[test]
782    fn test_encrypt() {
783        let mut failed = 0;
784        let mut succ = 0;
785        for (k, n, ads, tau, m, c) in testvectors::ENCRYPT_VECTORS {
786            let name = format!("encrypt({k}, {n}, {ads:?}, {tau}, {m})");
787            let k = hex::decode(k).unwrap();
788            let k = k.as_slice().try_into().unwrap();
789            let n = hex::decode(n).unwrap();
790
791            let mut ad = Vec::new();
792            for i in *ads {
793                ad.push(hex::decode(i).unwrap());
794            }
795            let ad = ad.iter().map(Vec::as_slice).collect::<Vec<_>>();
796
797            let m = hex::decode(m).unwrap();
798            let c = hex::decode(c).unwrap();
799
800            if &vec_encrypt(&k, &n, &ad, *tau, &m) == &c {
801                println!("+ {name}");
802                succ += 1;
803            } else {
804                println!("- {name}");
805                failed += 1;
806            }
807        }
808        println!("{succ} succeeded, {failed} failed");
809        assert_eq!(failed, 0);
810    }
811
812    #[test]
813    fn test_decrypt() {
814        let mut failed = 0;
815        let mut succ = 0;
816        for (k, n, ads, tau, m, c) in testvectors::ENCRYPT_VECTORS {
817            let name = format!("decrypt({k}, {n}, {ads:?}, {tau}, {c})");
818            let k = hex::decode(k).unwrap();
819            let k = k.as_slice().try_into().unwrap();
820            let n = hex::decode(n).unwrap();
821
822            let mut ad = Vec::new();
823            for i in *ads {
824                ad.push(hex::decode(i).unwrap());
825            }
826            let ad = ad.iter().map(Vec::as_slice).collect::<Vec<_>>();
827
828            let m = hex::decode(m).unwrap();
829            let c = hex::decode(c).unwrap();
830
831            if vec_decrypt(&k, &n, &ad, *tau, &c) == Some(m) {
832                println!("+ {name}");
833                succ += 1;
834            } else {
835                println!("- {name}");
836                failed += 1;
837            }
838        }
839        println!("{succ} succeeded, {failed} failed");
840        assert_eq!(failed, 0);
841    }
842
843    #[test]
844    fn test_encrypt_decrypt() {
845        let aez = Aez::new(b"foobar");
846        let cipher = aez.encrypt(&[0], &[b"foobar"], 16, b"hi");
847        let plain = aez.decrypt(&[0], &[b"foobar"], 16, &cipher).unwrap();
848        assert_eq!(plain, b"hi");
849    }
850
851    #[test]
852    fn test_encrypt_decrypt_inplace() {
853        let mut buffer = Vec::from(PLAIN);
854        let aez = Aez::new(b"foobar");
855        aez.encrypt_inplace(&[0], &[], 16, &mut buffer);
856        let plain = aez.decrypt_inplace(&[0], &[], 16, &mut buffer).unwrap();
857        assert_eq!(plain, &PLAIN[..PLAIN.len() - 16]);
858    }
859
860    #[test]
861    fn test_encrypt_decrypt_buffer() {
862        let mut output = vec![0; PLAIN.len() + 16];
863        let aez = Aez::new(b"foobar");
864        aez.encrypt_buffer(&[0], &[], PLAIN, &mut output);
865        let plain = aez.decrypt_inplace(&[0], &[], 16, &mut output).unwrap();
866        assert_eq!(plain, PLAIN);
867    }
868
869    #[test]
870    fn test_encrypt_decrypt_long() {
871        let message = b"ene mene miste es rappelt in der kiste ene mene meck und du bist weg";
872        let aez = Aez::new(b"foobar");
873        let cipher = aez.encrypt(&[0], &[b"foobar"], 16, message);
874        let plain = aez.decrypt(&[0], &[b"foobar"], 16, &cipher).unwrap();
875        assert_eq!(plain, message);
876    }
877
878    #[test]
879    fn test_encrypt_decrypt_empty() {
880        let aez = Aez::new(b"jimbo");
881        let hash = aez.encrypt(&[0], &[b"foobar"], 16, b"");
882
883        assert!(aez.decrypt(&[0], &[b"foobar"], 16, &hash).is_some());
884        assert!(aez.decrypt(&[0], &[b"boofar"], 16, &hash).is_none());
885    }
886
887    #[test]
888    fn test_fuzzed_1() {
889        let aez = Aez::new(b"");
890        aez.encrypt(b"", &[], 2220241, &[0]);
891    }
892
893    #[test]
894    fn test_fuzzed_2() {
895        let aez = Aez::new(b"");
896        aez.encrypt(b"", &[], 673261693, &[]);
897    }
898
899    #[test]
900    fn test_fuzzed_3() {
901        // AEZ crashes if given an empty message and empty tau
902        let aez = Aez::new(&[0, 110, 109, 0]);
903        let value = aez.encrypt(&[0], &[], 0, &[]);
904        assert_eq!(&value, &[]);
905    }
906}