Finish Challenge 64 RSA parity oracle
parent
365bde182d
commit
c6c6167112
|
@ -14,3 +14,4 @@ Cargo.lock
|
|||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
__pycache__
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
@ -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"));
|
||||
}
|
||||
|
|
11
src/rsa.rs
11
src/rsa.rs
|
@ -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()?;
|
||||
|
|
79
src/set6.rs
79
src/set6.rs
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue