Implement Bleichenbacher PKCS padding oracle in Python
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
# cryptopals
|
# 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::challenge30();
|
||||||
set4::challenge31();
|
set4::challenge31();
|
||||||
set4::challenge32();
|
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::challenge41().unwrap_or_else(|_| println!("[fail] challenge 41"));
|
||||||
set6::challenge42().unwrap_or_else(|_| println!("[fail] challenge 42"));
|
set6::challenge42().unwrap_or_else(|_| println!("[fail] challenge 42"));
|
||||||
set6::challenge43().unwrap_or_else(|| println!("[fail] challenge 43"));
|
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> {
|
pub fn challenge47() -> Result<(), ErrorStack> {
|
||||||
// Generate a 256 bit keypair (that is, p and q will each be 128 bit primes), [n, e, d].
|
// 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)?;
|
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".
|
// 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 = Bytes::from_utf8("kick it, CC");
|
||||||
let m = BigNum::from_slice(&m.0)?;
|
let m = BigNum::from_slice(&m.0)?;
|
||||||
let n = bnclone(&public_key.n);
|
let n = bnclone(&public_key.n);
|
||||||
let n_bytes = n.num_bytes();
|
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
|
// Build an oracle function, just like you did in the last exercise, but have it check for
|
||||||
// plaintext[0] == 0 and plaintext[1] == 2.
|
// plaintext[0] == 0 and plaintext[1] == 2.
|
||||||
@@ -428,15 +433,20 @@ pub fn challenge47() -> Result<(), ErrorStack> {
|
|||||||
// Decrypt "c" using your padding oracle.
|
// Decrypt "c" using your padding oracle.
|
||||||
let c_unpadded = rsa::rsa_encrypt_unpadded(&m, &public_key)?;
|
let c_unpadded = rsa::rsa_encrypt_unpadded(&m, &public_key)?;
|
||||||
let c = rsa::rsa_encrypt(&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)");
|
println!("[xxxx] Challenge 47: Bleichenbacher's PKCS 1.5 Padding Oracle (Simple Case)");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user