From b513ee399837ba38796a6336d9e1501c2c0e195e Mon Sep 17 00:00:00 2001 From: Zach Heylmun Date: Sat, 11 Nov 2023 13:10:42 -0500 Subject: [PATCH] Deterministic implementation of prime factors recovery (#380) Implements deterministic recovery of `p` and `q` from `n`, `e,` and `d` using the algorithm specified in NIST 800-56B Appendix C.2 --- src/algorithms/rsa.rs | 78 +++++++++++++++++++++++++++++++++++++++++-- src/errors.rs | 4 +++ src/key.rs | 22 +++++++++--- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index 3480acdc..6473f378 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -3,9 +3,10 @@ use alloc::borrow::Cow; use alloc::vec::Vec; use num_bigint::{BigInt, BigUint, IntoBigInt, IntoBigUint, ModInverse, RandBigInt, ToBigInt}; -use num_traits::{One, Signed, Zero}; +use num_integer::{sqrt, Integer}; +use num_traits::{FromPrimitive, One, Pow, Signed, Zero}; use rand_core::CryptoRngCore; -use zeroize::Zeroize; +use zeroize::{Zeroize, Zeroizing}; use crate::errors::{Error, Result}; use crate::traits::{PrivateKeyParts, PublicKeyParts}; @@ -194,3 +195,76 @@ fn blind( fn unblind(key: &impl PublicKeyParts, m: &BigUint, unblinder: &BigUint) -> BigUint { (m * unblinder) % key.n() } + +/// The following (deterministic) algorithm also recovers the prime factors `p` and `q` of a modulus `n`, given the +/// public exponent `e` and private exponent `d` using the method described in +/// [NIST 800-56B Appendix C.2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf). +pub fn recover_primes(n: &BigUint, e: &BigUint, d: &BigUint) -> Result<(BigUint, BigUint)> { + // Check precondition + let two = BigUint::from_u8(2).unwrap(); + if e <= &two.pow(16u32) || e >= &two.pow(256u32) { + return Err(Error::InvalidArguments); + } + + // 1. Let a = (de – 1) × GCD(n – 1, de – 1). + let one = BigUint::one(); + let a = Zeroizing::new((d * e - &one) * (n - &one).gcd(&(d * e - &one))); + + // 2. Let m = floor(a /n) and r = a – m n, so that a = m n + r and 0 ≤ r < n. + let m = Zeroizing::new(&*a / n); + let r = Zeroizing::new(&*a - &*m * n); + + // 3. Let b = ( (n – r)/(m + 1) ) + 1; if b is not an integer or b^2 ≤ 4n, then output an error indicator, + // and exit without further processing. + let modulus_check = Zeroizing::new((n - &*r) % (&*m + &one)); + if !modulus_check.is_zero() { + return Err(Error::InvalidArguments); + } + let b = Zeroizing::new((n - &*r) / (&*m + &one) + one); + + let four = BigUint::from_u8(4).unwrap(); + let four_n = Zeroizing::new(n * four); + let b_squared = Zeroizing::new(b.pow(2u32)); + if *b_squared <= *four_n { + return Err(Error::InvalidArguments); + } + let b_squared_minus_four_n = Zeroizing::new(&*b_squared - &*four_n); + + // 4. Let ϒ be the positive square root of b^2 – 4n; if ϒ is not an integer, + // then output an error indicator, and exit without further processing. + let y = Zeroizing::new(sqrt((*b_squared_minus_four_n).clone())); + + let y_squared = Zeroizing::new(y.pow(2u32)); + let sqrt_is_whole_number = y_squared == b_squared_minus_four_n; + if !sqrt_is_whole_number { + return Err(Error::InvalidArguments); + } + let p = (&*b + &*y) / &two; + let q = (&*b - &*y) / two; + + Ok((p, q)) +} + +#[cfg(test)] +mod tests { + use num_traits::FromPrimitive; + + use super::*; + + #[test] + fn recover_primes_works() { + let n = BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(); + let e = BigUint::from_u64(65537).unwrap(); + let d = BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(); + let p = BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(); + let q = BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap(); + + let (mut p1, mut q1) = recover_primes(&n, &e, &d).unwrap(); + + if p1 < q1 { + std::mem::swap(&mut p1, &mut q1); + } + assert_eq!(p, p1); + assert_eq!(q, q1); + } +} diff --git a/src/errors.rs b/src/errors.rs index d416aa04..7a0116bd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -63,6 +63,9 @@ pub enum Error { /// Invalid padding length. InvalidPadLen, + + /// Invalid arguments. + InvalidArguments, } #[cfg(feature = "std")] @@ -91,6 +94,7 @@ impl core::fmt::Display for Error { Error::Internal => write!(f, "internal error"), Error::LabelTooLong => write!(f, "label too long"), Error::InvalidPadLen => write!(f, "invalid padding length"), + Error::InvalidArguments => write!(f, "invalid arguments"), } } } diff --git a/src/key.rs b/src/key.rs index b9cfd86e..25e677d4 100644 --- a/src/key.rs +++ b/src/key.rs @@ -11,6 +11,8 @@ use serde::{Deserialize, Serialize}; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::algorithms::generate::generate_multi_prime_key_with_exp; +use crate::algorithms::rsa::recover_primes; + use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::traits::{PaddingScheme, PrivateKeyParts, PublicKeyParts, SignatureScheme}; @@ -232,12 +234,19 @@ impl RsaPrivateKey { n: BigUint, e: BigUint, d: BigUint, - primes: Vec, + mut primes: Vec, ) -> Result { - // TODO(tarcieri): support recovering `p` and `q` from `d` if `primes` is empty - // See method in Appendix C: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br1.pdf + let mut should_validate = false; if primes.len() < 2 { - return Err(Error::NprimesTooSmall); + if !primes.is_empty() { + return Err(Error::NprimesTooSmall); + } + // Recover `p` and `q` from `d`. + // See method in Appendix C.2: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf + let (p, q) = recover_primes(&n, &e, &d)?; + primes.push(p); + primes.push(q); + should_validate = true; } let mut k = RsaPrivateKey { @@ -247,6 +256,11 @@ impl RsaPrivateKey { precomputed: None, }; + // Validate the key if we had to recover the primes. + if should_validate { + k.validate()?; + } + // precompute when possible, ignore error otherwise. let _ = k.precompute();