Skip to content

Commit

Permalink
Add AES-GCM(includes GHASH) (#157)
Browse files Browse the repository at this point in the history
* GHASH implementation init

* Add comments in GHASH

* Use Polynomial library for GHASH multiplication.

* Implement GCM,some changes to CTR and AES

* update comments

* Add tests in ctr

* fix formatting

* Add GCM to README

* Update GCM README

* Update GCM readme and figure

* Update readme, more detail and desc

* Readme missed hash key

* Fix lint
  • Loading branch information
mrdaybird authored Sep 23, 2024
1 parent b426f06 commit e4387fe
Show file tree
Hide file tree
Showing 9 changed files with 1,000 additions and 25 deletions.
1 change: 0 additions & 1 deletion src/encryption/symmetric/aes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ where [(); N / 8]:
.try_into()
.unwrap(),
);
assert!(state != State::default(), "State is not instantiated");

// Round 0 - add round key
Self::add_round_key(&mut state, round_keys.next().unwrap());
Expand Down
62 changes: 62 additions & 0 deletions src/encryption/symmetric/modes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,68 @@ IV4["IV||2"]-->Fk2[F_k]-->xor2["⨁"]-->c2
m2-->xor2
```

## GCM: Galois/Counter Mode

GCM is a block cipher mode of operation that provides both confidentiality and authentication.
To provide confidentiality, it uses Counter(CTR) mode for encryption and decryption.
To provide authentication, it uses a universal hash function, GHASH.
Authentication is provided not only for confidential data but also other associated data.

In this section, we will give an informal overview of GCM mode for 128-bit block cipher.
*To see the formal definition of the operations of GCM, I recommend reading the original paper. [The Galois/Counter Mode of Operation (GCM)](https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf)*

The two operations of GCM are authenticated encryption and authenticated decryption.

Here is a figure that gives a complete overview of the authenticated encryption in GCM.

In the figure, we have taken
- the size of plaintext is `3 * 128-bit = 384 bits or 48 bytes`,
- Additionally Authenticated Data(AAD) is of `2 * 128-bit = 248 bits or 32 bytes`.

![GCM](./figure_full_gcm.svg)
*Note: The yellow diamonds represent functions/algorithms, the small rectangle with a blue outline represents 128-bit blocks.*
Also,
- *Enc(K)*: The encryption operation of the cipher used, for example AES, under the key K.
- *incr*: The increment function, which treats the rightmost 32-bit of the block as an unsigned integer and increments it by 1.

If you look at the figure carefully, you will notice that the GCM mode is composed of two main parts:
- Encryption: This part is the same as the CTR mode of operation, with minor changes to the counter block.
- Authentication: In this part, we generate an authentication tag for the ciphertext and some additional data, which we refer to as Additionally Authenticated Data(AAD).

The counter block is the same as in CTR mode. In general, it can be thought of as a 96-bit nonce value followed by a 32-bit counter value.

The tag is generated by XOR of:
1. Hash of ciphertext and AAD, using GHASH algorithm
2. Encryption of Counter block 0.

### GHASH

The GHASH algorithm can be viewed as a series of `ADD and MULTIPLY` in $GF(2^{128})$. Mathematically put the basic operation of GHASH is,

$$
X_{i} =
\begin{cases}
0 & \quad i = 0 \\
( X_{i-1} \oplus B_{i} ) * H & \quad \text{otherwise}
\end{cases}
$$

$B_{i}$ represents blocks of AAD followed by blocks of ciphertext followed by a special length block.
The length block consists of 64-bit lengths(in bits) of AAD and ciphertext.
$H$ called the hash key, is the encryption of 128-bit of zeros using the chosen cipher and key.

The interesting thing to note here is that the multiplication($*$) and addition($\oplus$) are operations of the Galois(finite) field of order $2^{128}$.
A brief summary of finite field arithmetic,
- The elements of the field are represented as polynomials. Each bit of the 128-bit block represents coefficients of a polynomial of degree strictly less than 128.
- Addition in a finite field is equivalent to bitwise XOR.
- Multiplication in a finite field is the multiplication of corresponding polynomials modulo an irreducible reducing polynomial.

In GCM the reducing polynomial is $f = 1 + x + x^2 + x^7 + x^{128}$

If you want to read about Finite Field, the Wikipedia article on [Finite Field Arithemtic](https://en.wikipedia.org/wiki/Galois/Counter_Mode) is pretty good!

The authenticated decryption operation is identical to authenticated encryption, except the tag is generated before the decryption.

## Next Steps
Implement more modes, and subsequent attacks/vulnerabilities:
- [ ] CFB
Expand Down
175 changes: 151 additions & 24 deletions src/encryption/symmetric/modes/ctr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@

use crate::encryption::symmetric::{counter::Counter, BlockCipher};

/// [`BlockCipher`] counter mode of operation
pub struct CTR<C: BlockCipher>
where [(); C::BLOCK_SIZE / 2]: {
nonce: [u8; C::BLOCK_SIZE / 2],
/// [`BlockCipher`] counter mode of operation with two parameters:
/// - `C`, a cipher that implements the `BlockCipher` trait.
/// - `M`, a usize const that indicates the size of counter in bytes.
pub struct CTR<C: BlockCipher, const M: usize>
where [(); C::BLOCK_SIZE - M]: {
nonce: [u8; C::BLOCK_SIZE - M],
}

impl<C: BlockCipher> CTR<C>
where [(); C::BLOCK_SIZE / 2]:
impl<C: BlockCipher, const M: usize> CTR<C, M>
where [(); C::BLOCK_SIZE - M]:
{
/// Create a CTR mode of operation object
/// # Arguments
/// - `nonce`: *Non-repeating* IV of [`BlockCipher::BLOCK_SIZE`]/2 bytes. Nonce is concatenated
/// with [`Counter`] to generate a keystream that does not repeat for a long time.
pub fn new(nonce: [u8; C::BLOCK_SIZE / 2]) -> Self { Self { nonce } }
/// - `nonce`: *Non-repeating* IV of [`BlockCipher::BLOCK_SIZE`] - M bytes. Nonce is concatenated
/// with [`Counter`], of M bytes, to generate a keystream that does not repeat for a long time.
pub fn new(nonce: [u8; C::BLOCK_SIZE - M]) -> Self { Self { nonce } }

/// Encrypt a plaintext with [`BlockCipher::Key`] and [`Counter`]
/// ## Arguments
Expand All @@ -37,27 +39,27 @@ where [(); C::BLOCK_SIZE / 2]:
/// let mut rng = thread_rng();
/// let rand_key: [u8; 16] = rng.gen();
/// let key = Key::<128>::new(rand_key);
/// let nonce: [u8; AES::<128>::BLOCK_SIZE / 2] = rng.gen();
/// let counter: Counter<{ AES::<128>::BLOCK_SIZE / 2 }> = Counter::from(0);
/// let nonce: [u8; 12] = rng.gen();
/// let counter: Counter<4> = Counter::from(0);
///
/// let ctr = CTR::<AES<128>>::new(nonce);
/// let ctr = CTR::<AES<128>, 4>::new(nonce);
/// let plaintext = b"Hello World!";
///
/// let ciphertext = ctr.encrypt(&key, &counter, plaintext).unwrap();
/// ```
pub fn encrypt(
&self,
key: &C::Key,
counter: &Counter<{ C::BLOCK_SIZE / 2 }>,
counter: &Counter<M>,
plaintext: &[u8],
) -> Result<Vec<u8>, String> {
let mut ciphertext = Vec::new();
let mut cipher_counter = *counter;

for chunk in plaintext.chunks(C::BLOCK_SIZE) {
let mut block = [0u8; C::BLOCK_SIZE];
block[..C::BLOCK_SIZE / 2].copy_from_slice(&self.nonce);
block[C::BLOCK_SIZE / 2..].copy_from_slice(&cipher_counter.0);
block[..{ C::BLOCK_SIZE - M }].copy_from_slice(&self.nonce);
block[{ C::BLOCK_SIZE - M }..].copy_from_slice(&cipher_counter.0);
cipher_counter.increment()?;

let encrypted = C::encrypt_block(key, &C::Block::from(block.to_vec()));
Expand All @@ -70,7 +72,7 @@ where [(); C::BLOCK_SIZE / 2]:
Ok(ciphertext)
}

/// Decrypt a ciphertext with counter of size [`BlockCipher::BLOCK_SIZE`]/2 bytes
/// Decrypt a ciphertext with counter of size [`BlockCipher::BLOCK_SIZE`] - M bytes
/// ## Usage
/// ```
/// #![allow(incomplete_features)]
Expand All @@ -86,10 +88,10 @@ where [(); C::BLOCK_SIZE / 2]:
/// let mut rng = thread_rng();
/// let rand_key: [u8; 16] = rng.gen();
/// let key = Key::<128>::new(rand_key);
/// let nonce: [u8; AES::<128>::BLOCK_SIZE / 2] = rng.gen();
/// let counter: Counter<{ AES::<128>::BLOCK_SIZE / 2 }> = Counter::from(0);
/// let nonce: [u8; 12] = rng.gen();
/// let counter: Counter<4> = Counter::from(0);
///
/// let ctr = CTR::<AES<128>>::new(nonce);
/// let ctr = CTR::<AES<128>, 4>::new(nonce);
/// let plaintext = b"Hello World!";
///
/// let ciphertext = ctr.encrypt(&key, &counter, plaintext).unwrap();
Expand All @@ -98,7 +100,7 @@ where [(); C::BLOCK_SIZE / 2]:
pub fn decrypt(
&self,
key: &C::Key,
counter: &Counter<{ C::BLOCK_SIZE / 2 }>,
counter: &Counter<M>,
ciphertext: &[u8],
) -> Result<Vec<u8>, String> {
self.encrypt(key, counter, ciphertext)
Expand All @@ -107,6 +109,8 @@ where [(); C::BLOCK_SIZE / 2]:

#[cfg(test)]
mod tests {
use std::{fmt::Write, num::ParseIntError};

use rand::{thread_rng, Rng};
use rstest::{fixture, rstest};

Expand All @@ -127,13 +131,13 @@ mod tests {
}

#[rstest]
fn ctr(rand_key: Key<128>) {
fn test_ctr_rand_key(rand_key: Key<128>) {
for _ in 0..10 {
let mut rng = thread_rng();
let nonce: [u8; AES::<128>::BLOCK_SIZE / 2] = rng.gen();
let counter: Counter<{ AES::<128>::BLOCK_SIZE / 2 }> = Counter::from(0);
let nonce: [u8; AES::<128>::BLOCK_SIZE - 4] = rng.gen();
let counter: Counter<4> = Counter::from(0);

let ctr = CTR::<AES<128>>::new(nonce);
let ctr = CTR::<AES<128>, 4>::new(nonce);

let plaintext = rand_message(rng.gen_range(1000..10000));
let ciphertext = ctr.encrypt(&rand_key, &counter, &plaintext).unwrap();
Expand All @@ -143,4 +147,127 @@ mod tests {
assert_eq!(plaintext, decrypted);
}
}

/// Encode bytes to hex
pub fn encode_hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &b in bytes {
write!(&mut s, "{:02x}", b).unwrap();
}
s
}

/// Decode hex to bytes
pub fn decode_hex(s: &str) -> Result<Vec<u8>, ParseIntError> {
(0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16)).collect()
}

// `test_ctr_128`, `test_ctr_192`, and 'test_ctr_256' are based on test vectors given in:
// "Recommendation for Block Cipher Modes of Operation(NIST Special Publication 800-38A)"
// Link: (https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf)

// Appendix F.5.1 and F.5.2
#[rstest]
#[case(
"2b7e151628aed2a6abf7158809cf4f3c",
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee"
)]
fn test_ctr_128(
#[case] kx: &str,
#[case] ivx: &str,
#[case] ptx: &str,
#[case] expected_ctx: &str,
) {
let k = decode_hex(kx).unwrap();
let iv = decode_hex(ivx).unwrap();
let pt = decode_hex(ptx).unwrap();

let key = Key::<128>::new(k.try_into().unwrap());
let nonce = &iv[..8];
let counter = Counter(iv[8..].try_into().unwrap());

let ctr = CTR::<AES<128>, 8>::new(nonce.try_into().unwrap());

let ct = ctr.encrypt(&key, &counter, &pt).unwrap();

let ctx = encode_hex(&ct);
assert_eq!(ctx, expected_ctx);

let _pt = ctr.decrypt(&key, &counter, &ct).unwrap();

let _ptx = encode_hex(&_pt);
assert_eq!(_ptx, ptx);
}

// Appendix F.5.3 and F.5.4
#[rstest]
#[case(
"8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b",
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050"
)]
fn test_ctr_192(
#[case] kx: &str,
#[case] ivx: &str,
#[case] ptx: &str,
#[case] expected_ctx: &str,
) {
let k = decode_hex(kx).unwrap();
let iv = decode_hex(ivx).unwrap();
let pt = decode_hex(ptx).unwrap();

let key = Key::<192>::new(k.try_into().unwrap());
let nonce = &iv[..8];
let counter = Counter(iv[8..].try_into().unwrap());

let ctr = CTR::<AES<192>, 8>::new(nonce.try_into().unwrap());

let ct = ctr.encrypt(&key, &counter, &pt).unwrap();

let ctx = encode_hex(&ct);
assert_eq!(ctx, expected_ctx);

let _pt = ctr.decrypt(&key, &counter, &ct).unwrap();

let _ptx = encode_hex(&_pt);
assert_eq!(_ptx, ptx);
}

// Appendix F.5.3 and F.5.4
#[rstest]
#[case(
"603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4",
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
"6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710",
"601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6"
)]
fn test_ctr_256(
#[case] kx: &str,
#[case] ivx: &str,
#[case] ptx: &str,
#[case] expected_ctx: &str,
) {
let k = decode_hex(kx).unwrap();
let iv = decode_hex(ivx).unwrap();
let pt = decode_hex(ptx).unwrap();

let key = Key::<256>::new(k.try_into().unwrap());
let nonce = &iv[..8];
let counter = Counter(iv[8..].try_into().unwrap());

let ctr = CTR::<AES<256>, 8>::new(nonce.try_into().unwrap());

let ct = ctr.encrypt(&key, &counter, &pt).unwrap();

let ctx = encode_hex(&ct);
assert_eq!(ctx, expected_ctx);

let _pt = ctr.decrypt(&key, &counter, &ct).unwrap();

let _ptx = encode_hex(&_pt);
assert_eq!(_ptx, ptx);
}
}
Loading

0 comments on commit e4387fe

Please sign in to comment.