-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: cipher modes of operation (#127)
* feat: AES decryption * cleanup * docs: update README for aes decryption * remove accidental duplication * docs: fix typo in aes decryption example * docs: replace hex representation of a^-1(x) with decimal representation * feat: better name/code/docs for galois multiplication * more hex representation replacement * simplify galois_multiplication params * docs: fix typo * init cbc * add `BlockCipher` trait * add cbc * tests and docs * aes cbc example * add ctr * fix docs * change padding for CBC mode * fix doctests --------- Co-authored-by: bing <[email protected]>
- Loading branch information
1 parent
c218d47
commit 6e6a230
Showing
12 changed files
with
616 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
//! Demonstrating AES chained CBC mode of operation where last ciphertext of previous operation is | ||
//! used as IV for next operation. This has advantage as it reduces the bandwidth to share a new IV | ||
//! each time between the parties. But in CBC mode, IV should be unpredictable, this was formalised in [CWE-329](https://cwe.mitre.org/data/definitions/329.html). | ||
//! | ||
//! But this scheme is not Chosen-Plaintext Attack secure and any | ||
//! attacker can detect which original message was used in the ciphertext which is shown here. | ||
#![allow(incomplete_features)] | ||
#![feature(generic_const_exprs)] | ||
use rand::{thread_rng, Rng}; | ||
use ronkathon::encryption::symmetric::{ | ||
aes::{Block, Key, AES}, | ||
modes::cbc::CBC, | ||
}; | ||
|
||
fn attacker_chosen_message() -> [&'static [u8]; 2] { | ||
[b"You're gonna be pwned!", b"HAHA, You're gonna be dbl pwned!!"] | ||
} | ||
|
||
fn xor_blocks(a: &mut [u8], b: &[u8]) { | ||
for (x, y) in a.iter_mut().zip(b) { | ||
*x ^= *y; | ||
} | ||
} | ||
|
||
fn attacker<'a>(key: &Key<128>, iv: &Block, ciphertext: Vec<u8>) -> &'a [u8] { | ||
// Chose 2 random messages, {m_0, m_1} | ||
let messages = attacker_chosen_message(); | ||
|
||
// first blocks' ciphertext | ||
let c1 = &ciphertext[..16]; | ||
|
||
// select new IV as last blocks' ciphertext and intiate CBC with AES again with new IV | ||
let new_iv: [u8; 16] = ciphertext[ciphertext.len() - 16..].try_into().unwrap(); | ||
let cbc2 = CBC::<AES<128>>::new(Block(new_iv)); | ||
|
||
// Now, attacker selects the new message m_4 = IV ⨁ m_0 ⨁ NEW_IV | ||
let mut pwned_message = iv.0; | ||
xor_blocks(&mut pwned_message, messages[0]); | ||
xor_blocks(&mut pwned_message, &new_iv); | ||
|
||
// attacker receives ciphertext from encryption oracle | ||
let encrypted = cbc2.encrypt(key, &pwned_message); | ||
|
||
// attacker has gained knowledge about initial message | ||
if c1 == encrypted { | ||
messages[0] | ||
} else { | ||
messages[1] | ||
} | ||
} | ||
|
||
/// We simulate Chained CBC and show that attacker can know whether initial plaintext was message 1 | ||
/// or 2. | ||
fn main() { | ||
let mut rng = thread_rng(); | ||
|
||
// generate a random key and publicly known IV, and initiate CBC with AES cipher | ||
let key = Key::<128>::new(rng.gen()); | ||
let iv = Block(rng.gen()); | ||
let cbc = CBC::<AES<128>>::new(iv); | ||
|
||
// Chose 2 random messages, {m_0, m_1} | ||
let messages = attacker_chosen_message(); | ||
|
||
// select a uniform bit b, and chose message m_b for encryption | ||
let bit = rng.gen_range(0..=1); | ||
let encrypted = cbc.encrypt(&key, messages[bit]); | ||
|
||
let predicted_message = attacker(&key, &iv, encrypted); | ||
|
||
assert_eq!(messages[bit], predicted_message); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
//! Counter used during various encryption primitives for randomising IV in reduced bandwidth | ||
//! scenarios. Implements a simple increment by one counter. | ||
/// Counter consisting of big-endian integer using byte(8-bit) limbs | ||
#[derive(Debug, Clone, Copy)] | ||
pub struct Counter<const C: usize>(pub [u8; C]); | ||
|
||
impl<const C: usize> Counter<C> { | ||
/// returns a new Counter | ||
/// ## Arguments | ||
/// - `value`: big-endian integer represented using 8-bit limbs | ||
pub fn new(value: [u8; C]) -> Self { Self(value) } | ||
|
||
/// increases counter value by 1 for each new round of `C` byte input. | ||
/// | ||
/// ## Note | ||
/// Returns `max counter reached` error when counter value reaches maximum allowed by different | ||
/// counter length. | ||
pub fn increment(&mut self) -> Result<(), String> { | ||
match C { | ||
0 => Err("counter value is 0".to_string()), | ||
_ => { | ||
// check for max value | ||
let mut flag = true; | ||
for value in self.0.iter() { | ||
if *value != u8::MAX { | ||
flag = false; | ||
} | ||
} | ||
|
||
if flag { | ||
return Err("max counter reached".to_string()); | ||
} | ||
|
||
let mut add_carry = true; | ||
for i in (0..C).rev() { | ||
let (incremented_val, carry) = self.0[i].overflowing_add(add_carry as u8); | ||
self.0[i] = incremented_val; | ||
add_carry = carry; | ||
} | ||
|
||
Ok(()) | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl<const C: usize> From<usize> for Counter<C> { | ||
fn from(value: usize) -> Self { | ||
let mut limbs = [0u8; C]; | ||
|
||
let value_bytes = value.to_be_bytes(); | ||
for i in (0..std::cmp::min(C, 8)).rev() { | ||
limbs[i] = value_bytes[i]; | ||
} | ||
|
||
Self(limbs) | ||
} | ||
} |
Oops, something went wrong.