diff --git a/src/algorithms/generate.rs b/src/algorithms/generate.rs index 8908fd0b..7fa8acc1 100644 --- a/src/algorithms/generate.rs +++ b/src/algorithms/generate.rs @@ -1,14 +1,16 @@ //! Generate prime components for the RSA Private Key use alloc::vec::Vec; -use num_bigint::traits::ModInverse; use num_bigint::{BigUint, RandPrime}; #[allow(unused_imports)] use num_traits::Float; -use num_traits::{One, Zero}; +use num_traits::Zero; use rand_core::CryptoRngCore; -use crate::errors::{Error, Result}; +use crate::{ + algorithms::rsa::{compute_modulus, compute_private_exponent_euler_totient}, + errors::{Error, Result}, +}; pub struct RsaPrivateKeyComponents { pub n: BigUint, @@ -89,13 +91,7 @@ pub(crate) fn generate_multi_prime_key_with_exp( } } - let mut n = BigUint::one(); - let mut totient = BigUint::one(); - - for prime in &primes { - n *= prime; - totient *= prime - BigUint::one(); - } + let n = compute_modulus(&primes); if n.bits() != bit_size { // This should never happen for nprimes == 2 because @@ -104,11 +100,9 @@ pub(crate) fn generate_multi_prime_key_with_exp( continue 'next; } - // NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so. - // This ensures that `exp` is not a factor of any `(prime - 1)`. - if let Some(d) = exp.mod_inverse(totient) { + if let Ok(d) = compute_private_exponent_euler_totient(&primes, exp) { n_final = n; - d_final = d.to_biguint().unwrap(); + d_final = d; break; } } diff --git a/src/algorithms/rsa.rs b/src/algorithms/rsa.rs index 6473f378..35101526 100644 --- a/src/algorithms/rsa.rs +++ b/src/algorithms/rsa.rs @@ -245,6 +245,64 @@ pub fn recover_primes(n: &BigUint, e: &BigUint, d: &BigUint) -> Result<(BigUint, Ok((p, q)) } +/// Compute the modulus of a key from its primes. +pub(crate) fn compute_modulus(primes: &[BigUint]) -> BigUint { + primes.iter().product() +} + +/// Compute the private exponent from its primes (p and q) and public exponent +/// This uses Euler's totient function +#[inline] +pub(crate) fn compute_private_exponent_euler_totient( + primes: &[BigUint], + exp: &BigUint, +) -> Result { + if primes.len() < 2 { + return Err(Error::InvalidPrime); + } + + let mut totient = BigUint::one(); + + for prime in primes { + totient *= prime - BigUint::one(); + } + + // NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so. + // This ensures that `exp` is not a factor of any `(prime - 1)`. + if let Some(d) = exp.mod_inverse(totient) { + Ok(d.to_biguint().unwrap()) + } else { + // `exp` evenly divides `totient` + Err(Error::InvalidPrime) + } +} + +/// Compute the private exponent from its primes (p and q) and public exponent +/// +/// This is using the method defined by +/// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47). +/// (Carmichael function) +/// +/// FIPS 186-4 **requires** the private exponent to be less than λ(n), which would +/// make Euler's totiem unreliable. +#[inline] +pub(crate) fn compute_private_exponent_carmicheal( + p: &BigUint, + q: &BigUint, + exp: &BigUint, +) -> Result { + let p1 = p - BigUint::one(); + let q1 = q - BigUint::one(); + + let lcm = p1.lcm(&q1); + if let Some(d) = exp.mod_inverse(lcm) { + Ok(d.to_biguint().unwrap()) + } else { + // `exp` evenly divides `lcm` + Err(Error::InvalidPrime) + } +} + #[cfg(test)] mod tests { use num_traits::FromPrimitive; diff --git a/src/key.rs b/src/key.rs index 9589e1d3..5e6de22f 100644 --- a/src/key.rs +++ b/src/key.rs @@ -11,7 +11,10 @@ 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::algorithms::rsa::{ + compute_modulus, compute_private_exponent_carmicheal, compute_private_exponent_euler_totient, + recover_primes, +}; use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; @@ -279,6 +282,46 @@ impl RsaPrivateKey { Ok(k) } + /// Constructs an RSA key pair from its two primes p and q. + /// + /// This will rebuild the private exponent and the modulus. + /// + /// Private exponent will be rebuilt using the method defined in + /// [NIST 800-56B Section 6.2.1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br2.pdf#page=47). + pub fn from_p_q(p: BigUint, q: BigUint, public_exponent: BigUint) -> Result { + if p == q { + return Err(Error::InvalidPrime); + } + + let n = compute_modulus(&[p.clone(), q.clone()]); + let d = compute_private_exponent_carmicheal(&p, &q, &public_exponent)?; + + Self::from_components(n, public_exponent, d, vec![p, q]) + } + + /// Constructs an RSA key pair from its primes. + /// + /// This will rebuild the private exponent and the modulus. + pub fn from_primes(primes: Vec, public_exponent: BigUint) -> Result { + if primes.len() < 2 { + return Err(Error::NprimesTooSmall); + } + + // Makes sure that primes is pairwise unequal. + for (i, prime1) in primes.iter().enumerate() { + for prime2 in primes.iter().take(i) { + if prime1 == prime2 { + return Err(Error::InvalidPrime); + } + } + } + + let n = compute_modulus(&primes); + let d = compute_private_exponent_euler_totient(&primes, &public_exponent)?; + + Self::from_components(n, public_exponent, d, primes) + } + /// Get the public key from the private key, cloning `n` and `e`. /// /// Generally this is not needed since `RsaPrivateKey` implements the `PublicKey` trait, @@ -495,6 +538,7 @@ mod tests { use hex_literal::hex; use num_traits::{FromPrimitive, ToPrimitive}; + use pkcs8::DecodePrivateKey; use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; #[test] @@ -753,4 +797,47 @@ mod tests { Error::ModulusTooLarge ); } + + #[test] + fn build_key_from_primes() { + const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/pkcs8/rsa2048-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = ref_key.primes().to_vec(); + + let exp = ref_key.e().clone(); + let key = + RsaPrivateKey::from_primes(primes, exp).expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(key.n(), ref_key.n()); + + assert_eq!(key.dp(), ref_key.dp()); + assert_eq!(key.dq(), ref_key.dq()); + + assert_eq!(key.d(), ref_key.d()); + } + + #[test] + fn build_key_from_p_q() { + const RSA_2048_SP800_PRIV_DER: &[u8] = + include_bytes!("../tests/examples/pkcs8/rsa2048-sp800-56b-priv.der"); + let ref_key = RsaPrivateKey::from_pkcs8_der(RSA_2048_SP800_PRIV_DER).unwrap(); + assert_eq!(ref_key.validate(), Ok(())); + + let primes = ref_key.primes().to_vec(); + let exp = ref_key.e().clone(); + + let key = RsaPrivateKey::from_p_q(primes[0].clone(), primes[1].clone(), exp) + .expect("failed to import key from primes"); + assert_eq!(key.validate(), Ok(())); + + assert_eq!(key.n(), ref_key.n()); + + assert_eq!(key.dp(), ref_key.dp()); + assert_eq!(key.dq(), ref_key.dq()); + + assert_eq!(key.d(), ref_key.d()); + } } diff --git a/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der b/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der new file mode 100644 index 00000000..7c03cbaa Binary files /dev/null and b/tests/examples/pkcs8/rsa2048-sp800-56b-priv.der differ