Skip to content

Commit 96e059c

Browse files
authored
Shorten widths of BoxedUints (#506)
Fixes #490 - Bump `crypto-bigint` dependency to the current trunk. - Use the width of a single prime instead of the full modulus where possible. Brings `bench_rsa_2048_pkcsv1_decrypt` to pre-`crypto-bigint` values. - Add a check to `RsaPrivateKey::from_components()` to ensure consistency between the primes and the modulus. - Remove zero padding limbs from the primes and the modulus in `RsaPrivateKey::from_components()`. - Add a check to `rsa_decrypt()` to ensure the bit precision of the ciphertext is the same as that of the modulus. Notes: - `bench_rsa_2048_pkcsv1_sign_blinded` can be restored to the original performance by using variable-time inversion in `algorithms::rsa::blind()` (as it was during `num-bigint` times), but it seems to me that the blinding factor must be kept secret, so we have to use the constant-time inversion. This leads to about 5x slowdown compared to pre-`crypto-bigint` performance. - The changes in test vectors are due to RustCrypto/crypto-bigint#781 ### Possible further improvements - Keep precomputed values as `Odd`/`NonZero` as appropriate. - If RustCrypto/crypto-bigint#811 is fixed, resizing integers for the purposes of division can be avoided.
1 parent cba7806 commit 96e059c

13 files changed

+190
-87
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/algorithms/rsa.rs

Lines changed: 101 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use core::cmp::Ordering;
44

55
use crypto_bigint::modular::{BoxedMontyForm, BoxedMontyParams};
6-
use crypto_bigint::{BoxedUint, Gcd, NonZero, Odd, RandomMod, Wrapping};
6+
use crypto_bigint::{BoxedUint, Gcd, NonZero, Odd, RandomMod, Resize};
77
use rand_core::TryCryptoRng;
88
use zeroize::Zeroize;
99

@@ -39,6 +39,10 @@ pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
3939
let n = priv_key.n();
4040
let d = priv_key.d();
4141

42+
if c.bits_precision() != n.as_ref().bits_precision() {
43+
return Err(Error::Decryption);
44+
}
45+
4246
if c >= n.as_ref() {
4347
return Err(Error::Decryption);
4448
}
@@ -51,9 +55,9 @@ pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
5155
let c = if let Some(rng) = rng {
5256
let (blinded, unblinder) = blind(rng, priv_key, c, n_params)?;
5357
ir = Some(unblinder);
54-
blinded.widen(bits)
58+
blinded.try_resize(bits).ok_or(Error::Internal)?
5559
} else {
56-
c.widen(bits)
60+
c.try_resize(bits).ok_or(Error::Internal)?
5761
};
5862

5963
let is_multiprime = priv_key.primes().len() > 2;
@@ -68,41 +72,61 @@ pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
6872
(Some(dp), Some(dq), Some(qinv), Some(p_params), Some(q_params)) if !is_multiprime => {
6973
// We have the precalculated values needed for the CRT.
7074

71-
let _p = &priv_key.primes()[0];
75+
let p = &priv_key.primes()[0];
7276
let q = &priv_key.primes()[1];
7377

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

81+
// TODO: it may be faster to convert to and from Montgomery with prepared parameters
82+
// (modulo `p` and `q`) rather than calculating the remainder directly.
83+
7784
// m1 = c^dP mod p
78-
let cp = BoxedMontyForm::new(c.clone(), p_params.clone());
85+
let p_wide = p_params.modulus().resize_unchecked(c.bits_precision());
86+
let c_mod_dp = (&c % p_wide.as_nz_ref()).resize_unchecked(dp.bits_precision());
87+
let cp = BoxedMontyForm::new(c_mod_dp, p_params.clone());
7988
let mut m1 = cp.pow(dp);
8089
// m2 = c^dQ mod q
81-
let cq = BoxedMontyForm::new(c, q_params.clone());
90+
let q_wide = q_params.modulus().resize_unchecked(c.bits_precision());
91+
let c_mod_dq = (&c % q_wide.as_nz_ref()).resize_unchecked(dq.bits_precision());
92+
let cq = BoxedMontyForm::new(c_mod_dq, q_params.clone());
8293
let m2 = cq.pow(dq).retrieve();
8394

95+
// Note that since `p` and `q` may have different `bits_precision`,
96+
// it may be different for `m1` and `m2` as well.
97+
8498
// (m1 - m2) mod p = (m1 mod p) - (m2 mod p) mod p
85-
let m2r = BoxedMontyForm::new(m2.clone(), p_params.clone());
99+
let m2_mod_p = match p_params.bits_precision().cmp(&q_params.bits_precision()) {
100+
Ordering::Less => {
101+
let p_wide = NonZero::new(p.clone())
102+
.expect("`p` is non-zero")
103+
.resize_unchecked(q_params.bits_precision());
104+
(&m2 % p_wide).resize_unchecked(p_params.bits_precision())
105+
}
106+
Ordering::Greater => (&m2).resize_unchecked(p_params.bits_precision()),
107+
Ordering::Equal => m2.clone(),
108+
};
109+
let m2r = BoxedMontyForm::new(m2_mod_p, p_params.clone());
86110
m1 -= &m2r;
87111

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

90114
// h = qInv.(m1 - m2) mod p
91-
let mut m: Wrapping<BoxedUint> = Wrapping(qinv.mul(&m1).retrieve());
115+
let h = (qinv * m1).retrieve();
92116

93117
// m = m2 + h.q
94-
m *= Wrapping(q.clone());
95-
m += Wrapping(m2);
96-
m.0
118+
let m2 = m2.try_resize(n.bits_precision()).ok_or(Error::Internal)?;
119+
let hq = (h * q)
120+
.try_resize(n.bits_precision())
121+
.ok_or(Error::Internal)?;
122+
m2.wrapping_add(&hq)
97123
}
98124
_ => {
99125
// c^d (mod n)
100126
pow_mod_params(&c, d, n_params)
101127
}
102128
};
103129

104-
// Ensure output precision matches input precision
105-
let m = m.shorten(n_params.bits_precision());
106130
match ir {
107131
Some(ref ir) => {
108132
// unblind
@@ -118,6 +142,8 @@ pub fn rsa_decrypt<R: TryCryptoRng + ?Sized>(
118142
/// Returns a plaintext `BoxedUint`. Performs RSA blinding if an `Rng` is passed. This will also
119143
/// check for errors in the CRT computation.
120144
///
145+
/// `c` must have the same `bits_precision` as the RSA key modulus.
146+
///
121147
/// # ☢️️ WARNING: HAZARDOUS API ☢️
122148
///
123149
/// Use this function with great care! Raw RSA should never be used without an appropriate padding
@@ -209,16 +235,8 @@ fn pow_mod_params(base: &BoxedUint, exp: &BoxedUint, n_params: &BoxedMontyParams
209235
}
210236

211237
fn reduce_vartime(n: &BoxedUint, p: &BoxedMontyParams) -> BoxedMontyForm {
212-
let bits_precision = p.modulus().bits_precision();
213238
let modulus = p.modulus().as_nz_ref().clone();
214-
215-
let n = match n.bits_precision().cmp(&bits_precision) {
216-
Ordering::Less => n.widen(bits_precision),
217-
Ordering::Equal => n.clone(),
218-
Ordering::Greater => n.shorten(bits_precision),
219-
};
220-
221-
let n_reduced = n.rem_vartime(&modulus).widen(p.bits_precision());
239+
let n_reduced = n.rem_vartime(&modulus).resize_unchecked(p.bits_precision());
222240
BoxedMontyForm::new(n_reduced, p.clone())
223241
}
224242

@@ -248,19 +266,20 @@ pub fn recover_primes(
248266

249267
// 1. Let a = (de – 1) × GCD(n – 1, de – 1).
250268
let bits = d.bits_precision() * 2;
251-
let one = BoxedUint::one().widen(bits);
252-
let e = e.widen(bits);
253-
let d = d.widen(bits);
254-
let n = n.as_ref().widen(bits);
269+
let one = BoxedUint::one_with_precision(bits);
270+
let e = e.resize_unchecked(d.bits_precision());
271+
let d = d.resize_unchecked(d.bits_precision());
272+
let n = n.resize_unchecked(bits);
255273

256-
let a1 = &d * &e - &one;
257-
let a2 = (&n - &one).gcd(&a1);
274+
let a1 = d * e - &one;
275+
let a2 = (n.as_ref() - &one).gcd(&a1);
258276
let a = a1 * a2;
259-
let n = n.widen(a.bits_precision());
277+
let n = n.resize_unchecked(a.bits_precision());
260278

261279
// 2. Let m = floor(a /n) and r = a – m n, so that a = m n + r and 0 ≤ r < n.
262-
let m = &a / NonZero::new(n.clone()).expect("checked");
263-
let r = a - &m * &n;
280+
let m = &a / &n;
281+
let r = a - &m * n.as_ref();
282+
let n = n.get();
264283

265284
// 3. Let b = ( (n – r)/(m + 1) ) + 1; if b is not an integer or b^2 ≤ 4n, then output an error indicator,
266285
// and exit without further processing.
@@ -292,7 +311,7 @@ pub fn recover_primes(
292311
let bits = core::cmp::max(b.bits_precision(), y.bits_precision());
293312
let two = NonZero::new(BoxedUint::from(2u64))
294313
.expect("2 is non zero")
295-
.widen(bits);
314+
.resize_unchecked(bits);
296315
let p = (&b + &y) / &two;
297316
let q = (b - y) / two;
298317

@@ -325,7 +344,7 @@ pub(crate) fn compute_private_exponent_euler_totient(
325344
for prime in primes {
326345
totient *= prime - &BoxedUint::one();
327346
}
328-
let exp = exp.widen(totient.bits_precision());
347+
let exp = exp.resize_unchecked(totient.bits_precision());
329348

330349
// NOTE: `mod_inverse` checks if `exp` evenly divides `totient` and returns `None` if so.
331350
// This ensures that `exp` is not a factor of any `(prime - 1)`.
@@ -356,7 +375,7 @@ pub(crate) fn compute_private_exponent_carmicheal(
356375
// LCM inlined
357376
let gcd = p1.gcd(&q1);
358377
let lcm = p1 / NonZero::new(gcd).expect("gcd is non zero") * &q1;
359-
let exp = exp.widen(lcm.bits_precision());
378+
let exp = exp.resize_unchecked(lcm.bits_precision());
360379
if let Some(d) = exp.inv_mod(&lcm).into() {
361380
Ok(d)
362381
} else {
@@ -373,11 +392,55 @@ mod tests {
373392
fn recover_primes_works() {
374393
let bits = 2048;
375394

376-
let n = BoxedUint::from_be_hex("d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", bits).unwrap();
395+
let n = BoxedUint::from_be_hex(
396+
concat!(
397+
"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222",
398+
"b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e",
399+
"74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb",
400+
"28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7",
401+
"ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078c",
402+
"c780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2",
403+
"e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf67",
404+
"76fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b"
405+
),
406+
bits,
407+
)
408+
.unwrap();
377409
let e = BoxedUint::from(65_537u64);
378-
let d = BoxedUint::from_be_hex("c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", bits).unwrap();
379-
let p = BoxedUint::from_be_hex("f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61", bits / 2).unwrap();
380-
let q = BoxedUint::from_be_hex("da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", bits / 2).unwrap();
410+
let d = BoxedUint::from_be_hex(
411+
concat!(
412+
"c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d7395832",
413+
"0dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92f",
414+
"ae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552",
415+
"e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f",
416+
"54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057",
417+
"fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0",
418+
"c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd87",
419+
"7d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341"
420+
),
421+
bits,
422+
)
423+
.unwrap();
424+
let p = BoxedUint::from_be_hex(
425+
concat!(
426+
"f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd",
427+
"624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07b",
428+
"d405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159",
429+
"067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61"
430+
),
431+
bits / 2,
432+
)
433+
.unwrap();
434+
let q = BoxedUint::from_be_hex(
435+
concat!(
436+
"da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7",
437+
"f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b",
438+
"961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909",
439+
"ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b"
440+
),
441+
bits / 2,
442+
)
443+
.unwrap();
381444

382445
let (mut p1, mut q1) = recover_primes(&NonZero::new(n).unwrap(), &e, &d).unwrap();
383446

src/encoding.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
RsaPrivateKey, RsaPublicKey,
99
};
1010
use core::convert::{TryFrom, TryInto};
11-
use crypto_bigint::{BoxedUint, NonZero, Odd};
11+
use crypto_bigint::{BoxedUint, NonZero, Odd, Resize};
1212
use pkcs8::{
1313
der::{asn1::OctetStringRef, Encode},
1414
Document, EncodePrivateKey, EncodePublicKey, ObjectIdentifier, SecretDocument,
@@ -115,13 +115,20 @@ impl EncodePrivateKey for RsaPrivateKey {
115115

116116
let bits = self.d().bits_precision();
117117

118+
debug_assert!(bits >= self.primes[0].bits_vartime());
119+
debug_assert!(bits >= self.primes[1].bits_vartime());
120+
118121
let exponent1 = Zeroizing::new(
119-
(self.d() % NonZero::new(&self.primes[0].widen(bits) - &BoxedUint::one()).unwrap())
120-
.to_be_bytes(),
122+
(self.d()
123+
% NonZero::new((&self.primes[0]).resize_unchecked(bits) - &BoxedUint::one())
124+
.unwrap())
125+
.to_be_bytes(),
121126
);
122127
let exponent2 = Zeroizing::new(
123-
(self.d() % NonZero::new(&self.primes[1].widen(bits) - &BoxedUint::one()).unwrap())
124-
.to_be_bytes(),
128+
(self.d()
129+
% NonZero::new((&self.primes[1]).resize_unchecked(bits) - &BoxedUint::one())
130+
.unwrap())
131+
.to_be_bytes(),
125132
);
126133
let coefficient = Zeroizing::new(
127134
self.crt_coefficient()

0 commit comments

Comments
 (0)