Implement Bleichenbacher PKCS padding oracle in Python
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
# cryptopals
|
||||
|
||||
My solutions to the cryptopals challenges.
|
||||
My solutions to the [cryptopals](https://cryptopals.com/) challenges.
|
||||
|
||||
140
data/set6c47.py
Normal file
140
data/set6c47.py
Normal file
@@ -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()
|
||||
|
||||
16
src/main.rs
16
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"));
|
||||
|
||||
26
src/set6.rs
26
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(())
|
||||
|
||||
Reference in New Issue
Block a user