diff --git a/README.md b/README.md index 56d5571..aab6502 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # cryptopals -My solutions to the cryptopals challenges. \ No newline at end of file +My solutions to the [cryptopals](https://cryptopals.com/) challenges. diff --git a/data/set6c47.py b/data/set6c47.py new file mode 100644 index 0000000..c05695a --- /dev/null +++ b/data/set6c47.py @@ -0,0 +1,140 @@ +from math import log, ceil, floor + + +e = 3 +d = 49245850836238243386848117224834103046337172957950760944544575720018018155267 +n = 73868776254357365080272175837251154570062235041494422684546086388903725268033 + + +def bytes_needed(n: int) -> int: + if n == 0: + return 1 + return int(log(n, 256)) + 1 + + +def add_padding(m: int, k: int) -> int: + padding_size = 11 + from_len = bytes_needed(m) + assert(from_len + padding_size <= k) + padding_str_len = k - 3 - from_len + r = [0x0, 0x2] + for _ in range(2, padding_str_len + 2): + r.append(0xff) + r.append(0x0) + r += m.to_bytes(from_len, 'big') + r = int.from_bytes(r, byteorder='big') + return r + + +def remove_padding(m: int, k: int) -> int: + v = m.to_bytes(k, 'big') + assert(v[0] == 0x0 and v[1] == 0x2) + i = 2 + while v[i] == 0xff: + i = i + 1 + assert(v[i] == 0x0) + return int.from_bytes(v[i + 1:], 'big') + + +def oracle(c: int) -> bool: + m = decrypt(c) + v = m.to_bytes(bytes_needed(n), 'big') + return v[0] == 0x0 and v[1] == 0x2 + + +def encrypt(m: int) -> int: + return pow(m, e, n) + + +def decrypt(c: int) -> int: + return pow(c, d, n) + + +def test(): + m = int.from_bytes(b"kick it, CC", byteorder='big') + m = 129852745126415640677073731 + assert(m == 129852745126415640677073731) + + k = bytes_needed(n) + assert(k == 32) + + padded_m = 5300541194335152988749892502228755547482451611528547105226896651010982723 + 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)) + print('[tests passed]') + + +def main(): + test() + + k = bytes_needed(n) + m = int.from_bytes(b"kick it, CC", byteorder='big') + c = encrypt(add_padding(m, k)) + assert(m == remove_padding(decrypt(c), k)) + + B = pow(2, 8 * (k - 2)) + # Step 1: Blinding (not necessary because already PKCS conforming). + i = 1 + s = 1 + m = [(2 * B, 3 * B - 1)] + assert(oracle(c)) + + # Step 2: Searching for PKCS conforming messages. + while True: + print(f"{i=}\n{s=}\n{m=}") + if i == 1: + # Step 2.a: Starting the search. + s = ceil(n / (3 * B)) + while not oracle(c * pow(s, e, n) % n): + s += 1 + elif len(m) > 1: + # Step 2.b: Searching with more than one interval left. + raise Exception("Not implemented") + elif len(m) == 1: + # Step 2.c: Searching with one interval left. + a, b = m[0] + found = False + r = ceil(2 * ((b * s - 2 * B) / n)) + while not found: + s_lower = ceil((2 * B + r * n) / b) + s_upper = ceil((3 * B + r * n) / a) + assert(s_lower < s_upper + 1) + for s in range(s_lower, s_upper): + if oracle(c * pow(s, e, n) % n): + found = True + break + r += 1 + else: + raise Exception("No intervals?") + + # Step 3: Narrowing the set of solutions. + m_new = [] + for (a, b) in m: + lower = ceil((a * s - 3 * B + 1) / n) + upper = ceil((b * s - 2 * B) / n) + for r in range(lower, upper): + a_new = max(a, ceil((2 * B + r * n) / s)) + b_new = min(b, floor((3 * B - 1 + r * n) / s)) + m_new.append((a_new, b_new)) + m = m_new + + # Step 4: Computing the solutions. + if len(m) == 1 and m[0][0] == m[0][1]: + m = a * pow(s0, -1, n) % n + break + i = i + 1 + + print(m) + + + + + + +main() + diff --git a/src/main.rs b/src/main.rs index 4bdd98d..6ae0005 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,15 +62,15 @@ 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")); } - 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")); diff --git a/src/set6.rs b/src/set6.rs index 850c41d..0aa1390 100644 --- a/src/set6.rs +++ b/src/set6.rs @@ -410,12 +410,17 @@ 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)?; + 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". let m = Bytes::from_utf8("kick it, CC"); let m = BigNum::from_slice(&m.0)?; let n = bnclone(&public_key.n); 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 // plaintext[0] == 0 and plaintext[1] == 2. @@ -428,15 +433,20 @@ pub fn challenge47() -> Result<(), ErrorStack> { // Decrypt "c" using your padding oracle. let c_unpadded = rsa::rsa_encrypt_unpadded(&m, &public_key)?; let c = rsa::rsa_encrypt(&m, &public_key)?; + println!("c={:?}", c); + + assert!(!oracle(&c_unpadded), "oracle wrongly thinks unpadded message is padded"); + assert!(oracle(&c), "oracle wrongly thinks padded message is not padded"); + + // B = 2^(8(k−2)); k is the length of n in bytes; + // let mut ctx = BigNumContext::new()?; + // let mut p = BigNum::new()?; + // let k = BigNum::from_u32(n_bytes.try_into().unwrap())?; + // p.checked_sub(&k, &BigNum::from_u32(2)?); + // p = &p * BigNum::from_u32(8); + + // b.exp(&BigNum::from_u32(2)?, &BigNum::from_u32(8)? * &(&n_bytes - &BigNum::from_u32(2)), &mut ctx); - 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(())