diff --git a/src/main.rs b/src/main.rs index 69ec6ee..5ec909a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,13 +4,16 @@ mod cbc; mod ctr; mod ecb; mod mt19937; +mod mtcipher; mod parser; mod set1; mod set2; mod set3; +mod set4; +mod utils; fn main() { - const RUN_ALL: bool = true; + const RUN_ALL: bool = false; if RUN_ALL { set1::challenge1(); set1::challenge2(); @@ -36,7 +39,8 @@ fn main() { set3::challenge22(); set3::challenge23(); set3::challenge24(); + set4::challenge25(); } else { - set3::challenge24(); + set4::challenge25(); } } diff --git a/src/mt19937.rs b/src/mt19937.rs index c5ca01a..8c55827 100644 --- a/src/mt19937.rs +++ b/src/mt19937.rs @@ -34,6 +34,11 @@ impl MT19937 { self.twist(); } + pub fn extract_bytes(&mut self) -> [u8; 4] { + let n = self.extract_number(); + n.to_ne_bytes() + } + pub fn extract_number(&mut self) -> u32 { if self.index == N { self.twist(); @@ -60,10 +65,9 @@ impl MT19937 { fn twist(&mut self) { const M: usize = 397; - const R: u32 = 31; - const A: u32 = 0x9908B0DF; - const LOWER_MASK: u32 = (1 << R) - 1; // 0x7fffffff - const UPPER_MASK: u32 = !LOWER_MASK; // 0x80000000 + const A: u32 = 0x9908_B0DF; + const LOWER_MASK: u32 = 0x7fff_ffff; + const UPPER_MASK: u32 = 0x8000_0000; const FIRST_HALF: usize = N - M; for i in 0..FIRST_HALF { diff --git a/src/mtcipher.rs b/src/mtcipher.rs new file mode 100644 index 0000000..b11cdac --- /dev/null +++ b/src/mtcipher.rs @@ -0,0 +1,26 @@ +use crate::{bytes::Bytes, mt19937}; + +pub fn decrypt(key: u16, data: &Bytes) -> Bytes { + encrypt(key, data) +} + +pub fn encrypt(key: u16, Bytes(data): &Bytes) -> Bytes { + // You can create a trivial stream cipher out of any PRNG; use it to + // generate a sequence of 8 bit outputs and call those outputs a keystream. + // XOR each byte of plaintext with each successive byte of keystream. + // Write the function that does this for MT19937 using a 16-bit seed. + + let mut mt = mt19937::MT19937::new(); + mt.seed(key as u32); + let mut result: Vec = vec![]; + + for chunk in data.chunks(4) { + let key = mt.extract_bytes(); + for i in 0..chunk.len() { + let cipher_char = chunk[i] ^ key[i]; + result.push(cipher_char); + } + } + + Bytes(result) +} diff --git a/src/set3.rs b/src/set3.rs index 2148b7f..adcedc6 100644 --- a/src/set3.rs +++ b/src/set3.rs @@ -3,12 +3,13 @@ use crate::bytes_base64::BytesBase64; use crate::cbc; use crate::ctr; use crate::mt19937; +use crate::mtcipher; +use crate::utils; use rand::Rng; use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; use std::io::{BufRead, BufReader}; -use std::time::{SystemTime, UNIX_EPOCH}; pub fn challenge17() { fn read(path: &str) -> Vec { @@ -317,17 +318,8 @@ pub fn challenge21() { } pub fn challenge22() { - // let mut mt = mt19937::MT19937::new(); - fn unix_timestamp() -> u32 { - let start = SystemTime::now(); - let since_the_epoch = start - .duration_since(UNIX_EPOCH) - .expect("Time went backwards"); - since_the_epoch.as_secs() as u32 - } - // Wait a random number of seconds between, I don't know, 40 and 1000. - let now = unix_timestamp(); + let now = utils::unix_timestamp(); let wait_time: u32 = rand::thread_rng().gen_range(40..1000); let seed = now + wait_time; @@ -341,7 +333,7 @@ pub fn challenge22() { // From the 32 bit RNG output, discover the seed. fn find_seed(rngout: u32) -> Option { let mut mt = mt19937::MT19937::new(); - let start = unix_timestamp() - 2000; + let start = utils::unix_timestamp() - 2000; for seed in start..(start + 4000) { mt.seed(seed); if rngout == mt.extract_number() { @@ -448,5 +440,93 @@ pub fn challenge23() { } pub fn challenge24() { - println!("[xxxx] Challenge 24: xxx"); + // Verify that you can encrypt and decrypt properly. This code should look + // similar to your CTR code. + let key: u16 = 111; + let cleartext = Bytes::from_utf8("Let's see if we can get the party started hard my friends."); + let cipher = mtcipher::encrypt(key, &cleartext); + let roundtrip = mtcipher::decrypt(key, &cipher); + assert_eq!(cleartext, roundtrip); + + // Use your function to encrypt a known plaintext (say, 14 consecutive 'A' + // characters) prefixed by a random number of random characters. + fn get_plaintext() -> Bytes { + let length: usize = rand::thread_rng().gen_range(30..100); + let mut data = Bytes::random(length); + data.0.append(&mut Bytes(vec![b'A'; 14]).0); + data + } + let key: u16 = rand::thread_rng().gen::(); + let plaintext = get_plaintext(); + let cipher = mtcipher::encrypt(key, &plaintext); + + // From the ciphertext, recover the "key" (the 16 bit seed). + fn recover_key(cipher: &Bytes) -> u16 { + let cipher_len = cipher.len(); + // brute force bb! + for key in 0..u16::MAX { + let mut found_key = true; + let roundtrip = mtcipher::decrypt(key, &cipher); + // check if the last 14 chars are 'A' - if yes, we found the key + for i in (cipher_len - 14)..cipher_len { + if roundtrip.0[i] != b'A' { + found_key = false; + break; + } + } + if found_key { + return key; + } + } + 0 + } + let recovered_key = recover_key(&cipher); + assert_eq!(key, recovered_key); + + // Use the same idea to generate a random "password reset token" using + // MT19937 seeded from the current time. + fn get_reset_token(time: Option) -> Bytes { + const TOKEN_LENGTH: usize = 16; + let time = match time { + Some(time) => time, + None => utils::unix_timestamp(), + }; + let mut token = vec![]; + let mut mt = mt19937::MT19937::new(); + mt.seed(time); + + while token.len() < (TOKEN_LENGTH - 1) { + for b in mt.extract_bytes() { + if token.len() >= TOKEN_LENGTH { + break; + } + if b.is_ascii_alphanumeric() { + token.push(b); + } + } + } + Bytes(token) + } + + let token = get_reset_token(None); + // println!("{}", token.to_utf8()); + + // Write a function to check if any given password token is actually the + // product of an MT19937 PRNG seeded with the current time. + fn is_time_token(token: &Bytes) -> bool { + let current_time = utils::unix_timestamp(); + for time in (current_time - 10)..(current_time + 10) { + let time_token = get_reset_token(Some(time)); + if *token == time_token { + return true; + } + } + false + } + + assert_eq!(is_time_token(&token), true); + let non_token = Bytes(vec![b'z', 16]); + assert_eq!(is_time_token(&non_token), false); + + println!("[okay] Challenge 24: MT19937 stream cipher implemented and cracked"); } diff --git a/src/set4.rs b/src/set4.rs new file mode 100644 index 0000000..4db2f3b --- /dev/null +++ b/src/set4.rs @@ -0,0 +1,3 @@ +pub fn challenge25() { + println!("[xxxx] Challenge 25: TBD"); +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..0ec0e51 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,9 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +pub fn unix_timestamp() -> u32 { + let start = SystemTime::now(); + let since_the_epoch = start + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + since_the_epoch.as_secs() as u32 +}