diff --git a/src/dh.rs b/src/dh.rs new file mode 100644 index 0000000..af69f57 --- /dev/null +++ b/src/dh.rs @@ -0,0 +1,93 @@ +// Implementations of Diffie–Hellman key exchange +use num_bigint::BigUint; +use num_bigint::RandBigInt; +use rand::Rng; +use std::fs; + +#[derive(Clone)] +pub struct PublicKey(pub BigUint); + +#[derive(Clone)] +pub struct PrivateKey(pub BigUint); + +#[derive(Clone)] +pub struct Keypair { + pub private: PrivateKey, + pub public: PublicKey, +} + +impl Keypair { + pub fn make(p: &BigUint, g: &BigUint) -> Self { + let mut rng = rand::thread_rng(); + let private = rng.gen_biguint_below(p); + let public = g.modpow(&private, p); + Self { + private: PrivateKey(private), + public: PublicKey(public), + } + } +} + +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 + } +} + +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 simple() { + #![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). +} + +pub fn serious() { + let p = load_large_prime(); + let g = 2_u8.into(); + + let a = Keypair::make(&p, &g); + let b = 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"); +} diff --git a/src/main.rs b/src/main.rs index 5995a9b..4bdd98d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod bytes; mod bytes_base64; mod cbc; mod ctr; +mod dh; mod dsa; mod ecb; mod md4; @@ -27,7 +28,7 @@ mod srp; mod utils; fn main() { - const RUN_ALL: bool = true; + const RUN_ALL: bool = false; if RUN_ALL { set1::challenge1(); set1::challenge2(); @@ -61,17 +62,17 @@ fn main() { set4::challenge30(); set4::challenge31(); set4::challenge32(); - set5::challenge33(); - set5::challenge34(); - set5::challenge35(); - set5::challenge36().unwrap_or_else(|| println!("[fail] challenge 36")); - set5::challenge37().unwrap_or_else(|| println!("[fail] challenge 37")); - set5::challenge38().unwrap_or_else(|| println!("[fail] challenge 38")); - set5::challenge39().unwrap_or_else(|| println!("[fail] challenge 39")); - set5::challenge40().unwrap_or_else(|| println!("[fail] challenge 40")); - set6::challenge41().unwrap_or_else(|_| println!("[fail] challenge 41")); - set6::challenge42().unwrap_or_else(|_| println!("[fail] challenge 42")); } + set5::challenge33(); + set5::challenge34(); + set5::challenge35(); + set5::challenge36().unwrap_or_else(|| println!("[fail] challenge 36")); + set5::challenge37().unwrap_or_else(|| println!("[fail] challenge 37")); + set5::challenge38().unwrap_or_else(|| println!("[fail] challenge 38")); + set5::challenge39().unwrap_or_else(|| println!("[fail] challenge 39")); + set5::challenge40().unwrap_or_else(|| println!("[fail] challenge 40")); + set6::challenge41().unwrap_or_else(|_| println!("[fail] challenge 41")); + set6::challenge42().unwrap_or_else(|_| println!("[fail] challenge 42")); set6::challenge43().unwrap_or_else(|| println!("[fail] challenge 43")); set6::challenge44().unwrap_or_else(|| println!("[fail] challenge 44")); set6::challenge45().unwrap_or_else(|| println!("[fail] challenge 45")); diff --git a/src/rsa.rs b/src/rsa.rs index a0c782b..23e372b 100644 --- a/src/rsa.rs +++ b/src/rsa.rs @@ -1,36 +1,13 @@ use crate::utils::bnclone; +use num_bigint::BigInt; use num_bigint::BigUint; -use num_bigint::RandBigInt; +use num_bigint::ToBigInt; use openssl::bn::BigNum; use openssl::bn::BigNumContext; use openssl::bn::MsbOption; use openssl::error::ErrorStack; use openssl::sha::sha256; -#[derive(Clone)] -pub struct PublicKey(pub BigUint); - -#[derive(Clone)] -pub struct PrivateKey(pub BigUint); - -#[derive(Clone)] -pub struct Keypair { - pub private: PrivateKey, - pub public: PublicKey, -} - -impl Keypair { - pub fn make(p: &BigUint, g: &BigUint) -> Self { - let mut rng = rand::thread_rng(); - let private = rng.gen_biguint_below(p); - let public = g.modpow(&private, p); - Self { - private: PrivateKey(private), - public: PublicKey(public), - } - } -} - pub struct RsaPublicKey { pub e: BigNum, pub n: BigNum, @@ -51,7 +28,10 @@ fn generate_random_prime(bits: i32) -> Result { Ok(p) } -pub fn rsa_gen_keys_with_size(p_bits: i32, q_bits: i32) -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> { +pub fn rsa_gen_keys_with_size( + p_bits: i32, + q_bits: i32, +) -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> { let mut ctx = BigNumContext::new()?; loop { @@ -87,6 +67,43 @@ pub fn rsa_gen_keys() -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> { rsa_gen_keys_with_size(512, 512) } +fn extended_gcd(a: &BigUint, b: &BigUint) -> (BigInt, BigInt, BigInt) { + // https://brilliant.org/wiki/extended-euclidean-algorithm/ + assert!(a < b, "a has to be smaller than b for extended GCD"); + let mut a: BigInt = a.clone().into(); + let mut b: BigInt = b.clone().into(); + + let mut x: BigInt = 0_i32.into(); + let mut y: BigInt = 1_i32.into(); + let mut u: BigInt = 1_i32.into(); + let mut v: BigInt = 0_i32.into(); + + while a != 0_u32.into() { + let (q, r) = (&b / &a, &b % &a); + let m = &x - &u * &q; + let n = &y - &v * &q; + b = a; + a = r; + x = u; + y = v; + u = m; + v = n; + } + (b, x, y) +} + +pub fn invmod_biguint(a: &BigUint, n: &BigUint) -> Option { + let r = extended_gcd(a, n); + match r { + (_, _, v1) if v1 == 0_i32.into() => None, + (_, u1, _) => { + let n = n.clone().to_bigint()?; + let r = ((&u1 % &n) + &n) % &n; + r.to_biguint() + } + } +} + pub fn invmod(a: &BigNum, n: &BigNum) -> Result { fn extended_gcd(a: BigNum, b: BigNum) -> Result<(BigNum, BigNum, BigNum), ErrorStack> { // credit: https://www.dcode.fr/extended-gcd diff --git a/src/set5.rs b/src/set5.rs index 41dc340..6d42c41 100644 --- a/src/set5.rs +++ b/src/set5.rs @@ -1,4 +1,5 @@ use crate::bytes::Bytes; +use crate::dh; use crate::rsa; use crate::sha1; use crate::srp; @@ -9,98 +10,29 @@ 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(); + dh::simple(); + dh::serious(); println!("[okay] Challenge 33: implemented Diffie-Hellman"); } mod challenge34 { use crate::bytes::Bytes; use crate::cbc; - use crate::rsa; + use crate::dh; use crate::sha1::Sha1; use num_bigint::BigUint; pub struct Bot { p: BigUint, - keypair: rsa::Keypair, + keypair: dh::Keypair, pub s: Option, pub key: Option, } impl Bot { pub fn new(p: BigUint, g: &BigUint) -> Self { - let keypair = rsa::Keypair::make(&p, g); + let keypair = dh::Keypair::make(&p, g); Bot { p, keypair, @@ -109,11 +41,11 @@ mod challenge34 { } } - pub fn get_public_key(&self) -> rsa::PublicKey { + pub fn get_public_key(&self) -> dh::PublicKey { self.keypair.public.clone() } - pub fn exchange_keys(&mut self, public: &rsa::PublicKey) -> rsa::PublicKey { + pub fn exchange_keys(&mut self, public: &dh::PublicKey) -> dh::PublicKey { let s = public.0.modpow(&self.keypair.private.0, &self.p); self.s = Some(s); self.make_encryption_key(); @@ -176,8 +108,8 @@ mod challenge34 { pub fn challenge34() { fn echo() { - let p = challenge33::load_large_prime(); - let g = 2_u8.to_biguint().unwrap(); + let p = dh::load_large_prime(); + let g: BigUint = 2_u8.into(); let mut a = challenge34::Bot::new(p.clone(), &g); @@ -200,9 +132,9 @@ pub fn challenge34() { } fn echo_mitm() { - let p = challenge33::load_large_prime(); - let g = 2_u8.to_biguint().unwrap(); - let p_public = rsa::PublicKey(p.clone()); + let p = dh::load_large_prime(); + let g = 2_u8.into(); + let p_public = dh::PublicKey(p.clone()); // A->M Send "p", "g", "A" let mut a = challenge34::Bot::new(p.clone(), &g); @@ -239,7 +171,7 @@ pub fn challenge34() { 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(); + let s = 0_u8.into(); challenge34::decrypt_with_s(cipher, &s) } @@ -266,22 +198,22 @@ pub fn challenge35() { 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(); + let s = 1_u8.into(); 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(); + let s = 0_u8.into(); 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 s1: BigUint = 1_u8.into(); + let s2: BigUint = p - &s1; let m1 = challenge34::decrypt_with_s(cipher, &s1); let m2 = challenge34::decrypt_with_s(cipher, &s2); @@ -294,7 +226,7 @@ pub fn challenge35() { // 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 p = dh::load_large_prime(); let g = 2_u8.to_biguint().unwrap(); echo(&m, &g, &p); @@ -432,8 +364,8 @@ 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 n = dh::load_large_prime(); + let g: BigUint = 2_u8.into(); let password = "bar"; // Server: x = SHA256(salt|password); v = g**x % n; @@ -480,9 +412,9 @@ pub fn challenge38() -> 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 g: BigUint = 2_u8.into(); + let n = dh::load_large_prime(); + let b: BigUint = 11_u8.into(); let salt_bytes = Bytes(salt.to_be_bytes().to_vec()); for password in dict { @@ -511,8 +443,12 @@ pub fn challenge38() -> Option<()> { } pub fn challenge39() -> Option<()> { - // I recommend you not bother with primegen, - // but do take the time to get your own EGCD and + let a: BigUint = 17_u32.into(); + let n: BigUint = 3120_u32.into(); + let r: BigUint = 2753_u32.into(); + assert_eq!(rsa::invmod_biguint(&a, &n)?, r, "invmod does not work"); + + // 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()?; diff --git a/src/set6.rs b/src/set6.rs index ca070cf..850c41d 100644 --- a/src/set6.rs +++ b/src/set6.rs @@ -364,7 +364,6 @@ pub fn challenge46() -> Result<(), ErrorStack> { let c_b = rsa::rsa_encrypt_unpadded(&BigNum::from_u32(98)?, &public_key)?; assert!(parity_oracle(&c_b), "parity oracle even doesn't work"); - // Double by multiplying with (2**e) % n let mut ctx = BigNumContext::new()?; let two = BigNum::from_u32(2)?; @@ -374,8 +373,7 @@ pub fn challenge46() -> Result<(), ErrorStack> { return &(&c * &multiplier) % &private_key.n; }; - - let solve = |mut cipher: BigNum, n: BigNum | -> Result { + let solve = |mut cipher: BigNum, n: BigNum| -> Result { // - You can repeatedly apply this heuristic, once per bit of the message, checking your oracle // function each time. // - Your decryption function starts with bounds for the plaintext of [0,n]. @@ -410,7 +408,6 @@ pub fn challenge46() -> Result<(), ErrorStack> { } pub fn challenge47() -> Result<(), ErrorStack> { - // Generate a 256 bit keypair (that is, p and q will each be 128 bit primes), [n, e, d]. let (public_key, private_key) = rsa::rsa_gen_keys_with_size(128, 128)?; @@ -432,8 +429,14 @@ pub fn challenge47() -> Result<(), ErrorStack> { let c_unpadded = rsa::rsa_encrypt_unpadded(&m, &public_key)?; let c = rsa::rsa_encrypt(&m, &public_key)?; - assert!(!oracle(&c_unpadded), "oracle wrongly thinks unpadded message is padded"); - assert!(oracle(&c), "oracle wrongly thinks padded message is not padded"); + assert!( + !oracle(&c_unpadded), + "oracle wrongly thinks unpadded message is padded" + ); + assert!( + oracle(&c), + "oracle wrongly thinks padded message is not padded" + ); println!("[xxxx] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)"); Ok(()) diff --git a/src/srp.rs b/src/srp.rs index 20c215c..69ea35e 100644 --- a/src/srp.rs +++ b/src/srp.rs @@ -1,5 +1,5 @@ use crate::bytes::Bytes; -use crate::set5; +use crate::dh; use crate::sha1; use num_bigint::BigUint; use num_bigint::RandBigInt; @@ -61,7 +61,7 @@ impl Server { let mut rng = rand::thread_rng(); // Agree on N=[NIST Prime], g=2, k=3 - let n = set5::challenge33::load_large_prime(); + let n = dh::load_large_prime(); let g = 2_u8.to_biguint()?; let k = 3_u8.to_biguint()?; diff --git a/src/utils.rs b/src/utils.rs index b50f1aa..c40b928 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,9 @@ use crate::{bytes::Bytes, bytes_base64::BytesBase64}; +use openssl::bn::BigNum; use std::{ io::{BufRead, BufReader}, time::{SystemTime, UNIX_EPOCH}, }; -use openssl::bn::BigNum; pub fn unix_timestamp() -> u32 { let start = SystemTime::now();