Clean up and consider BigUint as alternative to BigNum for RSA implementation

This commit is contained in:
2023-01-29 12:57:03 -05:00
parent 6abf32c361
commit edbe2144ae
7 changed files with 190 additions and 140 deletions

93
src/dh.rs Normal file
View File

@@ -0,0 +1,93 @@
// Implementations of DiffieHellman 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<u8> = 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::<u32>() % p;
let A = expmod(g, a, p);
// Do the same for "b" and "B".
let b: u32 = rng.gen::<u32>() % 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");
}

View File

@@ -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"));

View File

@@ -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<BigNum, ErrorStack> {
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<BigUint> {
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<BigNum, ErrorStack> {
fn extended_gcd(a: BigNum, b: BigNum) -> Result<(BigNum, BigNum, BigNum), ErrorStack> {
// credit: https://www.dcode.fr/extended-gcd

View File

@@ -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<u8> = 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::<u32>() % p;
let A = expmod(g, a, p);
// Do the same for "b" and "B".
let b: u32 = rng.gen::<u32>() % 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<BigUint>,
pub key: Option<Bytes>,
}
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()?;

View File

@@ -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<BigNum, ErrorStack> {
let solve = |mut cipher: BigNum, n: BigNum| -> Result<BigNum, ErrorStack> {
// - 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(())

View File

@@ -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()?;

View File

@@ -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();