Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds RsaPrivateKey::from_primes and RsaPrivateKey::from_p_q methods #386

Merged
merged 1 commit into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions src/algorithms/generate.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -89,13 +91,7 @@ pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRngCore + ?Sized>(
}
}

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
Expand All @@ -104,11 +100,9 @@ pub(crate) fn generate_multi_prime_key_with_exp<R: CryptoRngCore + ?Sized>(
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;
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<BigUint> {
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<BigUint> {
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;
Expand Down
89 changes: 88 additions & 1 deletion src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<RsaPrivateKey> {
if p == q {
tarcieri marked this conversation as resolved.
Show resolved Hide resolved
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<BigUint>, public_exponent: BigUint) -> Result<RsaPrivateKey> {
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,
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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());
}
}
Binary file added tests/examples/pkcs8/rsa2048-sp800-56b-priv.der
Binary file not shown.