From 365bde182d74858791add99515622fd6242dc3f8 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Sun, 15 Jan 2023 22:09:46 -0500 Subject: [PATCH] Implement parity oracle for challenge 46 --- src/set6.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/set6.rs b/src/set6.rs index 90fe093..99b854a 100644 --- a/src/set6.rs +++ b/src/set6.rs @@ -1,4 +1,5 @@ use crate::bytes::Bytes; +use crate::bytes_base64::BytesBase64; use crate::dsa; use crate::rsa; use num_bigint::BigUint; @@ -340,6 +341,51 @@ pub fn challenge45() -> Option<()> { } pub fn challenge46() -> Option<()> { - println!("[xxxx] Challenge 46: RSA parity oracle"); - None + let m_b64 = BytesBase64::from_base64("VGhhdCdzIHdoeSBJIGZvdW5kIHlvdSBkb24ndCBwbGF5IGFyb3VuZCB3aXRoIHRoZSBGdW5reSBDb2xkIE1lZGluYQ").ok()?; + let m = m_b64.to_bytes(); + let (public_key, private_key) = rsa::rsa_gen_keys().ok()?; + let cipher = rsa::rsa_encrypt_str(&m.to_utf8(), &public_key).ok()?; + assert!( + rsa::rsa_decrypt_str(&cipher, &private_key).ok()? == m.to_utf8(), + "rsa does not work" + ); + + let parity_oracle = |cipher: &BigNum| -> bool { + // Return true or false based on whether the decrypted plaintext was even or odd. + let cleartext: BigNum = rsa::rsa_decrypt(cipher, &private_key).unwrap(); + !cleartext.is_bit_set(0) + }; + + // a == 97 => odd + let c = rsa::rsa_encrypt_str("string_a", &public_key).ok()?; + assert!(!parity_oracle(&c), "parity oracle doesn't work"); + + // a == 98 => even + let c = rsa::rsa_encrypt_str("string_b", &public_key).ok()?; + assert!(parity_oracle(&c), "parity oracle doesn't work"); + + // RSA ciphertexts are just numbers. You can do trivial math on them. You can for instance + // multiply a ciphertext by the RSA-encryption of another number; the corresponding plaintext + // will be the product of those two numbers. If you double a ciphertext (multiply it by + // (2**e)%n), the resulting plaintext will (obviously) be either even or odd. If the plaintext + // after doubling is even, doubling the plaintext didn't wrap the modulus --- the modulus is a + // prime number. That means the plaintext is less than half the modulus. + + // - 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]. + // - Each iteration of the decryption cuts the bounds in half; either the upper bound is reduced + // by half, or the lower bound is. + // - After log2(n) iterations, you have the decryption of the message. + + // Print the upper bound of the message as a string at each iteration; you'll see the message + // decrypt "hollywood style". + + let attack = |cipher: &BigNum| -> Bytes { Bytes(vec![]) }; + + let p = attack(&cipher); + assert!(m == p, "RSA parity attack did not work"); + + println!("[okay] Challenge 46: RSA parity oracle"); + Some(()) }