kauma/src/tasks/tasks01/gcm_crack.rs
2024-12-05 15:57:18 +01:00

257 lines
7.7 KiB
Rust

use std::{env::args, fs::canonicalize, slice::Chunks};
use anyhow::{Ok, Result};
use base64::{prelude::BASE64_STANDARD, Engine};
use openssl::derive;
use serde::{Deserialize, Serialize};
use serde_json::{map, Value};
use crate::utils::{
self,
ciphers::ghash,
dff::ddf,
edf::edf,
field::FieldElement,
math::{reverse_bits_in_bytevec, xor_bytes},
poly::Polynomial,
sff::sff,
};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CrackAnswer {
tag: String,
H: String,
mask: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct Message {
ciphertext: Vec<u8>,
ad: Vec<u8>,
tag: Vec<u8>,
l_field: Vec<u8>,
}
fn parse_message(val: &Value) -> Result<(Message, Polynomial)> {
let ciphertext_text: String = serde_json::from_value(val["ciphertext"].clone())?;
let mut ciphertext_bytes: Vec<u8> = BASE64_STANDARD.decode(ciphertext_text)?;
let mut c_len: Vec<u8> = ((ciphertext_bytes.len() * 8) as u64).to_be_bytes().to_vec();
if ciphertext_bytes.len() % 16 != 0 {
ciphertext_bytes.append(vec![0u8; 16 - (ciphertext_bytes.len() % 16)].as_mut());
}
let ciphertext_chunks: Vec<FieldElement> = ciphertext_bytes
.chunks(16)
.into_iter()
.map(|chunk| FieldElement::new(chunk.to_vec()))
.collect();
let ad_text: String = serde_json::from_value(val["associated_data"].clone())?;
let mut ad_bytes: Vec<u8> = BASE64_STANDARD.decode(ad_text)?;
let mut l_field: Vec<u8> = ((ad_bytes.len() * 8) as u64).to_be_bytes().to_vec();
if ad_bytes.len() % 16 != 0 || ad_bytes.is_empty() {
ad_bytes.append(vec![0u8; 16 - (ad_bytes.len() % 16)].as_mut());
}
let ad_chunks: Vec<FieldElement> = ad_bytes
.chunks(16)
.into_iter()
.map(|chunk| FieldElement::new(chunk.to_vec()))
.collect();
let tag_text: String = serde_json::from_value(val["tag"].clone()).unwrap_or("".to_string());
let tag_bytes: Vec<u8> = BASE64_STANDARD.decode(tag_text)?;
let tag_field: FieldElement = FieldElement::new(tag_bytes.clone());
l_field.append(c_len.as_mut());
// Combine all data
let mut combined: Vec<FieldElement> =
Vec::with_capacity(ad_chunks.len() + ciphertext_chunks.len() + 1);
combined.extend(ad_chunks);
combined.extend(ciphertext_chunks.clone());
combined.push(FieldElement::new(l_field.clone()));
combined.push(tag_field);
combined.reverse();
let h_poly: Polynomial = Polynomial::new(combined);
Ok((
Message {
ciphertext: ciphertext_bytes,
ad: ad_bytes,
tag: tag_bytes,
l_field,
},
h_poly,
))
}
pub fn gcm_crack(args: &Value) -> Result<CrackAnswer> {
// Prepare first equation
let (m1_data, m1_h_poly) = parse_message(&args["m1"])?;
let (_, m2_h_poly) = parse_message(&args["m2"])?;
let (m3_data, _) = parse_message(&args["m3"])?;
eprintln!("m1 poly: {:?}", m1_h_poly.clone().to_c_array());
eprintln!("m2 poly: {:?}", m2_h_poly.clone().to_c_array());
let combine_poly = m1_h_poly + m2_h_poly;
eprintln!("combine poly: {:?}", combine_poly.clone().to_c_array());
let combine_sff = sff(combine_poly.monic());
let mut combine_ddf: Vec<(Polynomial, u128)> = vec![];
for (factor, _) in combine_sff {
combine_ddf.extend(ddf(factor));
}
eprintln!("combine_ddf: {:?}", combine_ddf);
let mut combine_edf: Vec<Polynomial> = vec![];
for (factor, degree) in combine_ddf {
if degree == 1 {
combine_edf.extend(edf(factor, degree as u32));
}
}
eprintln!("combine_edf: {:?}", combine_edf);
let mut m3_auth_tag: Vec<u8> = vec![];
let mut h_candidate: FieldElement = FieldElement::zero();
let mut eky0: Vec<u8> = vec![];
for candidate in combine_edf {
if candidate.degree() == 1 {
h_candidate = candidate.extract_component(0);
let m1_ghash = ghash(
reverse_bits_in_bytevec(h_candidate.to_vec()),
m1_data.ad.clone(),
m1_data.ciphertext.clone(),
m1_data.l_field.clone(),
)
.unwrap();
eky0 = xor_bytes(&m1_data.tag, m1_ghash).unwrap();
eprintln!("eky0: {:?}", BASE64_STANDARD.encode(eky0.clone()));
let m3_ghash = ghash(
reverse_bits_in_bytevec(h_candidate.to_vec()),
m3_data.ad.clone(),
m3_data.ciphertext.clone(),
m3_data.l_field.clone(),
)
.unwrap();
m3_auth_tag = xor_bytes(&eky0, m3_ghash).unwrap();
eprintln!(
"M3 auth tag: {:02X?}",
BASE64_STANDARD.encode(m3_auth_tag.clone())
);
if m3_auth_tag == m3_data.tag {
eprintln!("Candidate valid");
eprintln!("{:02X?}", m3_auth_tag);
break;
} else {
eprintln!("H candidate not valid");
}
}
}
eprintln!(
"M3 Authentication TAG {:02X?}",
BASE64_STANDARD.encode(&m3_auth_tag)
);
if m3_auth_tag.is_empty() {
assert!(false);
eprintln!("No valid candidate found");
}
let (forgery_data, _) = parse_message(&args["forgery"])?;
let forgery_ghash = ghash(
reverse_bits_in_bytevec(h_candidate.to_vec()),
forgery_data.ad.clone(),
forgery_data.ciphertext.clone(),
forgery_data.l_field.clone(),
)
.unwrap();
let forgery_auth_tag = xor_bytes(&eky0, forgery_ghash).unwrap();
if eky0.is_empty() {
eky0 = vec![0; 16];
}
Ok(CrackAnswer {
tag: BASE64_STANDARD.encode(forgery_auth_tag),
H: h_candidate.to_b64(),
mask: BASE64_STANDARD.encode(eky0),
})
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use rand::Rng;
use serde_json::json;
use utils::ciphers::{aes_128_encrypt, gcm_encrypt_aes};
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
#[test]
fn test_random() -> Result<()> {
let key = vec![1, 1, 1, 1];
let nonce = BASE64_STANDARD.decode("4gF+BtR3ku/PUQci")?;
let ad = vec![0];
let input: Vec<u8> = Vec::with_capacity(rand::thread_rng().gen_range(0..=60));
let plain1 = gcm_encrypt_aes(nonce.clone(), key.clone(), input, ad.clone())?;
let input: Vec<u8> = Vec::with_capacity(rand::thread_rng().gen_range(0..=60));
let plain2 = gcm_encrypt_aes(nonce.clone(), key.clone(), input, ad.clone())?;
let input: Vec<u8> = Vec::with_capacity(rand::thread_rng().gen_range(0..=60));
let plain3 = gcm_encrypt_aes(nonce.clone(), key.clone(), input, ad.clone())?;
let crack_input = json!({
"testcases": {
"gcm_crack46": {
"action": "gcm_crack",
"arguments": {
"nonce": "4gF+BtR3ku/PUQci",
"m1": {
"ciphertext": BASE64_STANDARD.encode(plain1.0),
"associated_data": "",
"tag": BASE64_STANDARD.encode(plain1.1)
},
"m2": {
"ciphertext": BASE64_STANDARD.encode(plain2.0),
"associated_data": "",
"tag": BASE64_STANDARD.encode(plain2.1)
},
"m3": {
"ciphertext": BASE64_STANDARD.encode(plain3.0),
"associated_data": "",
"tag": BASE64_STANDARD.encode(plain3.1)
},
"forgery": {
"ciphertext": "AXe/ZQ==",
"associated_data": ""
}
}
}
}
});
todo!();
}
}