use crate::bytes::Bytes; use crate::rsa; use crate::sha1; use crate::srp; use num_bigint::BigUint; use num_bigint::RandBigInt; use num_bigint::ToBigUint; use openssl::bn::BigNum; use openssl::sha::sha256; use rand::Rng; pub mod challenge33 { use num_bigint::BigUint; use std::fs; pub fn load_large_prime() -> BigUint { let s = fs::read_to_string("data/33.txt").expect("run from git root dir"); let b: Vec = s .chars() .filter(|c| *c != '\n') .map(|c| c.to_digit(16).unwrap().try_into().unwrap()) .collect(); BigUint::from_radix_be(&b, 16).unwrap() } } pub fn challenge33() { fn expmod(base: u32, exp: u32, m: u32) -> u32 { if exp == 0 { return 1; } if exp % 2 == 0 { let s = expmod(base, exp / 2, m); (s * s) % m } else { (expmod(base, exp - 1, m) * base) % m } } fn simple_diffie_hellman() { #![allow(non_snake_case)] let mut rng = rand::thread_rng(); // Set a variable "p" to 37 and "g" to 5. let p: u32 = 37; let g: u32 = 5; // Generate "a", a random number mod 37. Now generate "A", which is "g" raised to the "a" // power mode 37 --- A = (g**a) % p. let a: u32 = rng.gen::() % p; let A = expmod(g, a, p); // Do the same for "b" and "B". let b: u32 = rng.gen::() % p; let B = expmod(g, b, p); // "A" and "B" are public keys. Generate a session key with them; set "s" to "B" raised to // the "a" power mod 37 --- s = (B**a) % p. let s = expmod(B, a, p); // Do the same with A**b, check that you come up with the same "s". let s_ = expmod(A, b, p); assert_eq!(s, s_, "crypto is broken"); // To turn "s" into a key, you can just hash it to create 128 bits of key material (or // SHA256 it to create a key for encrypting and a key for a MAC). } fn serious_diffie_hellman() { let p = challenge33::load_large_prime(); let g = 2_u8.to_biguint().unwrap(); let a = rsa::Keypair::make(&p, &g); let b = rsa::Keypair::make(&p, &g); let s = b.public.0.modpow(&a.private.0, &p); let s_ = a.public.0.modpow(&b.private.0, &p); assert_eq!(s, s_, "crypto is broken"); } simple_diffie_hellman(); serious_diffie_hellman(); println!("[okay] Challenge 33: implemented Diffie-Hellman"); } mod challenge34 { use crate::bytes::Bytes; use crate::cbc; use crate::rsa; use crate::sha1::Sha1; use num_bigint::BigUint; pub struct Bot { p: BigUint, keypair: rsa::Keypair, pub s: Option, pub key: Option, } impl Bot { pub fn new(p: BigUint, g: &BigUint) -> Self { let keypair = rsa::Keypair::make(&p, g); Bot { p, keypair, s: None, key: None, } } pub fn get_public_key(&self) -> rsa::PublicKey { self.keypair.public.clone() } pub fn exchange_keys(&mut self, public: &rsa::PublicKey) -> rsa::PublicKey { let s = public.0.modpow(&self.keypair.private.0, &self.p); self.s = Some(s); self.make_encryption_key(); self.keypair.public.clone() } fn make_encryption_key(&mut self) { assert!(self.s.is_some(), "keys have not been exchanged"); if self.key.is_some() { return; } let mut sha1 = Sha1::default(); let key = &sha1.hash(&Bytes(self.s.clone().unwrap().to_bytes_be())).0[0..16]; self.key = Some(Bytes(key.to_vec())); } pub fn encrypt(&mut self, message: &Bytes) -> Bytes { assert!(self.key.is_some(), "keys have not been exchanged"); const BLOCK_SIZE: usize = 16; let key = self.key.as_ref().unwrap(); let mut iv = Bytes::random(16); let mut message = Bytes(message.0.clone()); message.pad_pkcs7(BLOCK_SIZE); // AES-CBC(SHA1(s)[0:16], iv=random(16), msg) + iv let mut cipher = cbc::encrypt(key, &iv, &message).0; cipher.append(&mut iv.0); Bytes(cipher) } pub fn decrypt(&mut self, cipher: &Bytes) -> Bytes { const BLOCK_SIZE: usize = 16; assert!(self.key.is_some(), "keys have not been exchanged"); let key = self.key.as_ref().unwrap(); let cipher_len = cipher.len(); let iv = Bytes(cipher.0[(cipher_len - BLOCK_SIZE)..cipher_len].to_vec()); let cipher = Bytes(cipher.0[0..cipher_len - BLOCK_SIZE].to_vec()); let mut message = cbc::decrypt(key, &iv, &cipher); message.remove_pkcs7(BLOCK_SIZE); message } } pub fn decrypt_with_s(cipher: &Bytes, s: &BigUint) -> Bytes { const BLOCK_SIZE: usize = 16; let mut sha1 = Sha1::default(); let key = Bytes(sha1.hash(&Bytes(s.to_bytes_be())).0[0..16].to_vec()); let cipher_len = cipher.len(); let iv = Bytes(cipher.0[(cipher_len - BLOCK_SIZE)..cipher_len].to_vec()); let cipher = Bytes(cipher.0[0..cipher_len - BLOCK_SIZE].to_vec()); let mut message = cbc::decrypt(&key, &iv, &cipher); message.remove_pkcs7(BLOCK_SIZE); message } } pub fn challenge34() { fn echo() { let p = challenge33::load_large_prime(); let g = 2_u8.to_biguint().unwrap(); let mut a = challenge34::Bot::new(p.clone(), &g); // A->B: Send "p", "g", "A" let mut b = challenge34::Bot::new(p, &g); // B->A: Send "B" a.exchange_keys(&b.exchange_keys(&a.get_public_key())); assert_eq!(a.s, b.s, "crypto is broken"); // A->B: Send AES-CBC(SHA1(s)[0:16], iv=random(16), msg) + iv let message_a = Bytes::from_utf8("Will this work? Let's make it longer."); let cipher_a = a.encrypt(&message_a); // B->A: Send AES-CBC(SHA1(s)[0:16], iv=random(16), A's msg) + iv let message_b = b.decrypt(&cipher_a); let cipher_b = b.encrypt(&message_b); let roundtrip = a.decrypt(&cipher_b); assert_eq!(message_a, roundtrip, "we broke it fam"); } fn echo_mitm() { let p = challenge33::load_large_prime(); let g = 2_u8.to_biguint().unwrap(); let p_public = rsa::PublicKey(p.clone()); // A->M Send "p", "g", "A" let mut a = challenge34::Bot::new(p.clone(), &g); let mut m = challenge34::Bot::new(p.clone(), &g); m.exchange_keys(&a.get_public_key()); // M->B Send "p", "g", "p" // B->M Send "B" let mut b = challenge34::Bot::new(p, &g); b.exchange_keys(&p_public); // M->A Send "p" a.exchange_keys(&p_public); // A->M Send AES-CBC(SHA1(s)[0:16], iv=random(16), msg) + iv // M->B Relay that to B let message_a = Bytes::from_utf8("Will this work again? I don't doubt it."); let cipher_a = a.encrypt(&message_a); // B->M Send AES-CBC(SHA1(s)[0:16], iv=random(16), A's msg) + iv let message_b = b.decrypt(&cipher_a); let cipher_b = b.encrypt(&message_b); // M->A Relay that to A let roundtrip = a.decrypt(&cipher_b); assert_eq!(message_a, roundtrip, "we broke it fam"); let restored_a = attack(&cipher_a); let restored_b = attack(&cipher_b); assert_eq!(message_a, restored_a, "attack didn't work"); assert_eq!(message_b, restored_b, "attack didn't work"); } fn attack(cipher: &Bytes) -> Bytes { // Do the DH math on this quickly to see what that does to the predictability of the key. // With p = A; s = (A ** b) % p becomes (p ** b) % p = 0 => s = 0 let s = 0_u8.to_biguint().unwrap(); challenge34::decrypt_with_s(cipher, &s) } echo(); echo_mitm(); println!("[okay] Challenge 34: implement MITM key-fixing attack on DH"); } pub fn challenge35() { fn echo(message: &Bytes, g: &BigUint, p: &BigUint) -> (Bytes, Bytes) { let mut a = challenge34::Bot::new(p.clone(), g); let mut b = challenge34::Bot::new(p.clone(), g); a.exchange_keys(&b.exchange_keys(&a.get_public_key())); assert_eq!(a.s, b.s, "crypto is broken"); let cipher_a = a.encrypt(message); let message_b = b.decrypt(&cipher_a); let cipher_b = b.encrypt(&message_b); let roundtrip = a.decrypt(&cipher_b); assert_eq!(*message, roundtrip, "we broke it fam"); (cipher_a, cipher_b) } fn attack_g1(cipher: &Bytes) -> Bytes { // A = (g ** a) % p, g = 1 -> A = 1 // s = (A ** b) % p, A = 1 -> s = 1 let s = 1_u8.to_biguint().unwrap(); challenge34::decrypt_with_s(cipher, &s) } fn attack_g_is_p(cipher: &Bytes) -> Bytes { // A = (g ** a) % p, g = p -> A = 0 // s = (A ** b) % p, A = 0 -> s = 0 let s = 0_u8.to_biguint().unwrap(); challenge34::decrypt_with_s(cipher, &s) } fn attack_g_is_p_minus_one(cipher: &Bytes, p: &BigUint) -> Bytes { // A = (g ** a) % p, g = p - 1 -> A = 1 | (p - 1) // s = (A ** b) % p, A = 1 | (p - 1) -> s = 1 | (p - 1) let s1 = 1_u8.to_biguint().unwrap(); let s2 = p - 1_u8.to_biguint().unwrap(); let m1 = challenge34::decrypt_with_s(cipher, &s1); let m2 = challenge34::decrypt_with_s(cipher, &s2); match m1.ascii_score().cmp(&m2.ascii_score()) { std::cmp::Ordering::Greater => m1, std::cmp::Ordering::Less => m2, std::cmp::Ordering::Equal => panic!("how can they be equal?"), } } // Do the MITM attack again, but play with "g". Write attacks for each. let m = Bytes::from_utf8("Quick to the point, to the point, no faking"); let p = challenge33::load_large_prime(); let g = 2_u8.to_biguint().unwrap(); echo(&m, &g, &p); // g = 1 let g = 1_u8.to_biguint().unwrap(); let (c1, c2) = echo(&m, &g, &p); assert_eq!(attack_g1(&c1), m, "failed to attack g = 1"); assert_eq!(attack_g1(&c2), m, "failed to attack g = 1"); // g = p let g = p.clone(); let (c1, c2) = echo(&m, &g, &p); assert_eq!(attack_g_is_p(&c1), m, "failed to attack g = p"); assert_eq!(attack_g_is_p(&c2), m, "failed to attack g = p"); // g = p - 1 let g = p.clone() - 1_u8.to_biguint().unwrap(); let (c1, c2) = echo(&m, &g, &p); assert_eq!(attack_g_is_p_minus_one(&c1, &p), m, "failed g = p - 1"); assert_eq!(attack_g_is_p_minus_one(&c2, &p), m, "failed g = p - 1"); println!("[okay] Challenge 35: implement MITM with malicious g attack on DH"); } pub fn challenge36() -> Option<()> { let mut rng = rand::thread_rng(); let mut server = srp::Server::default(); let email = "john1337@wayne.com"; let password = "horse planet carpet country"; let parameters = server.register(email, password)?; // Establish session let n = ¶meters.n; let g = ¶meters.g; let a = rng.gen_biguint_below(n); let a_public = g.modpow(&a, n); let session_keys = server.exchange(email, &a_public)?; // Client // Generate S = (B - k * g**x)**(a + u * x) % N // Generate K = SHA256(S) let u = srp::compute_u(&a_public, &session_keys.b_public)?; let x = srp::hash_password(session_keys.salt, password)?; let b_public = &session_keys.b_public; let k = ¶meters.k; let s_client = (b_public - ((k * g.modpow(&x, n)) % n)).modpow(&(a + &u * x), n); let k_client = sha256(&s_client.to_bytes_be()); // Server let k_server = server.get_k(email)?; assert_eq!(k_client, k_server); // I don't have HMAC-SHA256, so I will use HMAC-SHA1 instead. // C->S Send HMAC-SHA256(K, salt) let salt = Bytes(session_keys.salt.to_be_bytes().to_vec()); let mac = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt); assert!(server.log_in(email, &mac)?); println!("[okay] Challenge 36: implement secure remote password"); Some(()) } pub fn challenge37() -> Option<()> { let mut rng = rand::thread_rng(); let mut server = srp::Server::default(); let email = "john1337@wayne.com"; let password = "horse planet carpet country"; let parameters = server.register(email, password)?; let mut log_in_with_password = || -> Option { // Get your SRP working in an actual client-server setting. "Log in" with a // valid password using the protocol. let n = ¶meters.n; let g = ¶meters.g; let a = rng.gen_biguint_below(n); let a_public = g.modpow(&a, n); let session_keys = server.exchange(email, &a_public)?; // Client let u = srp::compute_u(&a_public, &session_keys.b_public)?; let x = srp::hash_password(session_keys.salt, password)?; let b_public = &session_keys.b_public; let k = ¶meters.k; let s_client = (b_public - ((k * g.modpow(&x, n)) % n)).modpow(&(a + &u * x), n); let k_client = sha256(&s_client.to_bytes_be()); let salt = Bytes(session_keys.salt.to_be_bytes().to_vec()); let mac = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt); server.log_in(email, &mac) }; assert!(log_in_with_password()?); let mut log_in_without_password_a_zero = || -> Option { // Now log in without your password by having the client send 0 as its "A" // value. let a_public = 0_u8.to_biguint()?; let session_keys = server.exchange(email, &a_public)?; // What does this to the "S" value that both sides compute? It becomes zero. let s_client: BigUint = 0_u8.to_biguint()?; let k_client = sha256(&s_client.to_bytes_be()); let salt = Bytes(session_keys.salt.to_be_bytes().to_vec()); let mac = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt); server.log_in(email, &mac) }; assert!(log_in_without_password_a_zero()?); let mut log_in_without_password_multiple_n = || -> Option { // Now log in without your password by having the client send N, N*2, &c. let n = ¶meters.n; let a_public = n * 2_u8.to_biguint()?; let session_keys = server.exchange(email, &a_public)?; // If A is a multiple of N, the term $(A * v**u) ** b % N$ also becomes zero. let s_client: BigUint = 0_u8.to_biguint()?; let k_client = sha256(&s_client.to_bytes_be()); let salt = Bytes(session_keys.salt.to_be_bytes().to_vec()); let mac = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt); server.log_in(email, &mac) }; assert!(log_in_without_password_multiple_n()?); println!("[okay] Challenge 37: break SRP with zero key"); Some(()) } pub fn challenge38() -> Option<()> { let mut rng = rand::thread_rng(); // Client and Server let n = challenge33::load_large_prime(); let g = 2_u8.to_biguint()?; let password = "bar"; // Server: x = SHA256(salt|password); v = g**x % n; let salt: u32 = rng.gen(); let x = srp::hash_password(salt, &password)?; let v = g.modpow(&x, &n); // C->C: I, A = g**a % n; let a = rng.gen_biguint_below(&n); let a_public = g.modpow(&a, &n); // S->C: salt, B = g**b % n, u = 128 bit random number // Pose as the server and use arbitrary values for b, B, u, and salt. let b = 11_u8.to_biguint()?; let b_public = g.modpow(&b, &n); let u: u128 = rng.gen(); // Client: x = SHA256(salt|password); S = B**(a + ux) % n; K = SHA256(S); let x = srp::hash_password(salt, &password)?; let s_client = b_public.modpow(&(a + &u * x), &n); let k_client = sha256(&s_client.to_bytes_be()); // Server: S = (A * v ** u)**b % n; K = SHA256(S); let s_server = (&a_public * v.modpow(&u.to_biguint()?, &n)).modpow(&b, &n); let k_server = sha256(&s_server.to_bytes_be()); assert_eq!(k_client, k_server); // C->S // Send HMAC-SHA256(K, salt) let salt_bytes = Bytes(salt.to_be_bytes().to_vec()); let mac_client = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt_bytes); // S->C let mac_server = sha1::hmac_sha1(&Bytes(k_server.to_vec()), &salt_bytes); assert_eq!(mac_server, mac_client, "HMAC verification failed"); fn crack_password( mac_client: &Bytes, salt: u32, a_public: &BigUint, u: u128, ) -> Option { // Now, run the protocol as a MITM attacker: pose as the server and use arbitrary values // for b, B, u, and salt. Crack the password from A's HMAC-SHA256(K, salt). let dict = ["foo", "bar", "lol"]; let g = 2_u8.to_biguint()?; let n = challenge33::load_large_prime(); let b = 11_u8.to_biguint()?; let salt_bytes = Bytes(salt.to_be_bytes().to_vec()); for password in dict { let x = srp::hash_password(salt, &password)?; let v = g.modpow(&x, &n); let s_attack = (a_public * v.modpow(&u.to_biguint()?, &n)).modpow(&b, &n); let k_attack = sha256(&s_attack.to_bytes_be()); let mac_attack = sha1::hmac_sha1(&Bytes(k_attack.to_vec()), &salt_bytes); if mac_attack == *mac_client { return Some(password.to_owned()); } } None } let cracked_password = crack_password(&mac_client, salt, &a_public, u); match cracked_password { Some(p) if p == password => { println!("[okay] Challenge 38: offline dictionary attack on SRP"); } _ => println!("[FAIL] Challenge 38: offline dictionary attack on SRP"), }; Some(()) } pub fn challenge39() -> Option<()> { // I recommend you not bother with primegen, // but do take the time to get your own EGCD and // invmod algorithm working. let a = BigNum::from_u32(17).ok()?; let n = BigNum::from_u32(3120).ok()?; let r = BigNum::from_u32(2753).ok()?; assert_eq!(rsa::invmod(&a, &n).ok()?, r, "invmod does not work"); let (public_key, private_key) = rsa::rsa_gen_keys().ok()?; // Test this out with a number, like "42". let i = BigNum::from_u32(1337).ok()?; let c = rsa::rsa_encrypt(&i, &public_key).ok()?; let m = rsa::rsa_decrypt(&c, &private_key).ok()?; assert_eq!(i, m, "rsa is broken"); let i = "Party, party, party, all night long. Yuah!"; let c = rsa::rsa_encrypt_str(&i, &public_key).ok()?; let m = rsa::rsa_decrypt_str(&c, &private_key).ok()?; assert_eq!(i, &m, "string rsa is broken"); println!("[okay] Challenge 39: implement RSA"); Some(()) } pub fn challenge40() -> Option<()> { let m = BigNum::from_u32(1337).ok()?; // 1. Capturing any 3 of the ciphertexts and their corresponding pubkeys let (p_0, _) = rsa::rsa_gen_keys().ok()?; let (p_1, _) = rsa::rsa_gen_keys().ok()?; let (p_2, _) = rsa::rsa_gen_keys().ok()?; let c_0 = rsa::rsa_encrypt(&m, &p_0).ok()?; let c_1 = rsa::rsa_encrypt(&m, &p_1).ok()?; let c_2 = rsa::rsa_encrypt(&m, &p_2).ok()?; // 2. Using the CRT to solve for the number represented by the three ciphertexts (which are // residues mod their respective pubkeys) let rsa::RsaPublicKey { e: _, n: n_0 } = p_0; let rsa::RsaPublicKey { e: _, n: n_1 } = p_1; let rsa::RsaPublicKey { e: _, n: n_2 } = p_2; // N_012 is the product of all three moduli let n_012 = &(&n_0 * &n_1) * &n_2; let m_s_0 = &n_1 * &n_2; let m_s_1 = &n_0 * &n_2; let m_s_2 = &n_0 * &n_1; // result = // (c_0 * m_s_0 * invmod(m_s_0, n_0)) + // (c_1 * m_s_1 * invmod(m_s_1, n_1)) + // (c_2 * m_s_2 * invmod(m_s_2, n_2)) mod N_012 let result = &(&(&(&(&c_0 * &m_s_0) * &rsa::invmod(&m_s_0, &n_0).ok()?) + &(&(&c_1 * &m_s_1) * &rsa::invmod(&m_s_1, &n_1).ok()?)) + &(&(&c_2 * &m_s_2) * &rsa::invmod(&m_s_2, &n_2).ok()?)) % &n_012; // 3. Taking the cube root of the resulting number fn cube_root(n: &BigNum) -> Option { let b = BigUint::from_bytes_be(&n.to_vec()); let b = b.nth_root(3); BigNum::from_slice(&b.to_bytes_be()).ok() } let m_cubed = &(&m * &m) * &m; assert_eq!(m_cubed, result, "CRT implementation did not work"); let c = cube_root(&result)?; assert_eq!(c, m, "cube root implementation did not work"); println!("[okay] Challenge 40: implement an E=3 RSA Broadcast attack"); Some(()) }