Finish challenge set 6, original challenges completed

This commit is contained in:
2023-02-05 17:42:58 -05:00
parent 1f2751a2f7
commit d81a8c3c30
5 changed files with 293 additions and 61 deletions

View File

@@ -1,3 +1,58 @@
# cryptopals # cryptopals
My solutions to the [cryptopals](https://cryptopals.com/) challenges. My solutions to the [cryptopals](https://cryptopals.com/) challenges.
As of February 2023, after 123 FocusMate sessions, I have completed the
(original) six challenge sets. I consider this project to be complete.
```
[okay] Challenge 1: SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
[okay] Challenge 2: 746865206b696420646f6e277420706c6179
[okay] Challenge 3: Cooking MC's like a pound of bacon
[okay] Challenge 4: Now that the party is jumping
[okay] Challenge 5: 0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d...
[okay] Challenge 6: I'm back and I'm rin...
[okay] Challenge 7: I'm back and I'm rin...
[okay] Challenge 8: Cipher 132 [d880619740...] with rating 2398 (average = 2873.8186) is the solution.
[okay] Challenge 9: "YELLOW SUBMARINE\u{4}\u{4}\u{4}\u{4}"
[okay] Challenge 10: I'm back and I'm
[okay] Challenge 11: [10 / 10]
[okay] Challenge 12: Rollin' in my 5.0
[okay] Challenge 13: role=admin
[okay] Challenge 14: Rollin' in my 5.0
[okay] Challenge 15: PKCS7 works
[okay] Challenge 16: admin=true
[okay] Challenge 17: 000003Cooking MC's like a pound of bacon
[okay] Challenge 18: Yo, VIP Let's kick it Ice, Ice, baby Ice, Ice, baby
[okay] Challenge 19: i have met them at close of day
[okay] Challenge 20: I'm rated "R"...this is a warning, ya better void / P
[okay] Challenge 21: implemented MT19937
[okay] Challenge 22: cracked MT19937 seed
[okay] Challenge 23: MT19937 RNG successfully cloned from output
[okay] Challenge 24: MT19937 stream cipher implemented and cracked
[okay] Challenge 25: recovered AES CTR plaintext via edit
[okay] Challenge 26: admin=true
[okay] Challenge 27: recovered key successfully
[okay] Challenge 28: implemented SHA-1
[okay] Challenge 29: extended SHA-1 keyed message successfully
[okay] Challenge 30: implemented and extended MD4 successfully
[okay] Challenge 31: recovered HMAC-SHA1 via timing attack
[okay] Challenge 32: recovered HMAC-SHA1 with slightly less artificial timing leak
[okay] Challenge 33: implemented Diffie-Hellman
[okay] Challenge 34: implement MITM key-fixing attack on DH
[okay] Challenge 35: implement MITM with malicious g attack on DH
[okay] Challenge 36: implement secure remote password
[okay] Challenge 37: break SRP with zero key
[okay] Challenge 38: offline dictionary attack on SRP
[okay] Challenge 39: implement RSA
[okay] Challenge 40: implement an e=3 RSA Broadcast attack
[okay] Challenge 41: implement unpadded message recovery oracle
[okay] Challenge 42: Bleichenbacher's e=3 RSA Attack
[okay] Challenge 43: DSA key recovery from nonce
[okay] Challenge 44: DSA nonce recovery from repeated nonce
[okay] Challenge 45: DSA parameter tampering
[okay] Challenge 46: RSA parity oracle
[okay] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)
[okay] Challenge 48: Bleichenbacher's PKCS 1.5 Padding Oracle (Complex Case)
```

View File

@@ -2,11 +2,16 @@ import sys
from math import log, ceil, floor from math import log, ceil, floor
from fractions import Fraction from fractions import Fraction
# Case for which 2.b does not execute (Simple Case)
e = 3 e = 3
d = 49245850836238243386848117224834103046337172957950760944544575720018018155267 d = 49245850836238243386848117224834103046337172957950760944544575720018018155267
n = 73868776254357365080272175837251154570062235041494422684546086388903725268033 n = 73868776254357365080272175837251154570062235041494422684546086388903725268033
# Case for which 2.b does execute (Complex Case)
e = 3
d = 7436226431632429054084263734954342197144411188982219770498201348078527629483
n = 11154339647448643581126395602431513296069677965376957820612905826953621832839
def bytes_needed(n: int) -> int: def bytes_needed(n: int) -> int:
if n == 0: if n == 0:
@@ -63,10 +68,6 @@ def test():
padded_m = 5300541194335152988749892502228755547482451611528547105226896651010982723 padded_m = 5300541194335152988749892502228755547482451611528547105226896651010982723
assert(padded_m == add_padding(m, k)) assert(padded_m == add_padding(m, k))
c_new = encrypt(padded_m)
c = 71554147358804792877798821486588152314859921438911236615156507964101619628630
assert(c == c_new)
assert(m == remove_padding(add_padding(m, k), k)) assert(m == remove_padding(add_padding(m, k), k))
print('[tests passed]') print('[tests passed]')
@@ -98,7 +99,9 @@ def main():
s += 1 s += 1
elif len(m) > 1: elif len(m) > 1:
# Step 2.b: Searching with more than one interval left. # Step 2.b: Searching with more than one interval left.
raise Exception("Not implemented") s += 1
while not oracle(c * pow(s, e, n) % n):
s += 1
elif len(m) == 1: elif len(m) == 1:
# Step 2.c: Searching with one interval left. # Step 2.c: Searching with one interval left.
a, b = m[0] a, b = m[0]
@@ -145,8 +148,7 @@ def main():
assert(m == 5300541194335152988749892502228755547482451611528547105226896651010982723) assert(m == 5300541194335152988749892502228755547482451611528547105226896651010982723)
m = remove_padding(m, k) m = remove_padding(m, k)
assert(m == m_orig) assert(m == m_orig)
print("[okay] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)") print("[okay] Challenge 48: Bleichenbacher's PKCS 1.5 Padding Oracle (Complex Case)")
main() main()

View File

@@ -1,10 +1,10 @@
#![warn(clippy::pedantic)] #![warn(clippy::pedantic)]
#![warn(clippy::nursery)]
#![allow(clippy::cast_possible_truncation)] #![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_precision_loss)] #![allow(clippy::cast_precision_loss)]
#![allow(clippy::items_after_statements)] #![allow(clippy::items_after_statements)]
#![allow(clippy::many_single_char_names)] #![allow(clippy::many_single_char_names)]
#![allow(clippy::module_name_repetitions)] #![allow(clippy::module_name_repetitions)]
#![feature(string_remove_matches)]
mod bytes; mod bytes;
mod bytes_base64; mod bytes_base64;
mod cbc; mod cbc;
@@ -28,7 +28,7 @@ mod srp;
mod utils; mod utils;
fn main() { fn main() {
const RUN_ALL: bool = false; const RUN_ALL: bool = true;
if RUN_ALL { if RUN_ALL {
set1::challenge1(); set1::challenge1();
set1::challenge2(); set1::challenge2();
@@ -70,12 +70,13 @@ fn main() {
set5::challenge38().unwrap_or_else(|| println!("[fail] challenge 38")); set5::challenge38().unwrap_or_else(|| println!("[fail] challenge 38"));
set5::challenge39().unwrap_or_else(|| println!("[fail] challenge 39")); set5::challenge39().unwrap_or_else(|| println!("[fail] challenge 39"));
set5::challenge40().unwrap_or_else(|| println!("[fail] challenge 40")); 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"));
set6::challenge46().unwrap_or_else(|_| println!("[fail] challenge 46"));
set6::challenge47().unwrap_or_else(|_| println!("[fail] challenge 47"));
set6::challenge48().unwrap_or_else(|_| println!("[fail] challenge 48"));
} }
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"));
set6::challenge46().unwrap_or_else(|_| println!("[fail] challenge 46"));
set6::challenge47().unwrap_or_else(|_| println!("[fail] challenge 47"));
} }

View File

@@ -2,10 +2,8 @@ use crate::bytes::Bytes;
use crate::bytes_base64::BytesBase64; use crate::bytes_base64::BytesBase64;
use crate::dsa; use crate::dsa;
use crate::rsa; use crate::rsa;
use crate::utils::bnclone; use crate::utils::{bn, bnclone, cube_root, div_ceil, div_floor};
use num_bigint::BigUint; use openssl::bn::{BigNum, BigNumContext};
use openssl::bn::BigNum;
use openssl::bn::BigNumContext;
use openssl::error::ErrorStack; use openssl::error::ErrorStack;
use openssl::sha::sha256; use openssl::sha::sha256;
@@ -61,12 +59,6 @@ pub fn challenge42() -> Result<(), ErrorStack> {
"RSA verify does not work" "RSA verify does not work"
); );
fn cube_root(n: &BigNum) -> Result<BigNum, ErrorStack> {
let b = BigUint::from_bytes_be(&n.to_vec());
let b = b.nth_root(3);
BigNum::from_slice(&b.to_bytes_be())
}
fn _cube(n: &BigNum) -> BigNum { fn _cube(n: &BigNum) -> BigNum {
n * &(n * n) n * &(n * n)
} }
@@ -82,7 +74,7 @@ pub fn challenge42() -> Result<(), ErrorStack> {
// Add one to the cube root to ensure that when the number is // Add one to the cube root to ensure that when the number is
// cubed again it contains the desired signature. // cubed again it contains the desired signature.
let sig_cubed = BigNum::from_slice(&v)?; let sig_cubed = BigNum::from_slice(&v)?;
let mut sig = cube_root(&sig_cubed)?; let mut sig = cube_root(&sig_cubed);
sig.add_word(1)?; sig.add_word(1)?;
Ok(sig) Ok(sig)
@@ -192,12 +184,12 @@ pub mod challenge44 {
let file = std::fs::File::open("data/44.txt").unwrap(); let file = std::fs::File::open("data/44.txt").unwrap();
let mut result = Vec::new(); let mut result = Vec::new();
let mut lines: Vec<String> = BufReader::new(file).lines().map(|l| l.unwrap()).collect(); let mut lines: Vec<String> = BufReader::new(file).lines().map(|l| l.unwrap()).collect();
// each message cosists of four lines: msg, s, r, m (sha1 hash of msg)
for line in lines.chunks_mut(4) { for line in lines.chunks_mut(4) {
line[0].remove_matches("msg: "); // each message cosists of four lines: msg, s, r, m (sha1 hash of msg)
line[1].remove_matches("s: "); line[0] = line[0][5..].to_string();
line[2].remove_matches("r: "); line[1] = line[1][3..].to_string();
line[3].remove_matches("m: "); line[2] = line[2][3..].to_string();
line[3] = line[3][3..].to_string();
let msg = Bytes::from_utf8(&line[0]); let msg = Bytes::from_utf8(&line[0]);
let s = BigNum::from_dec_str(&line[1]).unwrap(); let s = BigNum::from_dec_str(&line[1]).unwrap();
let r = BigNum::from_dec_str(&line[2]).unwrap(); let r = BigNum::from_dec_str(&line[2]).unwrap();
@@ -205,7 +197,7 @@ pub mod challenge44 {
assert_eq!( assert_eq!(
dsa::h(&msg).unwrap(), dsa::h(&msg).unwrap(),
m, m,
"Message hash from data/44.txt does not match" "hashes in data/44.txt do not match"
); );
let d = DsaSignedMsg { msg, r, s, m }; let d = DsaSignedMsg { msg, r, s, m };
result.push(d); result.push(d);
@@ -407,20 +399,136 @@ pub fn challenge46() -> Result<(), ErrorStack> {
Ok(()) Ok(())
} }
mod challenge47 {
use crate::rsa;
use crate::utils::{bn, bnclone, div_ceil, div_floor};
use openssl::bn::{BigNum, BigNumContext};
use std::cmp;
pub fn bleichenbachers_pkcs15_padding_oracle_attack(
c: &BigNum,
e: &BigNum,
d: &BigNum,
n: &BigNum,
) -> BigNum {
#![allow(non_snake_case)]
let mut ctx = BigNumContext::new().unwrap();
let public_key = rsa::RsaPublicKey {
e: bnclone(e),
n: bnclone(n),
};
let private_key = rsa::RsaPrivateKey {
d: bnclone(d),
n: bnclone(n),
};
// k is number of bytes of n as specified in Bleichenbacher's paper
let n_bytes = n.num_bytes();
let k: u32 = n_bytes.try_into().unwrap();
let oracle = |cipher: &BigNum| -> bool {
let cleartext: BigNum = rsa::rsa_decrypt_unpadded(cipher, &private_key).unwrap();
let v = cleartext.to_vec_padded(n_bytes).unwrap();
return v[0] == 0x0 && v[1] == 0x2;
};
let mul = |s: &BigNum| -> BigNum {
let mut ctx = BigNumContext::new().unwrap();
let mut f = BigNum::new().unwrap();
f.mod_exp(s, &public_key.e, &n, &mut ctx).unwrap();
&(c * &f) % n
};
let mut B = BigNum::new().unwrap();
// B = 2^(8(k2))
B.exp(&bn(2), &(&bn(8) * &(&bn(k) - &bn(2))), &mut ctx)
.unwrap();
// Step 1: Blinding (not necessary because already PKCS conforming).
let mut i: u32 = 1;
let mut s: BigNum = bn(1);
let mut m: Vec<(BigNum, BigNum)> = vec![(&bn(2) * &B, &(&bn(3) * &B) - &bn(1))];
let solution: BigNum;
// Step 2: Searching for PKCS conforming messages.
loop {
if i == 1 {
// Step 2.a: Starting the search.
s = div_ceil(&n, &(&bn(3) * &B));
while !oracle(&mul(&s)) {
s.add_word(1).unwrap();
}
} else if m.len() > 1 {
// Step 2.b: Searching with more than one interval left.
s.add_word(1).unwrap();
while !oracle(&mul(&s)) {
s.add_word(1).unwrap();
}
} else if m.len() == 1 {
// Step 2.c: Searching with one interval left.
let (a, b) = (bnclone(&m[0].0), bnclone(&m[0].1));
let mut found = false;
let mut r = div_ceil(&(&bn(2) * &(&(&b * &s) - &(&bn(2) * &(B)))), &n);
while !found {
let mut s_lower = div_ceil(&(&(&bn(2) * &B) + &(&r * n)), &b);
let s_upper = div_ceil(&(&(&bn(3) * &B) + &(&r * n)), &a);
while s_lower < s_upper {
if oracle(&mul(&s_lower)) {
found = true;
s = s_lower;
break;
}
s_lower.add_word(1).unwrap();
}
r.add_word(1).unwrap();
}
} else {
panic!("step 2 -- no more intervals?");
}
// Step 3: Narrowing the set of solutions.
let mut m_new: Vec<(BigNum, BigNum)> = vec![];
for (a, b) in m {
let lower_ceil = div_ceil(&(&(&a * &s) - &(&(&bn(3) * &B) + &bn(1))), &n);
let mut upper_ceil = div_ceil(&(&(&b * &s) - &(&bn(2) * &B)), &n);
let upper_floor = div_floor(&(&(&b * &s) - &(&bn(2) * &B)), &n);
if upper_floor == upper_ceil {
upper_ceil.add_word(1).unwrap();
}
let mut r = lower_ceil;
while r < upper_ceil {
let a_new = cmp::max(bnclone(&a), div_ceil(&(&(&bn(2) * &B) + &(&r * n)), &s));
let b_new = cmp::min(
bnclone(&b),
div_floor(&(&(&(&bn(3) * &B) - &bn(1)) + &(&r * n)), &s),
);
m_new.push((a_new, b_new));
r.add_word(1).unwrap();
}
}
m = m_new;
// Step 4: Computing the solutions.
if m.len() == 1 && m[0].0 == m[0].1 {
solution = rsa::rsa_padding_remove_pkcs1(&m[0].0, n_bytes).unwrap();
return solution;
}
i += 1;
}
}
}
pub fn challenge47() -> 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]. // 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)?; let (public_key, private_key) = rsa::rsa_gen_keys_with_size(128, 128)?;
println!("e={:?}", public_key.e);
println!("d={:?}", private_key.d);
println!("n={:?}", private_key.n);
// PKCS1.5-pad a short message, like "kick it, CC", and call it "m". Encrypt to to get "c". // PKCS1.5-pad a short message, like "kick it, CC", and call it "m". Encrypt to to get "c".
let m = Bytes::from_utf8("kick it, CC"); let m = BigNum::from_slice(&"kick it, CC".as_bytes())?;
let m = BigNum::from_slice(&m.0)?; let c = rsa::rsa_encrypt(&m, &public_key)?;
let n = bnclone(&public_key.n); let n_bytes = private_key.n.num_bytes();
let n_bytes = n.num_bytes();
println!("m={:?}", m);
println!("n_bytes={}", n_bytes);
// Build an oracle function, just like you did in the last exercise, but have it check for // Build an oracle function, just like you did in the last exercise, but have it check for
// plaintext[0] == 0 and plaintext[1] == 2. // plaintext[0] == 0 and plaintext[1] == 2.
@@ -430,24 +538,57 @@ pub fn challenge47() -> Result<(), ErrorStack> {
return v[0] == 0x0 && v[1] == 0x2; return v[0] == 0x0 && v[1] == 0x2;
}; };
// Decrypt "c" using your padding oracle. assert!(oracle(&c), "c is padded");
let c_unpadded = rsa::rsa_encrypt_unpadded(&m, &public_key)?; assert!(div_floor(&bn(3), &bn(2)) == bn(1));
let c = rsa::rsa_encrypt(&m, &public_key)?; assert!(div_ceil(&bn(3), &bn(2)) == bn(2));
println!("c={:?}", c); assert!(div_floor(&bn(4), &bn(2)) == bn(2));
assert!(div_ceil(&bn(4), &bn(2)) == bn(2));
assert!(!oracle(&c_unpadded), "oracle wrongly thinks unpadded message is padded"); let solution = challenge47::bleichenbachers_pkcs15_padding_oracle_attack(
assert!(oracle(&c), "oracle wrongly thinks padded message is not padded"); &c,
&public_key.e,
&private_key.d,
&private_key.n,
);
// B = 2^(8(k2)); k is the length of n in bytes; assert_eq!(m, solution, "Bleichenbacher's attack failed");
// let mut ctx = BigNumContext::new()?; println!("[okay] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)");
// let mut p = BigNum::new()?; Ok(())
// let k = BigNum::from_u32(n_bytes.try_into().unwrap())?; }
// p.checked_sub(&k, &BigNum::from_u32(2)?);
// p = &p * BigNum::from_u32(8); pub fn challenge48() -> Result<(), ErrorStack> {
// Set yourself up the way you did in #47, but this time generate a 768 bit modulus.
// b.exp(&BigNum::from_u32(2)?, &BigNum::from_u32(8)? * &(&n_bytes - &BigNum::from_u32(2)), &mut ctx);
// Use e, d, and n that execute step 2.b. (not all 768 bit modulus do)
let public_key = rsa::RsaPublicKey {
println!("[xxxx] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)"); e: BigNum::from_dec_str("3").unwrap(),
n: BigNum::from_dec_str(
"11154339647448643581126395602431513296069677965376957820612905826953621832839",
)
.unwrap(),
};
let private_key = rsa::RsaPrivateKey {
d: BigNum::from_dec_str(
"7436226431632429054084263734954342197144411188982219770498201348078527629483",
)
.unwrap(),
n: BigNum::from_dec_str(
"11154339647448643581126395602431513296069677965376957820612905826953621832839",
)
.unwrap(),
};
let m = BigNum::from_slice(&"kick it, CC".as_bytes())?;
let c = rsa::rsa_encrypt(&m, &public_key)?;
let solution = challenge47::bleichenbachers_pkcs15_padding_oracle_attack(
&c,
&public_key.e,
&private_key.d,
&private_key.n,
);
assert_eq!(m, solution, "Bleichenbacher's attack failed");
println!("[okay] Challenge 48: Bleichenbacher's PKCS 1.5 Padding Oracle (Complex Case)");
Ok(()) Ok(())
} }

View File

@@ -1,5 +1,6 @@
use crate::{bytes::Bytes, bytes_base64::BytesBase64}; use crate::{bytes::Bytes, bytes_base64::BytesBase64};
use openssl::bn::BigNum; use num_bigint::BigUint;
use openssl::bn::{BigNum, BigNumContext};
use std::{ use std::{
io::{BufRead, BufReader}, io::{BufRead, BufReader},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
@@ -55,3 +56,35 @@ pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
pub fn bnclone(b: &BigNum) -> BigNum { pub fn bnclone(b: &BigNum) -> BigNum {
BigNum::from_slice(&b.to_vec()).unwrap() BigNum::from_slice(&b.to_vec()).unwrap()
} }
pub fn cube_root(n: &BigNum) -> BigNum {
let b = BigUint::from_bytes_be(&n.to_vec());
let b = b.nth_root(3);
BigNum::from_slice(&b.to_bytes_be()).unwrap()
}
pub fn bn(n: u32) -> BigNum {
BigNum::from_u32(n).unwrap()
}
pub fn div_rem(a: &BigNum, b: &BigNum) -> (BigNum, BigNum) {
// kReturns the divider and remainder of a / b.
let mut ctx = BigNumContext::new().unwrap();
let mut div = BigNum::new().unwrap();
let mut rem = BigNum::new().unwrap();
div.div_rem(&mut rem, a, b, &mut ctx).unwrap();
(div, rem)
}
pub fn div_floor(a: &BigNum, b: &BigNum) -> BigNum {
let (div, _) = div_rem(a, b);
div
}
pub fn div_ceil(a: &BigNum, b: &BigNum) -> BigNum {
let (mut div, rem) = div_rem(a, b);
if rem.num_bits() != 0 {
div.add_word(1).unwrap();
}
div
}