documentation

This commit is contained in:
Sam Hadow 2024-04-28 22:42:57 +02:00
parent ce23e50b94
commit 263bd5d8dd
5 changed files with 136 additions and 12 deletions

View File

@ -1,12 +1,25 @@
# aes-project # aes-project
AES-128, and square attack against reduced AES-128 (only 4-turns) implementation in rust. AES-128, and square attack against reduced AES-128 (only 4-turns) implementation in rust.
### commands
#### encrypt
encrypt or decrypt a text
#### findkey
square attack against 4-turns AES-128 with a specific key
#### square
square attack against 4-turns AES-128 with n randomly generated keys
### example ### example
encrypt the text "Hello, world!" using the key 2b7e151628aed2a6abf7158809cf4f3c, hex-encoded. 10-turns AES-128 encrypt the text "Hello, world!" using the key 2b7e151628aed2a6abf7158809cf4f3c, hex-encoded. 10-turns AES-128
``` ```
cargo run -- encrypt -K -k '2b7e151628aed2a6abf7158809cf4f3c' -t "Hello, world!" -n 10 cargo run -- encrypt -K -k '2b7e151628aed2a6abf7158809cf4f3c' -t "Hello, world!" -n 10
``` ```
decrypt the text 7d6a80dde1d56639903194344eddf515, hex-encoded, using the key 2b7e151628aed2a6abf7158809cf4f3c, hex-encoded. 10-turns AES-128
```
cargo run -- encrypt -d -Kk '2b7e151628aed2a6abf7158809cf4f3c' -Tt '7d6a80dde1d56639903194344eddf515' -n 10
```
square attack against 4-turns AES-128, choosen key bba51a66aca801747294ff4317fb59e2, hex-encoded. square attack against 4-turns AES-128, choosen key bba51a66aca801747294ff4317fb59e2, hex-encoded.
``` ```
cargo run -- findkey bba51a66aca801747294ff4317fb59e2 -K cargo run -- findkey bba51a66aca801747294ff4317fb59e2 -K

View File

@ -1,7 +1,7 @@
// AES specs // AES specs
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf // https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf
// S box, 16*16 Matrix with fixed values /// SBOX, 16*16 Matrix with fixed values
#[rustfmt::skip] #[rustfmt::skip]
static SBOX: [[u8; 16]; 16] = [ static SBOX: [[u8; 16]; 16] = [
[0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,], [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,],
@ -22,6 +22,7 @@ static SBOX: [[u8; 16]; 16] = [
[0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,], [0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,],
]; ];
/// SBOX^-1
#[rustfmt::skip] #[rustfmt::skip]
static INVERSE_SBOX: [[u8; 16]; 16] = [ static INVERSE_SBOX: [[u8; 16]; 16] = [
[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,], [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,],
@ -42,11 +43,12 @@ static INVERSE_SBOX: [[u8; 16]; 16] = [
[0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,], [0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,],
]; ];
// for key schedule /// for key schedule, round constant word array
pub static RC: [u8; 11] = [ pub static RC: [u8; 11] = [
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36,
]; ];
/// clone data of an input slice into an array
pub fn clone_into_array<A, T>(slice: &[T]) -> A pub fn clone_into_array<A, T>(slice: &[T]) -> A
where where
A: Default + AsMut<[T]>, A: Default + AsMut<[T]>,
@ -57,6 +59,7 @@ where
a a
} }
/// xor between all bits of 2 4-bytes words.
pub fn xor(bits1: &[u8; 4], bits2: &[u8; 4]) -> [u8; 4] { pub fn xor(bits1: &[u8; 4], bits2: &[u8; 4]) -> [u8; 4] {
let mut result = [0u8; 4]; let mut result = [0u8; 4];
for i in 0..4 { for i in 0..4 {
@ -65,6 +68,7 @@ pub fn xor(bits1: &[u8; 4], bits2: &[u8; 4]) -> [u8; 4] {
result result
} }
/// substitute all bytes of a word with SBOX
pub fn substitute_word(bytes: &[u8; 4]) -> [u8; 4] { pub fn substitute_word(bytes: &[u8; 4]) -> [u8; 4] {
let mut result = [0u8; 4]; let mut result = [0u8; 4];
for i in 0..4 { for i in 0..4 {
@ -72,6 +76,7 @@ pub fn substitute_word(bytes: &[u8; 4]) -> [u8; 4] {
} }
result result
} }
/// substitute all bytes of a word with SBOX^-1
fn inverse_substitute_word(bytes: &[u8; 4]) -> [u8; 4] { fn inverse_substitute_word(bytes: &[u8; 4]) -> [u8; 4] {
let mut result = [0u8; 4]; let mut result = [0u8; 4];
for i in 0..4 { for i in 0..4 {
@ -80,6 +85,7 @@ fn inverse_substitute_word(bytes: &[u8; 4]) -> [u8; 4] {
result result
} }
/// substitute operation when encrypting. Substitute all bytes of a state with SBOX^-1
fn substitute_state(state: &mut [[u8; 4]; 4]) { fn substitute_state(state: &mut [[u8; 4]; 4]) {
for row in state.iter_mut().take(4) { for row in state.iter_mut().take(4) {
for item in row.iter_mut().take(4) { for item in row.iter_mut().take(4) {
@ -88,6 +94,7 @@ fn substitute_state(state: &mut [[u8; 4]; 4]) {
} }
} }
/// substitute operation when decrypting. Substitute all bytes of a state with SBOX
pub fn inverse_substitute_state(state: &mut [[u8; 4]; 4]) { pub fn inverse_substitute_state(state: &mut [[u8; 4]; 4]) {
for row in state.iter_mut().take(4) { for row in state.iter_mut().take(4) {
for item in row.iter_mut().take(4) { for item in row.iter_mut().take(4) {
@ -96,6 +103,7 @@ pub fn inverse_substitute_state(state: &mut [[u8; 4]; 4]) {
} }
} }
/// Substitute a given byte using the SBOX if encrypting or the SBOX^-1 if decrypting.
pub fn substitute(byte: u8, encryption: bool) -> u8 { pub fn substitute(byte: u8, encryption: bool) -> u8 {
let i: usize = ((byte >> 4) & 0xF).into(); let i: usize = ((byte >> 4) & 0xF).into();
let j: usize = (byte & 0xF).into(); let j: usize = (byte & 0xF).into();
@ -106,6 +114,7 @@ pub fn substitute(byte: u8, encryption: bool) -> u8 {
} }
} }
/// Shift bytes in a given word of 8 bytes.
pub fn shift_word(bytes: &[u8; 4]) -> [u8; 4] { pub fn shift_word(bytes: &[u8; 4]) -> [u8; 4] {
let mut result = [0u8; 4]; let mut result = [0u8; 4];
for i in 0..4 { for i in 0..4 {
@ -114,6 +123,7 @@ pub fn shift_word(bytes: &[u8; 4]) -> [u8; 4] {
result result
} }
/// Shift bytes^-1 operation in a given word of 8 bytes.
fn inverse_shift_word(bytes: &[u8; 4]) -> [u8; 4] { fn inverse_shift_word(bytes: &[u8; 4]) -> [u8; 4] {
let mut result = [0u8; 4]; let mut result = [0u8; 4];
for i in 0..4 { for i in 0..4 {
@ -122,6 +132,7 @@ fn inverse_shift_word(bytes: &[u8; 4]) -> [u8; 4] {
result result
} }
/// Cyclically shifts the last three rows of the state by different offsets, used when encrypting.
fn shift_rows(state: &mut [[u8; 4]; 4]) { fn shift_rows(state: &mut [[u8; 4]; 4]) {
for i in 1..4 { for i in 1..4 {
let mut tmp = vec![0u8; i]; let mut tmp = vec![0u8; i];
@ -135,6 +146,7 @@ fn shift_rows(state: &mut [[u8; 4]; 4]) {
} }
} }
/// Inverse operation of shift_rows, used when decrypting.
pub fn inverse_shift_rows(state: &mut [[u8; 4]; 4]) { pub fn inverse_shift_rows(state: &mut [[u8; 4]; 4]) {
for i in (1..4).rev() { for i in (1..4).rev() {
let mut tmp = vec![0u8; i]; let mut tmp = vec![0u8; i];
@ -148,6 +160,8 @@ pub fn inverse_shift_rows(state: &mut [[u8; 4]; 4]) {
} }
} }
/// Multiplication in Galois field GF(2^8).
/// a(x) * b(x) mod m(x), with m(x)=x^8+x^4+x^3+x^1 (reduce any term of degree higher than 8)
fn galois_multiplication(ap: u8, bp: u8) -> u8 { fn galois_multiplication(ap: u8, bp: u8) -> u8 {
let mut p = 0u8; let mut p = 0u8;
let mut high_bit; let mut high_bit;
@ -167,6 +181,7 @@ fn galois_multiplication(ap: u8, bp: u8) -> u8 {
p p
} }
/// Mix columns data in state, used when encrypting, non-linearity + diffusion.
fn mix_columns(state: &mut [[u8; 4]; 4]) { fn mix_columns(state: &mut [[u8; 4]; 4]) {
for i in 0..4 { for i in 0..4 {
let mut temp = [0u8; 4]; let mut temp = [0u8; 4];
@ -192,6 +207,7 @@ fn mix_columns(state: &mut [[u8; 4]; 4]) {
} }
} }
/// mix column^-1 operation on a state, used when decrypting
fn inverse_mix_columns(state: &mut [[u8; 4]; 4]) { fn inverse_mix_columns(state: &mut [[u8; 4]; 4]) {
for i in 0..4 { for i in 0..4 {
let mut temp = [0u8; 4]; let mut temp = [0u8; 4];
@ -218,6 +234,7 @@ fn inverse_mix_columns(state: &mut [[u8; 4]; 4]) {
} }
} }
/// Add given round key to a state.
pub fn add_round_key(state: &mut [[u8; 4]; 4], key: &[[u8; 4]; 4]) { pub fn add_round_key(state: &mut [[u8; 4]; 4], key: &[[u8; 4]; 4]) {
// for i in 0..4 { // for i in 0..4 {
// for j in 0..4 { // for j in 0..4 {
@ -231,12 +248,24 @@ pub fn add_round_key(state: &mut [[u8; 4]; 4], key: &[[u8; 4]; 4]) {
} }
} }
/// AES-128 structure
pub struct Aes { pub struct Aes {
n_turn: usize, n_turn: usize,
pub expanded_key: [[u8; 4]; 44], pub expanded_key: [[u8; 4]; 44],
} }
impl Aes { impl Aes {
/// Create a new Aes object from a given 16 bytes key and a number of turn.
///
/// Number of turn is the number of encryption and decryption turns (should be 10 for standard AES-128)
///
/// # Examples
///
/// ```no_run
/// let key: [u8; 16] = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,];
/// let nturn: usize = 10;
/// let aescipher = Aes::new(&key, &nturn);
/// ```
pub fn new(&key: &[u8; 16], &n_turn: &usize) -> Self { pub fn new(&key: &[u8; 16], &n_turn: &usize) -> Self {
Aes { Aes {
n_turn, n_turn,
@ -244,6 +273,14 @@ impl Aes {
} }
} }
/// Encrypt a given block with the key and number of turns specified in the Aes object. Return the encrypted block.
/// # Examples
///
/// ```no_run
/// let aescipher = Aes::new(&key, &nturn);
/// let block: [u8; 16] = [0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34,];
/// let encrypted_block: [u8; 16] = aescipher.encrypt_block(&block);
/// ```
pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] { pub fn encrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16]; let mut result = [0u8; 16];
@ -279,6 +316,14 @@ impl Aes {
result result
} }
/// Decrypt a given block with the key and number of turns specified in the Aes object. Return the decrypted block.
/// # Examples
///
/// ```no_run
/// let aescipher = Aes::new(&key, &nturn);
/// let encrypted_block: [u8; 16] = [0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34,];
/// let clear_block: [u8; 16] = aescipher.decrypt_block(&block);
/// ```
pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] { pub fn decrypt_block(&self, block: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16]; let mut result = [0u8; 16];
@ -315,6 +360,13 @@ impl Aes {
result result
} }
/// return the AES-128 expanded key from a 16 bytes user supplied key
/// # Examples
///
/// ```no_run
/// let key: [u8; 16] = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c,];
/// let expanded_key: [[u8; 4]; 44] = Aes::key_schedule(&key);
/// ```
pub fn key_schedule(key_bytes: &[u8; 16]) -> [[u8; 4]; 44] { pub fn key_schedule(key_bytes: &[u8; 16]) -> [[u8; 4]; 44] {
let mut original_key = [[0u8; 4]; 4]; let mut original_key = [[0u8; 4]; 4];
let mut expanded_key = [[0u8; 4]; 44]; let mut expanded_key = [[0u8; 4]; 44];

View File

@ -1,3 +1,33 @@
//! AES-128 and square attack implementation in rust.
//!
//! ### commands
//! #### encrypt
//! encrypt or decrypt a text
//!
//! #### findkey
//! square attack against 4-turns AES-128 with a specific key
//!
//! #### square
//! square attack against 4-turns AES-128 with n randomly generated keys
//!
//! # Examples
//! encrypt the text "Hello, world!" using the key 2b7e151628aed2a6abf7158809cf4f3c, hex-encoded. 10-turns AES-128
//!```
//! $ cargo run -- encrypt -K -k '2b7e151628aed2a6abf7158809cf4f3c' -t "Hello, world!" -n 10
//!```
//! decrypt the text 7d6a80dde1d56639903194344eddf515, hex-encoded, using the key 2b7e151628aed2a6abf7158809cf4f3c, hex-encoded. 10-turns AES-128
//!```
//! $ cargo run -- encrypt -d -Kk '2b7e151628aed2a6abf7158809cf4f3c' -Tt '7d6a80dde1d56639903194344eddf515' -n 10
//!```
//! square attack against 4-turns AES-128, choosen key bba51a66aca801747294ff4317fb59e2, hex-encoded.
//!```
//! $ cargo run -- findkey bba51a66aca801747294ff4317fb59e2 -K
//!```
//! square attack against 4-turns AES-128, 10 keys generated randomly.
//!```
//! $ cargo run -- square 10
//!```
mod aes; mod aes;
mod square; mod square;
mod utils; mod utils;
@ -90,7 +120,7 @@ fn main() -> std::io::Result<()> {
print!("Encrypted text, hex-encoded: "); print!("Encrypted text, hex-encoded: ");
} }
} }
Aes::print_key(&result); utils::print_array(&result);
println!(); println!();
} }
Commands::Findkey { key, hexkey } => { Commands::Findkey { key, hexkey } => {

View File

@ -1,7 +1,9 @@
use crate::aes::*; use crate::aes::*;
use crate::utils;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
impl Aes { impl Aes {
/// Reverse the key schedule to find the original key from a round key. aes_round should be the index of the given round key (in [0;10])
pub fn reverse_key_schedule(original_round_key: &[u8; 16], aes_round: usize) -> [u8; 16] { pub fn reverse_key_schedule(original_round_key: &[u8; 16], aes_round: usize) -> [u8; 16] {
let mut rcon = [0u8; 4]; let mut rcon = [0u8; 4];
let mut round_key: [u8; 16] = [0u8; 16]; let mut round_key: [u8; 16] = [0u8; 16];
@ -29,11 +31,15 @@ impl Aes {
round_key round_key
} }
/// Encrypt a given block with the given key. Returns the encrypted block. Use a reduced version of AES-128 with only 4 rounds
pub fn aes_reduced(&key: &[u8; 16], &block: &[u8; 16]) -> [u8; 16] { pub fn aes_reduced(&key: &[u8; 16], &block: &[u8; 16]) -> [u8; 16] {
let nturn = 4; let nturn = 4;
let aescipher = Self::new(&key, &nturn); let aescipher = Self::new(&key, &nturn);
aescipher.encrypt_block(&block) aescipher.encrypt_block(&block)
} }
/// Generate 2^8=256 encrypted block using a given key and a given constant byte.
///
/// In the clear block, the first byte of the blocks is every value from 0 to 255. All other bytes are set to the constant byte.
pub fn aes_reduced_gen_texts(&key: &[u8; 16], &const_byte: &u8) -> [[u8; 16]; 256] { pub fn aes_reduced_gen_texts(&key: &[u8; 16], &const_byte: &u8) -> [[u8; 16]; 256] {
let mut cleartext: [u8; 16]; let mut cleartext: [u8; 16];
let mut ciphertexts: [[u8; 16]; 256] = [[0; 16]; 256]; let mut ciphertexts: [[u8; 16]; 256] = [[0; 16]; 256];
@ -68,6 +74,11 @@ impl Aes {
// result // result
// } // }
/// Using 2^8=256 texts find the possible values for every bytes of the round key.
///
/// Return a [Vec<u8>; 16], each Vec contains the possible bytes at this index in the round key.
///
/// Texts should be generated using aes_reduced_gen_texts function.
pub fn guessroundkey(&texts: &[[u8; 16]; 256]) -> [Vec<u8>; 16] { pub fn guessroundkey(&texts: &[[u8; 16]; 256]) -> [Vec<u8>; 16] {
let mut key: [Vec<u8>; 16] = Default::default(); let mut key: [Vec<u8>; 16] = Default::default();
let mut s: u8; let mut s: u8;
@ -99,6 +110,11 @@ impl Aes {
key key
} }
/// Break a given key using a square attack against 4-rounds AES-128.
///
/// Generate 256 texts using the given key and use these text to find the bytes of the round key.
/// While there is still more than 1 possible byte at each position in the guess, generate 256 more texts with another constant and find the possible bytes of the round key again, calculate the intersection between previously and newly calculated possible byte at a certain position.
/// At the end of the loop there is only 1 possible byte at each position in the round key, calculate the original key reversing the key schedule and return this key.
pub fn findroundkey(&key: &[u8; 16]) -> [u8; 16] { pub fn findroundkey(&key: &[u8; 16]) -> [u8; 16] {
let mut found_key: [u8; 16] = [0x00; 16]; let mut found_key: [u8; 16] = [0x00; 16];
let mut const_byte: u8 = 0x00; let mut const_byte: u8 = 0x00;
@ -136,11 +152,13 @@ impl Aes {
found_key found_key
} }
/// return the original key, given a round key of index 4
pub fn findkey(&key: &[u8; 16]) -> [u8; 16] { pub fn findkey(&key: &[u8; 16]) -> [u8; 16] {
let roundkey: [u8; 16] = Self::findroundkey(&key); let roundkey: [u8; 16] = Self::findroundkey(&key);
Self::reverse_key_schedule(&roundkey, 4) Self::reverse_key_schedule(&roundkey, 4)
} }
/// generate a random 16 bytes array, used to generate a random key.
pub fn generate_random_keys(n: usize) -> Vec<[u8; 16]> { pub fn generate_random_keys(n: usize) -> Vec<[u8; 16]> {
let mut rng = thread_rng(); let mut rng = thread_rng();
let mut keys_vec = Vec::with_capacity(n); let mut keys_vec = Vec::with_capacity(n);
@ -154,6 +172,7 @@ impl Aes {
keys_vec keys_vec
} }
/// Test the square attack against 4-turns AES-128 using n randomly generated key. Print the number of correctly found keys at the end.
pub fn test_square_attack(n: usize) { pub fn test_square_attack(n: usize) {
println!( println!(
"Square attack against 4-turns AES with {} randomly generated keys", "Square attack against 4-turns AES with {} randomly generated keys",
@ -164,11 +183,11 @@ impl Aes {
let mut success: usize = 0; let mut success: usize = 0;
for &key in &keys { for &key in &keys {
print!("\ngenerated key: "); print!("\ngenerated key: ");
Self::print_key(&key); utils::print_array(&key);
found_key = Aes::findkey(&key); found_key = Aes::findkey(&key);
if found_key == key { if found_key == key {
print!("found key: "); print!("found key: ");
Self::print_key(&found_key); utils::print_array(&found_key);
success += 1; success += 1;
} else { } else {
println!("key search failed"); println!("key search failed");
@ -177,12 +196,6 @@ impl Aes {
println!("\n{}/{} key(s) found", success, n); println!("\n{}/{} key(s) found", success, n);
} }
pub fn print_key(&key: &[u8; 16]) {
for &byte in &key {
print!("{:02x}", byte);
}
println!();
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -1,5 +1,12 @@
use std::str; use std::str;
/// parse an string of 16 bytes hex-encoded and return an array of 16 bytes
/// # Examples
///
/// ```no_run
/// let text = String::from("2b7e151628aed2a6abf7158809cf4f3c");
/// let array: [u8; 16] = parse_hex_string(&text);
/// ```
pub fn parse_hex_string(input: &String) -> [u8; 16] { pub fn parse_hex_string(input: &String) -> [u8; 16] {
let mut result = [0u8; 16]; let mut result = [0u8; 16];
// Iterate over the string 2 characters each time // Iterate over the string 2 characters each time
@ -17,6 +24,15 @@ pub fn parse_hex_string(input: &String) -> [u8; 16] {
result result
} }
/// print bytes in an array as an hex-encoded string
pub fn print_array(&array: &[u8; 16]) {
for &byte in &array {
print!("{:02x}", byte);
}
println!();
}
/// split any string into an array of 16 bytes, (any additional byte in the string is lost)
pub fn string_to_u8_16(input: &String) -> [u8; 16] { pub fn string_to_u8_16(input: &String) -> [u8; 16] {
let mut result = [0u8; 16]; let mut result = [0u8; 16];
let bytes = input.as_bytes(); let bytes = input.as_bytes();