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
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.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::challenge44().unwrap_or_else(|| println!("[fail] challenge 44"));
|
||||||
set6::challenge45().unwrap_or_else(|| println!("[fail] challenge 45"));
|
set6::challenge45().unwrap_or_else(|| println!("[fail] challenge 45"));
|
||||||
set6::challenge46().unwrap_or_else(|| println!("[fail] challenge 46"));
|
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::BigUint;
|
||||||
use num_bigint::RandBigInt;
|
use num_bigint::RandBigInt;
|
||||||
use openssl::bn::BigNum;
|
use openssl::bn::BigNum;
|
||||||
|
@ -62,8 +63,6 @@ pub fn rsa_gen_keys() -> Result<(RsaPublicKey, RsaPrivateKey), ErrorStack> {
|
||||||
let mut n = BigNum::new()?;
|
let mut n = BigNum::new()?;
|
||||||
n.checked_mul(&p, &q, &mut ctx)?;
|
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.
|
// 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 et be (p-1)*(q-1) (the "totient"). You need this value only for keygen.
|
||||||
let mut et = BigNum::new()?;
|
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].
|
// 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))
|
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
|
// 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 r_manual = &(&(&u1 % n) + n) % n;
|
||||||
|
|
||||||
let mut ctx = BigNumContext::new()?;
|
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::bytes_base64::BytesBase64;
|
||||||
use crate::dsa;
|
use crate::dsa;
|
||||||
use crate::rsa;
|
use crate::rsa;
|
||||||
|
use crate::utils::bnclone;
|
||||||
use num_bigint::BigUint;
|
use num_bigint::BigUint;
|
||||||
use openssl::bn::BigNum;
|
use openssl::bn::BigNum;
|
||||||
use openssl::bn::BigNumContext;
|
use openssl::bn::BigNumContext;
|
||||||
|
@ -342,50 +343,74 @@ pub fn challenge45() -> Option<()> {
|
||||||
|
|
||||||
pub fn challenge46() -> Option<()> {
|
pub fn challenge46() -> Option<()> {
|
||||||
let m_b64 = BytesBase64::from_base64("VGhhdCdzIHdoeSBJIGZvdW5kIHlvdSBkb24ndCBwbGF5IGFyb3VuZCB3aXRoIHRoZSBGdW5reSBDb2xkIE1lZGluYQ").ok()?;
|
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 (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!(
|
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"
|
"rsa does not work"
|
||||||
);
|
);
|
||||||
|
|
||||||
let parity_oracle = |cipher: &BigNum| -> bool {
|
let parity_oracle = |cipher: &BigNum| -> bool {
|
||||||
// Return true or false based on whether the decrypted plaintext was even or odd.
|
// 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)
|
!cleartext.is_bit_set(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// a == 97 => odd
|
let c_a = rsa::rsa_encrypt_unpadded(&BigNum::from_u32(97).ok()?, &public_key).ok()?;
|
||||||
let c = rsa::rsa_encrypt_str("string_a", &public_key).ok()?;
|
assert!(!parity_oracle(&c_a), "parity oracle odd doesn't work");
|
||||||
assert!(!parity_oracle(&c), "parity oracle doesn't work");
|
|
||||||
|
|
||||||
// a == 98 => even
|
let c_b = rsa::rsa_encrypt_unpadded(&BigNum::from_u32(98).ok()?, &public_key).ok()?;
|
||||||
let c = rsa::rsa_encrypt_str("string_b", &public_key).ok()?;
|
assert!(parity_oracle(&c_b), "parity oracle even doesn't work");
|
||||||
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
|
// Double by multiplying with (2**e) % n
|
||||||
// function each time.
|
let mut ctx = BigNumContext::new().ok()?;
|
||||||
// - Your decryption function starts with bounds for the plaintext of [0,n].
|
let two = BigNum::from_u32(2).ok()?;
|
||||||
// - Each iteration of the decryption cuts the bounds in half; either the upper bound is reduced
|
let mut multiplier = BigNum::new().ok()?;
|
||||||
// by half, or the lower bound is.
|
multiplier.mod_exp(&two, &public_key.e, &n, &mut ctx).ok()?;
|
||||||
// - After log2(n) iterations, you have the decryption of the message.
|
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);
|
for _ in 0..num_bits {
|
||||||
assert!(m == p, "RSA parity attack did not work");
|
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");
|
println!("[okay] Challenge 46: RSA parity oracle");
|
||||||
Some(())
|
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},
|
io::{BufRead, BufReader},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
};
|
};
|
||||||
|
use openssl::bn::BigNum;
|
||||||
|
|
||||||
pub fn unix_timestamp() -> u32 {
|
pub fn unix_timestamp() -> u32 {
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
|
@ -50,3 +51,7 @@ pub fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
|
||||||
.map(|z| *(z.0) ^ *(z.1))
|
.map(|z| *(z.0) ^ *(z.1))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bnclone(b: &BigNum) -> BigNum {
|
||||||
|
BigNum::from_slice(&b.to_vec()).unwrap()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue