Skip to content

Commit

Permalink
feat: speedup decryption + fix multi-prime phi' calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
skyf0l committed Nov 10, 2024
1 parent 547cef0 commit 7607691
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 5 deletions.
40 changes: 40 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ itertools = "0.13"
discrete-logarithm = "1.0"
base64 = "0.22"
factordb = { version = "0.3.0", features = ["blocking"] }
rayon = "1.10.0"

[dependencies.rug]
version = "1.26"
Expand Down
15 changes: 12 additions & 3 deletions src/factors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ impl Factors {
.product()
}

/// Returns the totient of the factors.
/// Returns the Euler totient of the factors.
///
/// Note:
/// - phi(a*b) = phi(a) * phi(b)
/// - phi(p^k) = (p-1) * p^(k-1)
pub fn phi(&self) -> Integer {
self.phis().into_iter().product()
}

/// Returns the Euler totient of each unique factor (phi(p^k)).
pub fn phis(&self) -> Vec<Integer> {
self.0
.iter()
.map(|(f, p)| (f.clone() - 1u64).pow(*p as u32))
.product()
.map(|(f, p)| (f.clone()).pow(*p as u32 - 1u32) * (f.clone() - 1))
.collect()
}

/// Returns unique factors.
Expand Down
99 changes: 97 additions & 2 deletions src/key.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use openssl::rsa::RsaPrivateKeyBuilder;
use rayon::iter::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, ParallelIterator,
};
use rug::{
integer::{IsPrime, Order},
ops::Pow,
Integer,
};

use crate::factors::Factors;
use crate::{factors::Factors, ntheory::crt};

/// Attack error
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -68,7 +72,27 @@ impl PrivateKey {

/// Decrypt cipher message
pub fn decrypt(&self, c: &Integer) -> Integer {
c.clone().pow_mod(&self.d, &self.n).unwrap()
// Fast decryption using CRT
// See <https://exploringnumbertheory.wordpress.com/2015/11/16/speeding-up-modular-exponentiation-using-crt>

let phis = self.factors.phis();
// Calculate associated factors for all phis
let factors = self
.factors
.0
.iter()
.map(|(f, p)| f.clone().pow(*p as u32))
.collect::<Vec<_>>();

// Calculate residues c^(d mod phi) mod p for all factors
// This can take a while for large factors, so we parallelize it
let p = factors
.par_iter()
.zip(phis.into_par_iter())
.map(|(f, phi)| c.clone().pow_mod(&(&self.d % phi), f).unwrap())
.collect::<Vec<_>>();

crt(&p, &factors).unwrap()
}

/// Returns P factor
Expand Down Expand Up @@ -145,3 +169,74 @@ impl PrivateKey {
}
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use crate::{bytes_to_integer, Factors};

use super::*;

#[test]
fn decrypt_from_two_factors() {
let m = bytes_to_integer(b"RsaCracker!");
let e = Integer::from(65537);
let factors = Factors::from([
Integer::from_str(
"112219243609243706223486619551298085362360091408633161457003404046681540344297",
)
.unwrap(),
Integer::from_str(
"64052533192509995760322742160163582601357132095571262796409705234000154367147",
)
.unwrap(),
]);
let c = m.clone().pow_mod(&e, &factors.product()).unwrap();

let pk = PrivateKey::from_factors(factors, e).unwrap();
assert_eq!(m, pk.decrypt(&c));
}

#[test]
fn decrypt_from_many_factors() {
let m = bytes_to_integer(b"RsaCracker!");
let e = Integer::from(65537);
// First 30 primes
let factors = Factors::from([
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
89, 97, 101, 103, 107, 109,
]);
let c = m.clone().pow_mod(&e, &factors.product()).unwrap();

let pk = PrivateKey::from_factors(factors, e).unwrap();
assert_eq!(m, pk.decrypt(&c));
}

#[test]
fn decrypt_from_many_duplicated_factors() {
let m = bytes_to_integer(b"RsaCracker!");
let e = Integer::from(65537);
// 50 random primes between the 50th and 100th prime numbers, including duplicates
let factors = Factors::from([
229, 229, 229, 233, 233, 233, 233, 239, 239, 239, 241, 251, 257, 263, 263, 269, 269,
269, 277, 277, 281, 283, 293, 293, 307, 307, 307, 317, 317, 331, 331, 331, 337, 347,
347, 347, 349, 349, 353, 353, 367, 373, 379, 383, 389, 401, 409, 409, 409, 409,
]);
let c = m.clone().pow_mod(&e, &factors.product()).unwrap();

let pk = PrivateKey::from_factors(factors, e).unwrap();
assert_eq!(m, pk.decrypt(&c));
}

#[test]
fn decrypt_from_single_duplicated_factors() {
let m = bytes_to_integer(b"RsaCracker!");
let e = Integer::from(65537);
let factors = Factors::from([409; 50]);
let c = m.clone().pow_mod(&e, &factors.product()).unwrap();

let pk = PrivateKey::from_factors(factors, e).unwrap();
assert_eq!(m, pk.decrypt(&c));
}
}

0 comments on commit 7607691

Please sign in to comment.