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

feat: switch from num-bigint-dig to crypto-bigint #394

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
fcc3577
implement minimal decryption routine using BoxedUint
dignifiedquire Nov 29, 2023
bc92994
convert internals to use crypto-bigint
dignifiedquire Nov 29, 2023
21b7c81
store residue_params
dignifiedquire Nov 29, 2023
98a86a2
implement blinding (tests failing currently)
dignifiedquire Nov 29, 2023
f4131c5
fix blinding and fold in new impl
dignifiedquire Nov 29, 2023
2dafa35
implement basic widening strategy
dignifiedquire Nov 29, 2023
cb7d486
fix some widening/padding
dignifiedquire Nov 29, 2023
fd19c88
cleanup rsa algorithm
dignifiedquire Nov 29, 2023
813fe77
docs and debugging
dignifiedquire Nov 29, 2023
efc4460
fix crt value calculation in decryption
dignifiedquire Nov 30, 2023
9fde5fb
cleanup
dignifiedquire Nov 30, 2023
3f1751e
convert core key algorithms
dignifiedquire Nov 30, 2023
b795c22
cache p and q params
dignifiedquire Nov 30, 2023
6481533
convert hazmt decrypt interface
dignifiedquire Nov 30, 2023
97f8fa4
use crypto-bigint for encryption
dignifiedquire Dec 1, 2023
cc3f03b
convert more internal use to crypto-bigint
dignifiedquire Dec 1, 2023
d02fbe7
use crypto-bigint in signatuers
dignifiedquire Dec 1, 2023
a5fc616
convert from_components internally and pss signature
dignifiedquire Dec 1, 2023
7211bfa
cleanup rsapublickey
dignifiedquire Dec 1, 2023
c071ba1
store exponent as u64
dignifiedquire Dec 1, 2023
a811be0
fix: handle large m2
dignifiedquire Dec 2, 2023
102953d
cache boxedresiduie for qinv
dignifiedquire Dec 2, 2023
57d6f95
use some assign operations
dignifiedquire Dec 2, 2023
2cdd37c
update to latest crypto-bigint
dignifiedquire Dec 4, 2023
f7fa669
update crypto-bigint
dignifiedquire Dec 5, 2023
851cbac
use branch
dignifiedquire Dec 7, 2023
37558f6
use BoxedResidueParams::new_vartime
dignifiedquire Dec 7, 2023
2634f65
update to latest master
dignifiedquire Dec 14, 2023
56f6f27
switch to latest crypto-bigint
dignifiedquire Mar 22, 2024
fab7852
cleanup
dignifiedquire Mar 22, 2024
71a3506
Merge remote-tracking branch 'origin/master' into const-crypto-biguint
dignifiedquire Mar 22, 2024
98f3faa
chore: update MSRV to 1.73
dignifiedquire Mar 22, 2024
0db6317
refactor: remove remaining usage of num-bigint
dignifiedquire Mar 25, 2024
b2bb016
handle encoding sizes
dignifiedquire Mar 25, 2024
c52a90a
Merge remote-tracking branch 'origin/master' into const-crypto-biguint
dignifiedquire Jul 6, 2024
040f8b0
small fixes
dignifiedquire Jul 6, 2024
7b2aa3d
update deps
dignifiedquire Jul 6, 2024
a2d4998
widen and shorten
dignifiedquire Jul 6, 2024
cf3548b
some encoding fixes
dignifiedquire Jul 6, 2024
3ef5f3f
fix serde tests
dignifiedquire Jul 6, 2024
bd308a4
fix pkcs test parsing
dignifiedquire Jul 6, 2024
1b070ca
fix oaep test decoding
dignifiedquire Jul 6, 2024
2badede
add doc comments
dignifiedquire Jul 6, 2024
ee6b31a
improve feature selection
dignifiedquire Jul 6, 2024
64386b4
drop nightly feature
dignifiedquire Jul 6, 2024
53781fb
update subtle min version
dignifiedquire Jul 6, 2024
9c15ea9
happy clippy
dignifiedquire Jul 6, 2024
926e947
pad pass keys
dignifiedquire Jul 7, 2024
f747778
fix recovery
dignifiedquire Aug 11, 2024
bfba03a
disable slow tests for now
dignifiedquire Aug 11, 2024
7da4ec2
fix most pss tests
dignifiedquire Aug 11, 2024
63c14a3
Merge remote-tracking branch 'origin/master' into const-crypto-biguint
dignifiedquire Aug 11, 2024
f61ed75
fix proptest
dignifiedquire Aug 11, 2024
74d3197
fixup
dignifiedquire Aug 11, 2024
ea2236d
Merge remote-tracking branch 'origin/master' into const-crypto-biguint
dignifiedquire Nov 6, 2024
1794360
refactor: switch exponent to BoxedUint
dignifiedquire Nov 6, 2024
ac41749
update to latest crypto bigint
dignifiedquire Nov 6, 2024
3ff3c10
fixup benchmark code
dignifiedquire Nov 6, 2024
5402e8c
Fix 2049 bit RSA issues and roundtrip PKCS#1 and PKCS#8
Fethbita Dec 1, 2024
ec599ee
Fix clippy and fmt issues
Fethbita Dec 1, 2024
e15b6a4
Fix the doctests in Oaep
Fethbita Dec 1, 2024
dcc85c6
small cleanup and feature fix
dignifiedquire Dec 1, 2024
4b68379
Merge remote-tracking branch 'origin/master' into const-crypto-biguint
dignifiedquire Dec 1, 2024
b8312e9
remove unused ci step
dignifiedquire Dec 1, 2024
ffdc5c2
fix: handle wasm compiliaton
dignifiedquire Dec 1, 2024
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
28 changes: 28 additions & 0 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pkcs8 = { version = "0.10.2", default-features = false, features = ["alloc"] }
signature = { version = ">2.0, <2.3", default-features = false , features = ["alloc", "digest", "rand_core"] }
spki = { version = "0.7.2", default-features = false, features = ["alloc"] }
zeroize = { version = "1.5", features = ["alloc"] }
crypto-bigint = { version = "0.6.0-pre.0", features = ["zeroize", "alloc"] }

# optional dependencies
sha1 = { version = "0.10.5", optional = true, default-features = false, features = ["oid"] }
Expand Down Expand Up @@ -64,3 +65,6 @@ rustdoc-args = ["--cfg", "docsrs"]

[profile.dev]
opt-level = 2

[patch.crates-io]
crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint", branch = "master" }
198 changes: 100 additions & 98 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
//! Generic RSA implementation

use alloc::borrow::Cow;
use alloc::vec::Vec;
use num_bigint::{BigInt, BigUint, IntoBigInt, IntoBigUint, ModInverse, RandBigInt, ToBigInt};
use crypto_bigint::modular::{BoxedResidue, BoxedResidueParams};
use crypto_bigint::{BoxedUint, RandomMod};
use num_bigint::{BigUint, ModInverse};
use num_integer::{sqrt, Integer};
use num_traits::{FromPrimitive, One, Pow, Signed, Zero};
use num_traits::{FromPrimitive, One, Pow, Zero as _};
use rand_core::CryptoRngCore;
use zeroize::{Zeroize, Zeroizing};

use crate::errors::{Error, Result};
use crate::traits::{PrivateKeyParts, PublicKeyParts};
use crate::key::{reduce, to_biguint};
use crate::traits::keys::{PrivateKeyPartsNew, PublicKeyPartsNew};
use crate::traits::PublicKeyParts;

/// ⚠️ Raw RSA encryption of m with the public key. No padding is performed.
///
Expand All @@ -19,7 +22,7 @@ use crate::traits::{PrivateKeyParts, PublicKeyParts};
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_encrypt<K: PublicKeyParts>(key: &K, m: &BigUint) -> Result<BigUint> {
Ok(m.modpow(key.e(), key.n()))
Ok(m.modpow(&key.e(), &key.n()))
}

/// ⚠️ Performs raw RSA decryption with no padding or error checking.
Expand All @@ -33,92 +36,79 @@ pub fn rsa_encrypt<K: PublicKeyParts>(key: &K, m: &BigUint) -> Result<BigUint> {
#[inline]
pub fn rsa_decrypt<R: CryptoRngCore + ?Sized>(
mut rng: Option<&mut R>,
priv_key: &impl PrivateKeyParts,
c: &BigUint,
priv_key: &impl PrivateKeyPartsNew,
c: &BoxedUint,
) -> Result<BigUint> {
if c >= priv_key.n() {
let n = priv_key.n();
let d = priv_key.d();

if c >= n {
return Err(Error::Decryption);
}

if priv_key.n().is_zero() {
// TODO: is this fine?
if n.is_zero().into() {
return Err(Error::Decryption);
}

let mut ir = None;

let n_params = priv_key
.residue_params()
.cloned()
.unwrap_or_else(|| BoxedResidueParams::new(n.clone().get()).unwrap());

let c = if let Some(ref mut rng) = rng {
let (blinded, unblinder) = blind(rng, priv_key, c);
let (blinded, unblinder) = blind(rng, priv_key, &c, &n_params);
ir = Some(unblinder);
Cow::Owned(blinded)
} else {
Cow::Borrowed(c)
};

let dp = priv_key.dp();
let dq = priv_key.dq();
let qinv = priv_key.qinv();
let crt_values = priv_key.crt_values();

let m = match (dp, dq, qinv, crt_values) {
(Some(dp), Some(dq), Some(qinv), Some(crt_values)) => {
// We have the precalculated values needed for the CRT.

let p = &priv_key.primes()[0];
let q = &priv_key.primes()[1];

let mut m = c.modpow(dp, p).into_bigint().unwrap();
let mut m2 = c.modpow(dq, q).into_bigint().unwrap();

m -= &m2;

let mut primes: Vec<_> = priv_key
.primes()
.iter()
.map(ToBigInt::to_bigint)
.map(Option::unwrap)
.collect();

while m.is_negative() {
m += &primes[0];
}
m *= qinv;
m %= &primes[0];
m *= &primes[1];
m += &m2;

let mut c = c.into_owned().into_bigint().unwrap();
for (i, value) in crt_values.iter().enumerate() {
let prime = &primes[2 + i];
m2 = c.modpow(&value.exp, prime);
m2 -= &m;
m2 *= &value.coeff;
m2 %= prime;
while m2.is_negative() {
m2 += prime;
}
m2 *= &value.r;
m += &m2;
}

// clear tmp values
for prime in primes.iter_mut() {
prime.zeroize();
}
primes.clear();
c.zeroize();
m2.zeroize();

m.into_biguint().expect("failed to decrypt")
}
_ => c.modpow(priv_key.d(), priv_key.n()),
let has_precomputes = priv_key.dp().is_some();
let is_multiprime = priv_key.primes().len() > 2;

let m = if is_multiprime || !has_precomputes {
// c^d (mod n)
pow_mod_params(&c, &d, n_params.clone())
} else {
// We have the precalculated values needed for the CRT.

let dp = priv_key.dp().unwrap();
let dq = priv_key.dq().unwrap();
let qinv = priv_key.qinv().unwrap();
let p_params = priv_key.p_params().unwrap();
let q_params = priv_key.q_params().unwrap();

let p = &priv_key.primes()[0];
let q = &priv_key.primes()[1];

// precomputed: dP = (1/e) mod (p-1) = d mod (p-1)
// precomputed: dQ = (1/e) mod (q-1) = d mod (q-1)

// m1 = c^dP mod p
let m1 = pow_mod_params(&c, &dp, p_params.clone());
// m2 = c^dQ mod q
let m2 = pow_mod_params(&c, &dq, q_params.clone());

// precomputed: qInv = (1/q) mod p

// h = qInv.(m1 - m2) mod p
let x = m1.sub_mod(&m2, p);
let h = mul_mod_params(qinv, &x, p_params.clone());
// m = m2 + h.q
let m = m2.wrapping_add(&h.wrapping_mul(q)); // TODO: verify wrapping is correct here
m
};

match ir {
Some(ref ir) => {
// unblind
Ok(unblind(priv_key, &m, ir))
let res = to_biguint(&unblind(&m, ir, n_params));
Ok(res)
}
None => Ok(m),
None => Ok(to_biguint(&m)),
}
}

Expand All @@ -133,67 +123,79 @@ pub fn rsa_decrypt<R: CryptoRngCore + ?Sized>(
/// or signature scheme. See the [module-level documentation][crate::hazmat] for more information.
#[inline]
pub fn rsa_decrypt_and_check<R: CryptoRngCore + ?Sized>(
priv_key: &impl PrivateKeyParts,
priv_key: &impl PrivateKeyPartsNew,
rng: Option<&mut R>,
c: &BigUint,
c: &BoxedUint,
) -> Result<BigUint> {
let m = rsa_decrypt(rng, priv_key, c)?;

// In order to defend against errors in the CRT computation, m^e is
// calculated, which should match the original ciphertext.
let check = rsa_encrypt(priv_key, &m)?;

if c != &check {
if to_biguint(c) != check {
return Err(Error::Internal);
}

Ok(m)
}

/// Returns the blinded c, along with the unblinding factor.
fn blind<R: CryptoRngCore, K: PublicKeyParts>(
fn blind<R: CryptoRngCore, K: PublicKeyPartsNew>(
rng: &mut R,
key: &K,
c: &BigUint,
) -> (BigUint, BigUint) {
c: &BoxedUint,
n_params: &BoxedResidueParams,
) -> (BoxedUint, BoxedUint) {
// Blinding involves multiplying c by r^e.
// Then the decryption operation performs (m^e * r^e)^d mod n
// which equals mr mod n. The factor of r can then be removed
// by multiplying by the multiplicative inverse of r.

let mut r: BigUint;
let mut ir: Option<BigInt>;
let unblinder;
loop {
r = rng.gen_biguint_below(key.n());
if r.is_zero() {
r = BigUint::one();
}
ir = r.clone().mod_inverse(key.n());
if let Some(ir) = ir {
if let Some(ub) = ir.into_biguint() {
unblinder = ub;
break;
}
debug_assert_eq!(&key.n().clone().get(), n_params.modulus());

let mut r: BoxedUint = BoxedUint::one();
let mut ir: Option<BoxedUint> = None;
while ir.is_none() {
r = BoxedUint::random_mod(rng, key.n());
if r.is_zero().into() {
r = BoxedUint::one();
}
}

let c = {
let mut rpowe = r.modpow(key.e(), key.n()); // N != 0
let mut c = c * &rpowe;
c %= key.n();
// r^-1 (mod n)
ir = r.inv_mod(key.n()).into();
}

let blinded = {
// r^e (mod n)
let mut rpowe = pow_mod_params(&r, key.e(), n_params.clone());
// c * r^e (mod n)
let c = mul_mod_params(c, &rpowe, n_params.clone());
rpowe.zeroize();

c
};

(c, unblinder)
(blinded, ir.unwrap())
}

/// Given an m and and unblinding factor, unblind the m.
fn unblind(key: &impl PublicKeyParts, m: &BigUint, unblinder: &BigUint) -> BigUint {
(m * unblinder) % key.n()
fn unblind(m: &BoxedUint, unblinder: &BoxedUint, n_params: BoxedResidueParams) -> BoxedUint {
// m * r^-1 (mod n)
mul_mod_params(m, unblinder, n_params)
}

/// Computes `base.pow_mod(exp, n)` with precomputed `n_params`.
fn pow_mod_params(base: &BoxedUint, exp: &BoxedUint, n_params: BoxedResidueParams) -> BoxedUint {
let base = reduce(&base, n_params);
base.pow(exp).retrieve()
}

/// Computes `lhs.mul_mod(rhs, n)` with precomputed `n_params`.
fn mul_mod_params(lhs: &BoxedUint, rhs: &BoxedUint, n_params: BoxedResidueParams) -> BoxedUint {
// TODO: nicer api in crypto-bigint?
let lhs = BoxedResidue::new(lhs, n_params.clone());
let rhs = BoxedResidue::new(rhs, n_params);
(lhs * rhs).retrieve()
}

/// The following (deterministic) algorithm also recovers the prime factors `p` and `q` of a modulus `n`, given the
Expand Down
11 changes: 7 additions & 4 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! `pkcs1` crate's traits for types which impl the `pkcs8` crate's traits.

use crate::{
key::to_biguint,
traits::{PrivateKeyParts, PublicKeyParts},
BigUint, RsaPrivateKey, RsaPublicKey,
};
Expand Down Expand Up @@ -72,10 +73,12 @@ impl EncodePrivateKey for RsaPrivateKey {
let modulus = self.n().to_bytes_be();
let public_exponent = self.e().to_bytes_be();
let private_exponent = Zeroizing::new(self.d().to_bytes_be());
let prime1 = Zeroizing::new(self.primes[0].to_bytes_be());
let prime2 = Zeroizing::new(self.primes[1].to_bytes_be());
let exponent1 = Zeroizing::new((self.d() % (&self.primes[0] - 1u8)).to_bytes_be());
let exponent2 = Zeroizing::new((self.d() % (&self.primes[1] - 1u8)).to_bytes_be());
let prime1 = Zeroizing::new(to_biguint(&self.primes[0]).to_bytes_be());
let prime2 = Zeroizing::new(to_biguint(&self.primes[1]).to_bytes_be());
let exponent1 =
Zeroizing::new((self.d() % (&to_biguint(&self.primes[0]) - 1u8)).to_bytes_be());
let exponent2 =
Zeroizing::new((self.d() % (&to_biguint(&self.primes[1]) - 1u8)).to_bytes_be());
let coefficient = Zeroizing::new(
self.crt_coefficient()
.ok_or(pkcs1::Error::Crypto)?
Expand Down
Loading
Loading