From 47976540d5fbd43fca3ef639993c185d19e24f30 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Fri, 13 Jan 2023 21:09:13 -0500 Subject: [PATCH] Solve challenge 44 DSA recoery from repeated nonce --- src/main.rs | 3 +- src/set6.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9edc09f..af6fcb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,8 @@ fn main() { 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")); } + set6::challenge43().unwrap_or_else(|| println!("[fail] challenge 43")); set6::challenge44().unwrap_or_else(|| println!("[fail] challenge 44")); + set6::challenge45().unwrap_or_else(|| println!("[fail] challenge 45")); } diff --git a/src/set6.rs b/src/set6.rs index ac80911..9902958 100644 --- a/src/set6.rs +++ b/src/set6.rs @@ -143,7 +143,6 @@ pub mod challenge43 { } pub fn challenge43() -> Option<()> { - println!("[okay] Challenge 43: DSA key recovery from nonce"); let msg = Bytes::from_utf8("hello, world!"); let params = dsa::DsaParameters::new().ok()?; let keys = dsa::DsaKeys::new(¶ms).ok()?; @@ -169,17 +168,28 @@ pub fn challenge43() -> Option<()> { BigNum::from_hex_str("0954edd5e0afe5542a4adf012611a91912a3ec16").ok()?, "Recovery from none failed" ); + println!("[okay] Challenge 43: DSA key recovery from nonce"); Some(()) } pub mod challenge44 { - use std::io::{BufReader, BufRead}; use crate::bytes::Bytes; - use openssl::bn::BigNum; use crate::dsa; + use crate::rsa; + use openssl::bn::BigNum; + use openssl::bn::BigNumContext; + use std::io::{BufRead, BufReader}; - pub fn read_dsa_signed_messages() { + pub struct DsaSignedMsg { + pub msg: Bytes, + pub r: BigNum, + pub s: BigNum, + pub m: BigNum, + } + + pub fn read_dsa_signed_messages() -> Vec { let file = std::fs::File::open("data/44.txt").unwrap(); + let mut result = Vec::new(); let mut lines: Vec = BufReader::new(file).lines().map(|l| l.unwrap()).collect(); // each message cosists of four lines: msg, s, r, m (sha1 hash of msg) for line in lines.chunks_mut(4) { @@ -188,22 +198,90 @@ pub mod challenge44 { line[2].remove_matches("r: "); line[3].remove_matches("m: "); let msg = Bytes::from_utf8(&line[0]); + let s = BigNum::from_dec_str(&line[1]).unwrap(); + let r = BigNum::from_dec_str(&line[2]).unwrap(); let m = BigNum::from_hex_str(&line[3]).unwrap(); - assert_eq!(dsa::h(&msg).unwrap(), m, "Message hash from data/44.txt does not match"); + assert_eq!( + dsa::h(&msg).unwrap(), + m, + "Message hash from data/44.txt does not match" + ); + let d = DsaSignedMsg { msg, r, s, m }; + result.push(d); } + return result; } -} + pub fn calculate_k(d1: &DsaSignedMsg, d2: &DsaSignedMsg) -> Option { + // Recovers k if d1 and d2 have been created with the same k. + let mut ctx = BigNumContext::new().ok()?; + let params = dsa::DsaParameters::new().ok()?; + let mut num = BigNum::new().ok()?; + let mut den = BigNum::new().ok()?; + let mut den_inv = BigNum::new().ok()?; + let mut result = BigNum::new().ok()?; + // (m1 - m2) + // k = --------- mod q + // (s1 - s2) + num.mod_sub(&d1.m, &d2.m, ¶ms.q, &mut ctx).ok()?; + den.mod_sub(&d1.s, &d2.s, ¶ms.q, &mut ctx).ok()?; + if den_inv.mod_inverse(&den, ¶ms.q, &mut ctx).is_err() { + return None; + } + result.mod_mul(&num, &den_inv, ¶ms.q, &mut ctx).ok()?; + return Some(result); + } + pub fn find_x() -> Option { + let mut ctx = BigNumContext::new().ok()?; + let params = dsa::DsaParameters::new().ok()?; + let msgs = read_dsa_signed_messages(); + let num_msgs = msgs.len(); + for i in 0..(num_msgs - 1) { + for j in i..num_msgs { + let k = calculate_k(&msgs[i], &msgs[j]); + if k.is_none() { + continue; + } + let k = k.unwrap(); + let m = &msgs[i]; + let r_inv = rsa::invmod(&m.r, ¶ms.q); + let k_inv = rsa::invmod(&k, ¶ms.q); + + // Reconstruct x from k. If two messages have been encrypted with the same k, we + // can then use the x to get the same signature s and r. In that case we have found + // the private key x and return it. + if r_inv.is_ok() && k_inv.is_ok() { + let r_inv = r_inv.ok()?; + let k_inv = k_inv.ok()?; + let x = &(&r_inv * &(&(&m.s * &k) - &m.m)) % ¶ms.q; + let mut r = BigNum::from_u32(0).ok()?; + r.mod_exp(¶ms.g, &k, ¶ms.p, &mut ctx).ok()?; + r = &r % ¶ms.q; + let s = &(&k_inv * &(&m.m + &(&x * &r))) % ¶ms.q; + if s == m.s && r == m.r { + return Some(x); + } + } + } + } + return None; + } +} pub fn challenge44() -> Option<()> { - println!("[xxxx] Challenge 44: DSA nonce recovery from repeated nonce"); - let msg = Bytes::from_utf8("hello, world!"); - let params = dsa::DsaParameters::new().ok()?; - let keys = dsa::DsaKeys::new(¶ms).ok()?; - let sig = keys.sign(¶ms, &msg).ok()?; - let result = keys.verify(¶ms, &msg, &sig).ok()?; - assert!(result, "verify failed unexpectedly"); - challenge44::read_dsa_signed_messages(); + let x = challenge44::find_x()?; + let x_as_hex_str = x.to_hex_str().ok()?.to_ascii_lowercase(); + assert_eq!( + dsa::h(&Bytes::from_utf8(x_as_hex_str.as_ref())).ok()?, + BigNum::from_hex_str("ca8f6f7c66fa362d40760d135b763eb8527d3d52").ok()?, + "Recovery from repeated nonce failed" + ); + println!("[okay] Challenge 44: DSA nonce recovery from repeated nonce"); + Some(()) +} + +pub fn challenge45() -> Option<()> { + println!("[xxxx] Challenge 45: DSA parameter tampering"); None }