crylib 0.2.0

a collection of cryptographic functions
Documentation
//! [`Gallois/Counter Mode`] for AES
//!
//! This module implements Gallois/counter mode for AES.
//! It provides efficient encryption/decryption and authentication
//! for data of arbitrary length.
//!
//! # Examples
//!
//! ```
//! use crylib::aead::gcm::Gcm;
//! use crylib::aead::gcm::Aes128;
//! use crylib::aead::Aead;
//!
//! let plain_text = "Top secret message".as_bytes();
//! let additional_data = "Public information".as_bytes();
//!
//! let key = [
//!     0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83,
//!     0x08,
//! ];
//!
//! let init_vector = [ 0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88,];
//!
//! let cipher = Gcm::<Aes128>::new(key);
//!
//! let mut encrypted_message = vec![0u8; plain_text.len()];
//! let generated_tag =
//!     cipher.encrypt(&mut encrypted_message, &plain_text, &additional_data, &init_vector);
//!
//! let tag = [
//!     0x4, 0xca, 0x8f, 0xa2, 0x89, 0xa6, 0xac, 0x83, 0x84, 0x68, 0x71, 0x46, 0x2a, 0xda, 0x12,
//!     0x67,
//! ];
//!
//! let cipher_text = [
//!     0xcf, 0xdd, 0x5c, 0xc7, 0xaa, 0x96, 0x11, 0xb3, 0x8b, 0x5f, 0x8, 0x1f, 0x4e, 0x56, 0x81,
//!     0x67, 0x2, 0x68,
//! ];
//!
//! assert_eq!(encrypted_message, cipher_text);
//! assert_eq!(generated_tag, tag);
//!
//! let mut unencrypted_message = vec![0u8; encrypted_message.len()];
//! let unencrypted_message = cipher.decrypt( &mut
//!     unencrypted_message,
//!     &encrypted_message, additional_data, &init_vector, &generated_tag,
//!     ).expect("Our message has been modified!");
//!
//! assert_eq!(plain_text, "Top secret message".as_bytes());
//! ```
//!
//! [`Gallois/Counter Mode`]: https://en.wikipedia.org/wiki/Galois/Counter_Mode
mod aes;
use core::{marker::PhantomPinned, u128};

pub use aes::*;

use crate::aead::{BadData, IV_SIZE, TAG_SIZE};

use super::Aead;
const R: u128 = 0xe1 << 120;

/// A type that allows for authenticated
/// encryption and decryption in GCM via AES.
///
/// See [`Gcm`]'s implementations for examples.
pub struct Gcm<C: aes::AesCipher> {
    cipher: C,
    h: u128,
    _pin: PhantomPinned,
}

impl<C: aes::AesCipher> Aead for Gcm<C> {
    fn encrypt_inline(
        &self,
        msg: &mut [u8],
        add_data: &[u8],
        iv: &[u8; IV_SIZE],
    ) -> [u8; TAG_SIZE] {
        let counter = {
            let mut counter = [0; aes::BLOCK_SIZE];
            counter[aes::BLOCK_SIZE - 1] = 1;
            counter[..iv.len()].copy_from_slice(iv);
            counter
        };
        self.xor_bit_stream(msg, u128::from_be_bytes(counter));

        self.g_hash(msg, add_data, &counter)
    }

    fn decrypt_inline(
        &self,
        msg: &mut [u8],
        add_data: &[u8],
        iv: &[u8; IV_SIZE],
        tag: &[u8; TAG_SIZE],
    ) -> Result<(), BadData> {
        let counter = {
            let mut counter = [0; aes::BLOCK_SIZE];
            counter[aes::BLOCK_SIZE - 1] = 1;
            counter[..iv.len()].copy_from_slice(iv);
            counter
        };
        if &self.g_hash(msg, add_data, &counter) != tag {
            return Err(BadData);
        }
        self.xor_bit_stream(msg, u128::from_be_bytes(counter));
        Ok(())
    }
}

impl<C: aes::AesCipher> Gcm<C> {
    /// Construct a new [`Gcm`] cipher.
    pub fn new(key: C::Key) -> Self {
        let cipher = C::new(key);
        let mut h = [0u8; aes::BLOCK_SIZE];
        cipher.encrypt_inline(&mut h);

        Self {
            cipher,
            h: u128::from_be_bytes(h),
            _pin: PhantomPinned,
        }
    }

    /// Encrypts or decrypts `data` in counter mode.
    ///
    /// Because XOR is its own inverse,
    /// the same operation can be used for encryption and decryption
    ///
    /// This is a linear operation.
    ///
    /// This process can be parallel-ized,
    /// but that has not been implemented yet.
    fn xor_bit_stream(&self, data: &mut [u8], iv: u128) {
        for (counter, block) in data.chunks_mut(aes::BLOCK_SIZE).enumerate() {
            let mut stream = (iv + 1 + counter as u128).to_be_bytes();
            self.cipher.encrypt_inline(&mut stream);

            for (data_byte, stream_byte) in block.iter_mut().zip(stream) {
                *data_byte ^= stream_byte;
            }
        }
    }

    fn g_hash(
        &self,
        cipher_text: &[u8],
        add_data: &[u8],
        counter: &[u8; aes::BLOCK_SIZE],
    ) -> [u8; aes::BLOCK_SIZE] {
        let mut tag = 0;

        // TODO: use `array_chunks` once stabilized
        let chunks = add_data.chunks_exact(aes::BLOCK_SIZE);
        let remainder = chunks.remainder();

        for block in chunks {
            add_block(&mut tag, block.try_into().unwrap(), self.h);
        }

        let last_block = {
            let mut last_block = [0; aes::BLOCK_SIZE];
            last_block[..remainder.len()].copy_from_slice(remainder);
            last_block
        };

        add_block(&mut tag, last_block, self.h);

        // TODO: use `array_chunks` once stabilized
        let blocks = cipher_text.chunks_exact(aes::BLOCK_SIZE);
        let remainder = blocks.remainder();

        for block in blocks {
            add_block(&mut tag, block.try_into().unwrap(), self.h);
        }

        let last_block = {
            let mut last_block = [0; aes::BLOCK_SIZE];
            last_block[..remainder.len()].copy_from_slice(remainder);
            last_block
        };

        add_block(&mut tag, last_block, self.h);

        tag ^= ((add_data.len() as u128 * 8) << 64) | cipher_text.len() as u128 * 8;
        tag = gf_2to128_mul(tag, self.h);

        let encrypted_iv = u128::from_be_bytes(self.cipher.encrypt(counter));

        tag ^= encrypted_iv;
        tag.to_be_bytes()
    }
}

/// Multiplication in GF(2^128)
fn gf_2to128_mul(a: u128, b: u128) -> u128 {
    let mut product = 0;
    let mut temp = a;
    for i in (0..128).rev() {
        let mask = ((b >> i) & 1).wrapping_neg();
        product ^= temp & mask;

        let mask = (temp & 1).wrapping_neg();
        temp >>= 1;
        temp ^= R & mask;
    }
    product
}

/// A helper function for g_hash()
#[inline]
fn add_block(tag: &mut u128, block: [u8; aes::BLOCK_SIZE], h: u128) {
    *tag ^= u128::from_be_bytes(block);
    *tag = gf_2to128_mul(*tag, h);
}

#[cfg(test)]
mod tests {
    use super::aes::Aes128;
    use super::Aead;
    use super::Gcm;

    #[test]
    fn ctr_mode() {
        let key = [
            0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
            0x83, 0x08,
        ];
        let mut plain_text = [
            0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5,
            0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d,
            0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf,
            0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
            0xba, 0x63, 0x7b, 0x39,
        ];
        let counter = [
            0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88, 0x00, 0x00,
            0x00, 0x01,
        ];
        let cipher_text = [
            0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0,
            0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23,
            0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f,
            0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
            0x3d, 0x58, 0xe0, 0x91,
        ];
        let cipher = Gcm::<Aes128>::new(key);
        cipher.xor_bit_stream(&mut plain_text, u128::from_be_bytes(counter));
        assert_eq!(plain_text, cipher_text);
    }

    #[test]
    fn g_hash() {
        let key = [
            0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
            0x83, 0x08,
        ];
        let cipher = Gcm::<Aes128>::new(key);

        let counter = [
            0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88, 0x00, 0x00,
            0x00, 0x01,
        ];

        let cipher_text = [
            0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0,
            0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23,
            0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f,
            0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
            0x3d, 0x58, 0xe0, 0x91,
        ];
        let tag = [
            0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb, 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12,
            0x1a, 0x47,
        ];

        let add_data = [
            0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad,
            0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2,
        ];

        let h = 0xb83b533708bf535d0aa6e52980d53b78;
        assert_eq!(cipher.h, h);

        assert_eq!(tag, cipher.g_hash(&cipher_text, &add_data, &counter));
    }

    #[test]
    fn mul() {
        let a = 0x66e94bd4ef8a2c3b884cfa59ca342b2e;
        let b = 0x0388dace60b6a392f328c2b971b2fe78;
        let product = 0x5e2ec746917062882c85b0685353deb7;
        assert_eq!(super::gf_2to128_mul(a, b), product);
    }

    #[test]
    fn encrypt() {
        let key = [
            0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
            0x83, 0x08,
        ];
        let cipher = Gcm::<Aes128>::new(key);

        let init_vector = [
            0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88,
        ];

        let mut plain_text = [
            0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5,
            0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d,
            0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf,
            0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
            0xba, 0x63, 0x7b, 0x39,
        ];
        let tag = [
            0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb, 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12,
            0x1a, 0x47,
        ];

        let add_data = [
            0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad,
            0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2,
        ];
        let cipher_text = [
            0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0,
            0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23,
            0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f,
            0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
            0x3d, 0x58, 0xe0, 0x91,
        ];
        assert_eq!(
            tag,
            cipher.encrypt_inline(&mut plain_text, &add_data, &init_vector)
        );
        assert_eq!(plain_text, cipher_text);
    }

    #[test]
    fn decrypt() {
        let key = [
            0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c, 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30,
            0x83, 0x08,
        ];
        let cipher = Gcm::<Aes128>::new(key);

        let init_vector = [
            0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad, 0xde, 0xca, 0xf8, 0x88,
        ];

        let plain_text = [
            0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5,
            0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d,
            0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf,
            0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
            0xba, 0x63, 0x7b, 0x39,
        ];
        let tag = [
            0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb, 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12,
            0x1a, 0x47,
        ];

        let add_data = [
            0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad,
            0xbe, 0xef, 0xab, 0xad, 0xda, 0xd2,
        ];
        let mut cipher_text = [
            0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0,
            0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23,
            0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f,
            0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
            0x3d, 0x58, 0xe0, 0x91,
        ];
        cipher
            .decrypt_inline(&mut cipher_text, &add_data, &init_vector, &tag)
            .unwrap();
        assert_eq!(plain_text, cipher_text);
    }
}