From a1de52bfd2c58161d0b425d0125c63160e00765d Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Thu, 6 Oct 2022 17:05:46 -0400 Subject: [PATCH] Refactor challenge 36 to match SRP protocol better --- src/main.rs | 2 +- src/parser.rs | 3 +- src/set5.rs | 89 +++++++++++------------------------ src/srp.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 64 deletions(-) create mode 100644 src/srp.rs diff --git a/src/main.rs b/src/main.rs index ced6911..ec6b617 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ #![allow(clippy::items_after_statements)] #![allow(clippy::many_single_char_names)] #![allow(clippy::module_name_repetitions)] -#![allow(clippy::unnested_or_patterns)] mod bytes; mod bytes_base64; mod cbc; @@ -21,6 +20,7 @@ mod set3; mod set4; mod set5; mod sha1; +mod srp; mod utils; fn main() { diff --git a/src/parser.rs b/src/parser.rs index 5acef98..e43b81b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -17,8 +17,7 @@ pub fn parse_key_value(text: &str) -> HashMap { tokens = scan(text, 0, tokens); for token_chunk in tokens.chunks(4) { match token_chunk { - [Token::Identifier(key), Token::Equal, Token::Identifier(value), Token::Ampersand] - | [Token::Identifier(key), Token::Equal, Token::Identifier(value), Token::Semicolon] + [Token::Identifier(key), Token::Equal, Token::Identifier(value), Token::Ampersand | Token::Semicolon] | [Token::Identifier(key), Token::Equal, Token::Identifier(value)] => { result.insert(key.to_string(), value.to_string()); } diff --git a/src/set5.rs b/src/set5.rs index ed52866..b01494e 100644 --- a/src/set5.rs +++ b/src/set5.rs @@ -1,13 +1,14 @@ 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::sha::sha256; use rand::Rng; -mod challenge33 { +pub mod challenge33 { use num_bigint::BigUint; use std::fs; @@ -317,80 +318,46 @@ pub fn challenge35() { println!("[okay] Challenge 35: implement MITM with malicious g attack on DH"); } -pub fn challenge36() { +pub fn challenge36() -> Option<()> { let mut rng = rand::thread_rng(); - // C & S - // Agree on N=[NIST Prime], g=2, k=3, I (email), P (password) - let n = challenge33::load_large_prime(); - let g = 2_u8.to_biguint().unwrap(); - let k = 3_u8.to_biguint().unwrap(); - let _i = Bytes::from_utf8("john1337@wayne.com"); - let p = Bytes::from_utf8("horse planet carpet country"); + let mut server = srp::Server::default(); + let email = "john1337@wayne.com"; + let password = "horse planet carpet country"; + let parameters = server.register(email, password)?; - // S - // Generate salt as random integer - // Generate string xH=SHA256(salt|password) - // Convert xH to integer x somehow (put 0x on hexdigest) - // Generate v=g**x % N - // Save everything but x, xH - let salt: u32 = rng.gen(); - let mut salt_password = salt.to_be_bytes().to_vec(); - salt_password.append(&mut p.0.clone()); - let xh = sha256(&salt_password); - let x = BigUint::from_bytes_be(xh[0..4].try_into().unwrap()); - let v = g.modpow(&x, &n); + // 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)?; - // C->S - // Send I, A=g**a % N (a la Diffie Hellman) - let a = rng.gen_biguint_below(&n); - let a_public = g.modpow(&a, &n); - - // S->C - // Send salt, B=kv + g**b % N - let b = rng.gen_biguint_below(&n); - let b_public = k.clone() * v.clone() + g.modpow(&b, &n); - - // S, C - // Compute string uH = SHA256(A|B), u = integer of uH - let mut a_b = a_public.to_bytes_be(); - a_b.append(&mut b_public.to_bytes_be()); - let uh = sha256(&a_b); - let u = BigUint::from_bytes_be(uh[0..4].try_into().unwrap()); - - // C - // Generate string xH=SHA256(salt|password) - // Convert xH to integer x somehow (put 0x on hexdigest) - // Generate S = (B - k * g**x)**(a + u * x) % N - // Generate K = SHA256(S) - let mut salt_password = salt.to_be_bytes().to_vec(); - salt_password.append(&mut p.0.clone()); - let xh = sha256(&salt_password); - let x = BigUint::from_bytes_be(xh[0..4].try_into().unwrap()); - let s = (b_public - k * g.modpow(&x, &n)).modpow(&(a + &u * x), &n); - let k_client = sha256(&s.to_bytes_be()); - - // S - // Generate S = (A * v**u) ** b % N - // Generate K = SHA256(S) - let s = (a_public * v.modpow(&u, &n)).modpow(&b, &n); - let k_server = sha256(&s.to_bytes_be()); + // 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(salt.to_be_bytes().to_vec()); + // C->S Send HMAC-SHA256(K, salt) + let salt = Bytes(session_keys.salt.to_be_bytes().to_vec()); let mac_server = sha1::hmac_sha1(&Bytes(k_server.to_vec()), &salt); - // S->C - // Send "OK" if HMAC-SHA256(K, salt) validates + // S->C Send "OK" if HMAC-SHA256(K, salt) validates let mac_client = sha1::hmac_sha1(&Bytes(k_client.to_vec()), &salt); assert_eq!(mac_server, mac_client, "HMAC verification failed"); println!("[okay] Challenge 36: implement secure remote password"); + Some(()) } pub fn challenge37() { diff --git a/src/srp.rs b/src/srp.rs new file mode 100644 index 0000000..9e6d876 --- /dev/null +++ b/src/srp.rs @@ -0,0 +1,125 @@ +use crate::set5; +use num_bigint::BigUint; +use num_bigint::RandBigInt; +use num_bigint::ToBigUint; +use openssl::sha::sha256; +use rand::Rng; +use std::collections::HashMap; + +pub struct Parameters { + pub n: BigUint, + pub g: BigUint, + pub k: BigUint, +} + +pub struct SessionKeys { + pub salt: u32, + pub b_public: BigUint, +} + +struct PasswordEntry { + salt: u32, + n: BigUint, + g: BigUint, + k: BigUint, + v: BigUint, + a_public: Option, + b: Option, + b_public: Option, +} + +#[derive(Default)] +pub struct Server { + entries: HashMap, +} + +pub fn compute_u(a_public: &BigUint, b_public: &BigUint) -> Option { + let mut a_b = a_public.to_bytes_be(); + a_b.append(&mut b_public.to_bytes_be()); + let uh = sha256(&a_b); + let u = BigUint::from_bytes_be(uh[0..4].try_into().ok()?); + Some(u) +} + +pub fn hash_password(salt: u32, password: &str) -> Option { + // Generate string xH=SHA256(salt|password) + // Convert xH to integer x somehow (put 0x on hexdigest) + let mut salt_password: Vec = salt.to_be_bytes().to_vec(); + salt_password.append(&mut password.as_bytes().to_vec()); + let xh = sha256(&salt_password); + let x = BigUint::from_bytes_be(xh[0..4].try_into().ok()?); + Some(x) +} + +impl Server { + pub fn register(&mut self, email: &str, password: &str) -> Option { + if self.entries.contains_key(email) { + return None; + } + let mut rng = rand::thread_rng(); + + // Agree on N=[NIST Prime], g=2, k=3 + let n = set5::challenge33::load_large_prime(); + let g = 2_u8.to_biguint()?; + let k = 3_u8.to_biguint()?; + + // Generate salt as random integer + let salt: u32 = rng.gen(); + let x = hash_password(salt, password)?; + + // Generate v=g**x % N + // Save everything but x, xH + let v = g.modpow(&x, &n); + + let password_entry = PasswordEntry { + salt, + n: n.clone(), + g: g.clone(), + k: k.clone(), + v, + a_public: None, + b: None, + b_public: None, + }; + self.entries.insert(email.to_string(), password_entry); + Some(Parameters { n, g, k }) + } + + pub fn exchange(&mut self, email: &str, a_public: &BigUint) -> Option { + // C->S Send I, A=g**a % N (a la Diffie Hellman) + let mut password_entry = self.entries.get_mut(email)?; + password_entry.a_public = Some(a_public.clone()); + + let mut rng = rand::thread_rng(); + let n = &password_entry.n; + let k = &password_entry.k; + let v = &password_entry.v; + let g = &password_entry.g; + + // S->C Send salt, B=kv + g**b % N + let b = rng.gen_biguint_below(n); + let b_public = (k.clone() * v.clone() % n) + g.modpow(&b, n); + + password_entry.b = Some(b); + password_entry.b_public = Some(b_public.clone()); + Some(SessionKeys { + salt: password_entry.salt, + b_public, + }) + } + + pub fn get_k(&mut self, email: &str) -> Option<[u8; 32]> { + let password_entry = self.entries.get(email)?; + let a_public = password_entry.a_public.as_ref()?; + let u = compute_u(a_public, password_entry.b_public.as_ref()?)?; + let n = &password_entry.n; + let v = &password_entry.v; + let b = &password_entry.b.as_ref()?; + + // Generate S = (A * v**u) ** b % N + // Generate K = SHA256(S) + let s_server = (a_public * v.modpow(&u, n)).modpow(b, n); + let k_server = sha256(&s_server.to_bytes_be()); + Some(k_server) + } +}