Finish Challenge 64 RSA parity oracle

main
Felix Martin 2023-01-19 19:57:14 -05:00
parent 365bde182d
commit c6c6167112
6 changed files with 104 additions and 35 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
__pycache__

42
data/set6c46.py Normal file
View File

@ -0,0 +1,42 @@
from math import ceil, floor, log
e = 17
d = 413
n = 3233
m = 65;
def crypt(m):
return pow(m, e, n)
def clear(c):
return pow(c, d, n)
def oracle(c):
return clear(c) % 2 == 0
def double(c):
return (c * crypt(2)) % n
def solve(c, lower, upper):
div = 1
for i in range(int(log(n, 2)) + 1):
print(f"{i=} {lower=} {upper=} {div=}")
div = div * 2
lower = lower * 2
upper = upper * 2
new = (lower + upper) >> 1
c = double(c)
if oracle(c):
upper = new
else:
lower = new
return upper * n // div
for m in range(2, n - 1):
c = crypt(m)
r = solve(c, 0, 1)
print(f"{m=} {c=} {r=}")
assert(r == m)

View File

@ -76,4 +76,5 @@ fn main() {
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

@ -1,3 +1,4 @@
use crate::utils::bnclone;
use num_bigint::BigUint;
use num_bigint::RandBigInt;
use openssl::bn::BigNum;
@ -62,8 +63,6 @@ pub fn rsa_gen_keys() -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> {
let mut n = BigNum::new()?;
n.checked_mul(&p, &q, &mut ctx)?;
// This is stupid but I couldn't figure out how to clone a bignum so we do this.
let mut n2 = BigNum::new()?;
n2.checked_mul(&p, &q, &mut ctx)?;
// Let et be (p-1)*(q-1) (the "totient"). You need this value only for keygen.
let mut et = BigNum::new()?;
@ -80,7 +79,7 @@ pub fn rsa_gen_keys() -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> {
};
// Your public key is [e, n]. Your private key is [d, n].
return Ok((RsaPublicKey { e, n }, RsaPrivateKey { d, n: n2 }));
return Ok((RsaPublicKey { e, n: bnclone(&n) }, RsaPrivateKey { d, n }));
}
}
@ -120,12 +119,8 @@ pub fn invmod(a: &BigNum, n: &BigNum) -> Result<BigNum, ErrorStack> {
Ok((r1, u1, v1))
}
// No, couldn't think of a worse way to do that.
let a_cloned = BigNum::from_hex_str(&a.to_hex_str()?)?;
let n_cloned = BigNum::from_hex_str(&n.to_hex_str()?)?;
// if v1 == 0 there is no mod_inverse
let (_, u1, _v1) = extended_gcd(a_cloned, n_cloned)?;
let (_, u1, _v1) = extended_gcd(bnclone(&a), bnclone(&n))?;
let r_manual = &(&(&u1 % n) + n) % n;
let mut ctx = BigNumContext::new()?;

View File

@ -2,6 +2,7 @@ use crate::bytes::Bytes;
use crate::bytes_base64::BytesBase64;
use crate::dsa;
use crate::rsa;
use crate::utils::bnclone;
use num_bigint::BigUint;
use openssl::bn::BigNum;
use openssl::bn::BigNumContext;
@ -342,50 +343,74 @@ pub fn challenge45() -> Option<()> {
pub fn challenge46() -> Option<()> {
let m_b64 = BytesBase64::from_base64("VGhhdCdzIHdoeSBJIGZvdW5kIHlvdSBkb24ndCBwbGF5IGFyb3VuZCB3aXRoIHRoZSBGdW5reSBDb2xkIE1lZGluYQ").ok()?;
let m = m_b64.to_bytes();
let m = BigNum::from_slice(&m_b64.to_bytes().0).ok()?;
let (public_key, private_key) = rsa::rsa_gen_keys().ok()?;
let cipher = rsa::rsa_encrypt_str(&m.to_utf8(), &public_key).ok()?;
let c = rsa::rsa_encrypt_unpadded(&m, &public_key).ok()?;
let n = BigNum::from_slice(&public_key.n.to_vec()).ok()?;
assert!(
rsa::rsa_decrypt_str(&cipher, &private_key).ok()? == m.to_utf8(),
rsa::rsa_decrypt_unpadded(&c, &private_key).ok()? == m,
"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();
let cleartext: BigNum = rsa::rsa_decrypt_unpadded(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");
let c_a = rsa::rsa_encrypt_unpadded(&BigNum::from_u32(97).ok()?, &public_key).ok()?;
assert!(!parity_oracle(&c_a), "parity oracle odd 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");
let c_b = rsa::rsa_encrypt_unpadded(&BigNum::from_u32(98).ok()?, &public_key).ok()?;
assert!(parity_oracle(&c_b), "parity oracle even 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.
// Double by multiplying with (2**e) % n
let mut ctx = BigNumContext::new().ok()?;
let two = BigNum::from_u32(2).ok()?;
let mut multiplier = BigNum::new().ok()?;
multiplier.mod_exp(&two, &public_key.e, &n, &mut ctx).ok()?;
let double = |c: BigNum| -> BigNum {
return &(&c * &multiplier) % &private_key.n;
};
// 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 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].
// - 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.
let mut div = BigNum::from_u32(1)?;
let mut lower = BigNum::from_u32(0)?;
let mut upper = BigNum::from_u32(1)?;
let num_bits = n.num_bits();
let p = attack(&cipher);
assert!(m == p, "RSA parity attack did not work");
for _ in 0..num_bits {
div = &div << 1_i32;
lower = &lower << 1_i32;
upper = &upper << 1_i32;
let new = &(&lower + &upper) >> 1_i32;
cipher = double(cipher);
if parity_oracle(&cipher) {
upper = new;
} else {
lower = new;
}
}
Ok(&(&upper * &n) / &div)
};
let s = solve(bnclone(&c), bnclone(&n)).ok()?;
assert!(m == s, "RSA parity attack did not work");
println!("[okay] Challenge 46: RSA parity oracle");
Some(())
}
pub fn challenge47() -> Option<()> {
println!("[xxxx] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)");
None
}

View File

@ -3,6 +3,7 @@ use std::{
io::{BufRead, BufReader},
time::{SystemTime, UNIX_EPOCH},
};
use openssl::bn::BigNum;
pub fn unix_timestamp() -> u32 {
let start = SystemTime::now();
@ -50,3 +51,7 @@ pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
.map(|z| *(z.0) ^ *(z.1))
.collect()
}
pub fn bnclone(b: &BigNum) -> BigNum {
BigNum::from_slice(&b.to_vec()).unwrap()
}