diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 168747e..f4eaab3 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -1,10 +1,14 @@ use base64::prelude::*; -use std::collections::HashMap; +use std::{collections::HashMap, env::args}; -use crate::utils::parse::{Responses, Testcase, Testcases}; +use crate::utils::{ + ciphers::gcm_encrypt_aes, + parse::{Responses, Testcase, Testcases}, +}; use tasks01::{ block2poly::block2poly, + gcm::gcm_encrypt, gfmul::gfmul_task, poly2block::poly2block, sea128::sea128, @@ -53,6 +57,17 @@ pub fn task_deploy(testcase: &Testcase) -> Result { Ok(json) } + "gcm_encrypt" => { + let (ciphertext, auth_tag, l_field, auth_key_h) = gcm_encrypt(args)?; + let out_ciph = BASE64_STANDARD.encode(&ciphertext); + let out_tag = BASE64_STANDARD.encode(&auth_tag); + let out_l = BASE64_STANDARD.encode(&l_field); + let out_h = BASE64_STANDARD.encode(&auth_key_h); + + let json = json!({"ciphertext" : out_ciph, "tag" : out_tag, "L" : out_l, "H" : out_h}); + + Ok(json) + } _ => Err(anyhow!( "Fatal. No compatible action found. Json data was {:?}. Arguments were; {:?}", testcase, @@ -166,4 +181,24 @@ mod tests { Ok(()) } + + #[test] + fn test_task_gcm_encrypt_aes_case() -> Result<()> { + let json = fs::read_to_string("test_json/gcm_encrypt.json").unwrap(); + let parsed = parse_json(json).unwrap(); + + let expected = json!({ "responses" : { "b856d760-023d-4b00-bad2-15d2b6da22fe" : { + "ciphertext": "ET3RmvH/Hbuxba63EuPRrw==", + "tag": "Mp0APJb/ZIURRwQlMgNN/w==", + "L": "AAAAAAAAAEAAAAAAAAAAgA==", + "H": "Bu6ywbsUKlpmZXMQyuGAng==" + }}}); + + assert_eq!( + serde_json::to_value(task_distrubute(&parsed)?).unwrap(), + serde_json::to_value(expected).unwrap() + ); + + Ok(()) + } } diff --git a/src/tasks/tasks01/gcm.rs b/src/tasks/tasks01/gcm.rs new file mode 100644 index 0000000..4763d53 --- /dev/null +++ b/src/tasks/tasks01/gcm.rs @@ -0,0 +1,26 @@ +use anyhow::{anyhow, Result}; +use base64::prelude::*; +use serde_json::Value; + +use crate::utils::ciphers::gcm_encrypt_aes; + +pub fn gcm_encrypt(args: &Value) -> Result<(Vec, Vec, Vec, Vec)> { + let nonce_text: String = serde_json::from_value(args["nonce"].clone())?; + let nonce = BASE64_STANDARD.decode(nonce_text)?; + + let key_text: String = serde_json::from_value(args["key"].clone())?; + let key = BASE64_STANDARD.decode(key_text)?; + + let plaintext_text: String = serde_json::from_value(args["plaintext"].clone())?; + let plaintext = BASE64_STANDARD.decode(plaintext_text)?; + + let ad_text: String = serde_json::from_value(args["ad"].clone())?; + let ad = BASE64_STANDARD.decode(ad_text)?; + + let alg_text: String = serde_json::from_value(args["algorithm"].clone())?; + + match alg_text.as_str() { + "aes128" => Ok(gcm_encrypt_aes(nonce, key, plaintext, ad)?), + _ => Err(anyhow!("No compatible algorithm found")), + } +} diff --git a/src/tasks/tasks01/mod.rs b/src/tasks/tasks01/mod.rs index 97ea4d7..d1f3e99 100644 --- a/src/tasks/tasks01/mod.rs +++ b/src/tasks/tasks01/mod.rs @@ -1,4 +1,5 @@ pub mod block2poly; +pub mod gcm; pub mod gfmul; pub mod poly2block; pub mod sea128; diff --git a/src/utils/ciphers.rs b/src/utils/ciphers.rs index 885b1c3..2be30fb 100644 --- a/src/utils/ciphers.rs +++ b/src/utils/ciphers.rs @@ -1,9 +1,9 @@ -use std::io::BufRead; +use std::{io::BufRead, process::Output}; use anyhow::Result; use openssl::symm::{Cipher, Crypter, Mode}; -use crate::utils::field::ByteArray; +use crate::utils::{field::ByteArray, math::reverse_bits_in_bytevec, poly::gfmul}; use super::math::xor_bytes; @@ -122,6 +122,92 @@ pub fn xex_decrypt(mut key: Vec, tweak: &Vec, input: &Vec) -> Result Ok(output) } +pub fn gcm_encrypt_aes( + mut nonce: Vec, + key: Vec, + plaintext: Vec, + ad: Vec, +) -> Result<(Vec, Vec, Vec, Vec)> { + let mut ciphertext: Vec = vec![]; + + let mut counter: u32 = 1; + nonce.append(counter.to_be_bytes().to_vec().as_mut()); + //nonce.append(0u8.to_le_bytes().to_vec().as_mut()); + eprintln!("{:001X?}", nonce); + + let auth_tag_xor = aes_128_encrypt(&key, &nonce)?; + + let auth_key_h = aes_128_encrypt(&key, &0u128.to_be_bytes().to_vec())?; + + let plaintext_chunks: Vec> = plaintext.chunks(16).map(|x| x.to_vec()).collect(); + + counter = 2; + for chunk in plaintext_chunks { + nonce.drain(12..); + nonce.append(counter.to_be_bytes().to_vec().as_mut()); + + eprintln!("{:001X?}", nonce); + + let inter1 = aes_128_encrypt(&key, &nonce)?; + + let mut inter2 = xor_bytes(&inter1, chunk.clone())?; + + ciphertext.append(inter2.as_mut()); + counter += 1; + } + + let mut l_field: Vec = ((ad.len() * 8) as u64).to_be_bytes().to_vec(); + let mut c_len: Vec = ((ciphertext.len() * 8) as u64).to_be_bytes().to_vec(); + l_field.append(c_len.as_mut()); + + let auth_tag = xor_bytes( + &ghash(auth_key_h.clone(), ad, ciphertext.clone(), l_field.clone())?, + auth_tag_xor, + )?; + + Ok((ciphertext, auth_tag, l_field, auth_key_h)) +} + +pub fn ghash( + auth_key_h: Vec, + mut ad: Vec, + mut ciphertext: Vec, + l_field: Vec, +) -> Result> { + let output: Vec = vec![0; 16]; + + eprintln!("{:?}", (ad.len() % 16) as u8); + eprintln!("{:001X?}", ad); + + if ad.len() % 16 != 0 { + ad.append(vec![0u8; ad.len() % 16].as_mut()); + } + + if ciphertext.len() % 16 != 0 { + ciphertext.append(vec![0u8; ciphertext.len() % 16].as_mut()); + } + + eprintln!("{:001X?}", ad); + eprintln!("{:001X?}", ciphertext); + + let inter1 = xor_bytes(&output, ad)?; + let mut inter_loop = gfmul(inter1, auth_key_h.clone(), "gcm")?; + + inter_loop = inter_loop; + + let cipher_chunks = ciphertext.chunks(16); + + for chunk in cipher_chunks { + let inter3 = xor_bytes(&inter_loop, chunk.to_vec())?; + inter_loop = gfmul(inter3, auth_key_h.clone(), "gcm")?; + } + + let inter4 = xor_bytes(&inter_loop, l_field)?; + inter_loop = gfmul(inter4, auth_key_h.clone(), "gcm")?; + + Ok(inter_loop) +} + /* * let mut bytes: [u8; 16] = [0u8; 16]; bytes.copy_from_slice(&ciphertext); @@ -180,4 +266,36 @@ mod tests { Ok(()) } + + #[test] + fn test_gcm_encrypt_aes() -> Result<()> { + let nonce = BASE64_STANDARD.decode("4gF+BtR3ku/PUQci")?; + let key = BASE64_STANDARD.decode("Xjq/GkpTSWoe3ZH0F+tjrQ==")?; + let plaintext = BASE64_STANDARD.decode("RGFzIGlzdCBlaW4gVGVzdA==")?; + let ad = BASE64_STANDARD.decode("QUQtRGF0ZW4=")?; + + let (ciphertext, auth_tag, l_field, auth_key_h) = + gcm_encrypt_aes(nonce, key, plaintext, ad)?; + + eprintln!( + "Cipher: {:001X?} \n Tag: {:001X?} \n L_Field: {:001X?} \n H: {:001X?}", + BASE64_STANDARD.encode(&ciphertext), + BASE64_STANDARD.encode(&auth_tag), + BASE64_STANDARD.encode(&l_field), + BASE64_STANDARD.encode(&auth_key_h) + ); + + assert_eq!( + BASE64_STANDARD.encode(ciphertext), + "ET3RmvH/Hbuxba63EuPRrw==" + ); + assert_eq!(BASE64_STANDARD.encode(auth_tag), "Mp0APJb/ZIURRwQlMgNN/w=="); + assert_eq!(BASE64_STANDARD.encode(l_field), "AAAAAAAAAEAAAAAAAAAAgA=="); + assert_eq!( + BASE64_STANDARD.encode(auth_key_h), + "Bu6ywbsUKlpmZXMQyuGAng==" + ); + + Ok(()) + } } diff --git a/src/utils/field.rs b/src/utils/field.rs index afa9b06..5645b04 100644 --- a/src/utils/field.rs +++ b/src/utils/field.rs @@ -96,6 +96,10 @@ impl ByteArray { } true } + + pub fn reverse_bits_in_bytevec(&mut self) { + self.0 = self.0.iter_mut().map(|byte| byte.reverse_bits()).collect(); + } } #[cfg(test)] diff --git a/src/utils/math.rs b/src/utils/math.rs index 4021522..cf87e0c 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -10,3 +10,9 @@ pub fn xor_bytes(vec1: &Vec, mut vec2: Vec) -> Result> { Ok(vec2) } + +pub fn reverse_bits_in_bytevec(mut vec: Vec) -> Vec { + vec = vec.iter_mut().map(|byte| byte.reverse_bits()).collect(); + + vec +} diff --git a/src/utils/poly.rs b/src/utils/poly.rs index ec316a4..78889c8 100644 --- a/src/utils/poly.rs +++ b/src/utils/poly.rs @@ -4,7 +4,7 @@ use base64::prelude::*; use serde_json::Value; use std::{str::FromStr, u128, u8, usize}; -use super::field; +use super::{field, math::reverse_bits_in_bytevec}; pub const RED_POLY: u128 = 0x87000000_00000000_00000000_00000000; pub fn gfmul(poly_a: Vec, poly_b: Vec, semantic: &str) -> Result> { @@ -17,15 +17,20 @@ pub fn gfmul(poly_a: Vec, poly_b: Vec, semantic: &str) -> Result let mut poly2: ByteArray = ByteArray(poly_b); poly2.0.push(0x00); + if semantic == "gcm" { + poly1.reverse_bits_in_bytevec(); + poly2.reverse_bits_in_bytevec(); + } + let mut result: ByteArray = ByteArray(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); if poly2.LSB_is_one() { result.xor_byte_arrays(&poly1); } - poly2.right_shift(semantic)?; + poly2.right_shift("xex")?; while !poly2.is_empty() { - poly1.left_shift(semantic)?; + poly1.left_shift("xex")?; if poly1.msb_is_one() { poly1.xor_byte_arrays(&red_poly_bytes); @@ -35,11 +40,15 @@ pub fn gfmul(poly_a: Vec, poly_b: Vec, semantic: &str) -> Result result.xor_byte_arrays(&poly1); } - poly2.right_shift(semantic)?; + poly2.right_shift("xex")?; } result.0.remove(16); + if semantic == "gcm" { + result.reverse_bits_in_bytevec(); + } + Ok(result.0) } diff --git a/test_json/gcm_encrypt.json b/test_json/gcm_encrypt.json new file mode 100644 index 0000000..347475e --- /dev/null +++ b/test_json/gcm_encrypt.json @@ -0,0 +1,14 @@ +{ + "testcases": { + "b856d760-023d-4b00-bad2-15d2b6da22fe": { + "action": "gcm_encrypt", + "arguments": { + "algorithm": "aes128", + "nonce": "4gF+BtR3ku/PUQci", + "key": "Xjq/GkpTSWoe3ZH0F+tjrQ==", + "plaintext": "RGFzIGlzdCBlaW4gVGVzdA==", + "ad": "QUQtRGF0ZW4=" + } + } + } +}