Skip to content
Open
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.9.9 (2025-11-13)
### Fixed
- Support for cryptographic operations with larger keys ([#594])

[#594]: https://github.com/RustCrypto/RSA/pull/594

## 0.9.8 (2025-03-12)
### Added
- Doc comments to specify the `rand` version ([#473])

[#473]: https://github.com/RustCrypto/RSA/pull/473

## 0.9.7 (2024-11-26)
### Fixed
- always validate keys in from_components
- do not crash when handling tiny keys in PKCS1v15

## 0.9.6 (2023-12-01)
### Added
- expose a `pss::get_default_pss_signature_algo_id` helper ([#393])
Expand Down
23 changes: 18 additions & 5 deletions Cargo.lock

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

12 changes: 9 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rsa"
version = "0.9.6"
version = "0.9.9"
authors = ["RustCrypto Developers", "dignifiedquire <dignifiedquire@gmail.com>"]
edition = "2021"
description = "Pure Rust RSA implementation"
Expand Down Expand Up @@ -32,12 +32,12 @@ sha2 = { version = "0.10.6", optional = true, default-features = false, features
serde = { version = "1.0.184", optional = true, default-features = false, features = ["derive"] }

[target.'cfg(target_os = "zkvm")'.dependencies]
risc0-bigint2 = { git = "https://github.com/risc0/risc0", rev = "8fc8437633f08a66e0fbacce947f41d01b074774", default-features = false, features = ["num-bigint-dig"] }
getrandom = { version = "0.2", default-features = false, features = ["custom"] }
risc0-bigint2 = { version = "1.4.11", features = ["num-bigint-dig"] }

[dev-dependencies]
base64ct = { version = "1", features = ["alloc"] }
hex-literal = "0.4.1"
proptest = "1"
serde_test = "1.0.89"
rand_xorshift = "0.3"
rand_chacha = "0.3"
Expand All @@ -47,6 +47,12 @@ sha1 = { version = "0.10.5", default-features = false, features = ["oid"] }
sha2 = { version = "0.10.6", default-features = false, features = ["oid"] }
sha3 = { version = "0.10.7", default-features = false, features = ["oid"] }

[target.'cfg(not(target_os = "zkvm"))'.dev-dependencies]
proptest = "1"

[target.'cfg(target_os = "zkvm")'.dev-dependencies]
proptest = { version = "1", default-features = false, features = ["alloc"] }

[[bench]]
name = "key"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A portable RSA implementation in pure Rust.
```rust
use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey};

let mut rng = rand::thread_rng();
let mut rng = rand::thread_rng(); // rand@0.8
let bits = 2048;
let priv_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
let pub_key = RsaPublicKey::from(&priv_key);
Expand Down
11 changes: 10 additions & 1 deletion src/algorithms/pkcs1v15.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) fn pkcs1v15_encrypt_pad<R>(
where
R: CryptoRngCore + ?Sized,
{
if msg.len() > k - 11 {
if msg.len() + 11 > k {
return Err(Error::MessageTooLong);
}

Expand Down Expand Up @@ -195,4 +195,13 @@ mod tests {
}
}
}

#[test]
fn test_encrypt_tiny_no_crash() {
let mut rng = ChaCha8Rng::from_seed([42; 32]);
let k = 8;
let message = vec![1u8; 4];
let res = pkcs1v15_encrypt_pad(&mut rng, &message, k);
assert_eq!(res, Err(Error::MessageTooLong));
}
}
12 changes: 10 additions & 2 deletions src/algorithms/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use num_bigint::{BigInt, BigUint, IntoBigInt, IntoBigUint, ModInverse, RandBigIn
use num_integer::{sqrt, Integer};
use num_traits::{FromPrimitive, One, Pow, Signed, Zero};
use rand_core::CryptoRngCore;
#[cfg(target_os = "zkvm")]
use risc0_bigint2::ToBigInt2Buffer;
use zeroize::{Zeroize, Zeroizing};

use crate::errors::{Error, Result};
Expand All @@ -22,8 +24,14 @@ pub fn rsa_encrypt<K: PublicKeyParts>(key: &K, m: &BigUint) -> Result<BigUint> {
#[cfg(target_os = "zkvm")]
{
// If we're in the RISC Zero zkVM, try to use an accelerated version.
if *key.e() == BigUint::new(vec![65537]) {
return Ok(risc0_bigint2::rsa::modpow_65537(m, key.n()));
if m.bits() <= 4096 && key.n().bits() <= 4096 && *key.e() == BigUint::new(vec![65537]) {
let mut result = [0u32; risc0_bigint2::field::FIELD_4096_WIDTH_WORDS];
risc0_bigint2::rsa::modpow_65537(
&m.to_u32_array(),
&key.n().to_u32_array(),
&mut result,
);
return Ok(BigUint::from_slice(&result));
}
// Fall through when the exponent does not match the accelerator
}
Expand Down
29 changes: 15 additions & 14 deletions src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ impl RsaPublicKey {
/// Create a new public key from its components.
pub fn new_with_max_size(n: BigUint, e: BigUint, max_size: usize) -> Result<Self> {
let k = Self { n, e };
check_public_with_max_size(&k, max_size)?;
check_public_with_max_size(&k, Some(max_size))?;
Ok(k)
}

Expand Down Expand Up @@ -251,7 +251,6 @@ impl RsaPrivateKey {
d: BigUint,
mut primes: Vec<BigUint>,
) -> Result<RsaPrivateKey> {
let mut should_validate = false;
if primes.len() < 2 {
if !primes.is_empty() {
return Err(Error::NprimesTooSmall);
Expand All @@ -261,7 +260,6 @@ impl RsaPrivateKey {
let (p, q) = recover_primes(&n, &e, &d)?;
primes.push(p);
primes.push(q);
should_validate = true;
}

let mut k = RsaPrivateKey {
Expand All @@ -271,10 +269,8 @@ impl RsaPrivateKey {
precomputed: None,
};

// Validate the key if we had to recover the primes.
if should_validate {
k.validate()?;
}
// Always validate the key, to ensure precompute can't fail
k.validate()?;

// precompute when possible, ignore error otherwise.
let _ = k.precompute();
Expand Down Expand Up @@ -497,14 +493,19 @@ impl PrivateKeyParts for RsaPrivateKey {
/// Check that the public key is well formed and has an exponent within acceptable bounds.
#[inline]
pub fn check_public(public_key: &impl PublicKeyParts) -> Result<()> {
check_public_with_max_size(public_key, RsaPublicKey::MAX_SIZE)
check_public_with_max_size(public_key, None)
}

/// Check that the public key is well formed and has an exponent within acceptable bounds.
#[inline]
fn check_public_with_max_size(public_key: &impl PublicKeyParts, max_size: usize) -> Result<()> {
if public_key.n().bits() > max_size {
return Err(Error::ModulusTooLarge);
fn check_public_with_max_size(
public_key: &impl PublicKeyParts,
max_size: Option<usize>,
) -> Result<()> {
if let Some(max_size) = max_size {
if public_key.n().bits() > max_size {
return Err(Error::ModulusTooLarge);
}
}

let e = public_key
Expand Down Expand Up @@ -717,13 +718,13 @@ mod tests {
Base64::decode_vec("CUWC+hRWOT421kwRllgVjy6FYv6jQUcgDNHeAiYZnf5HjS9iK2ki7v8G5dL/0f+Yf+NhE/4q8w4m8go51hACrVpP1p8GJDjiT09+RsOzITsHwl+ceEKoe56ZW6iDHBLlrNw5/MtcYhKpjNU9KJ2udm5J/c9iislcjgckrZG2IB8ADgXHMEByZ5DgaMl4AKZ1Gx8/q6KftTvmOT5rNTMLi76VN5KWQcDWK/DqXiOiZHM7Nr4dX4me3XeRgABJyNR8Fqxj3N1+HrYLe/zs7LOaK0++F9Ul3tLelhrhsvLxei3oCZkF9A/foD3on3luYA+1cRcxWpSY3h2J4/22+yo4+Q==").unwrap(),
];

RsaPrivateKey::from_components(
let res = RsaPrivateKey::from_components(
BigUint::from_bytes_be(&n),
BigUint::from_bytes_be(&e),
BigUint::from_bytes_be(&d),
primes.iter().map(|p| BigUint::from_bytes_be(p)).collect(),
)
.unwrap();
);
assert_eq!(res, Err(Error::InvalidModulus));
}

#[test]
Expand Down
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
#![cfg_attr(not(feature = "sha2"), doc = "```ignore")]
//! use rsa::{RsaPrivateKey, RsaPublicKey, Oaep, sha2::Sha256};
//!
//! let mut rng = rand::thread_rng();
//! let mut rng = rand::thread_rng(); // rand@0.8
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
Expand All @@ -47,7 +47,7 @@
//! ```
//! use rsa::{RsaPrivateKey, RsaPublicKey, Pkcs1v15Encrypt};
//!
//! let mut rng = rand::thread_rng();
//! let mut rng = rand::thread_rng(); // rand@0.8
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
Expand All @@ -74,7 +74,7 @@
//! use rsa::signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::thread_rng();
//! let mut rng = rand::thread_rng(); // rand@0.8
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
Expand All @@ -101,7 +101,7 @@
//! use rsa::signature::{Keypair,RandomizedSigner, SignatureEncoding, Verifier};
//! use rsa::sha2::{Digest, Sha256};
//!
//! let mut rng = rand::thread_rng();
//! let mut rng = rand::thread_rng(); // rand@0.8
//!
//! let bits = 2048;
//! let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key");
Expand Down
4 changes: 2 additions & 2 deletions src/oaep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ impl Oaep {
/// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap();
/// let e = Base64::decode_vec("AQAB").unwrap();
///
/// let mut rng = rand::thread_rng();
/// let mut rng = rand::thread_rng(); // rand@0.8
/// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap();
/// let padding = Oaep::new::<Sha256>();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
Expand Down Expand Up @@ -98,7 +98,7 @@ impl Oaep {
/// let n = Base64::decode_vec("ALHgDoZmBQIx+jTmgeeHW6KsPOrj11f6CvWsiRleJlQpW77AwSZhd21ZDmlTKfaIHBSUxRUsuYNh7E2SHx8rkFVCQA2/gXkZ5GK2IUbzSTio9qXA25MWHvVxjMfKSL8ZAxZyKbrG94FLLszFAFOaiLLY8ECs7g+dXOriYtBwLUJK+lppbd+El+8ZA/zH0bk7vbqph5pIoiWggxwdq3mEz4LnrUln7r6dagSQzYErKewY8GADVpXcq5mfHC1xF2DFBub7bFjMVM5fHq7RK+pG5xjNDiYITbhLYrbVv3X0z75OvN0dY49ITWjM7xyvMWJXVJS7sJlgmCCL6RwWgP8PhcE=").unwrap();
/// let e = Base64::decode_vec("AQAB").unwrap();
///
/// let mut rng = rand::thread_rng();
/// let mut rng = rand::thread_rng(); // rand@0.8
/// let key = RsaPublicKey::new(BigUint::from_bytes_be(&n), BigUint::from_bytes_be(&e)).unwrap();
/// let padding = Oaep::new_with_mgf_hash::<Sha256, Sha1>();
/// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
Expand Down
7 changes: 1 addition & 6 deletions src/pkcs1v15/signature.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
#[allow(unused_imports)]
pub use ::signature::{
hazmat::{PrehashSigner, PrehashVerifier},
DigestSigner, DigestVerifier, Error, Keypair, RandomizedDigestSigner, RandomizedSigner, Result,
SignatureEncoding, Signer, Verifier,
};
pub use ::signature::SignatureEncoding;
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
Expand Down
9 changes: 2 additions & 7 deletions src/pss/signature.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
#[allow(unused_imports)]
pub use ::signature::{
hazmat::{PrehashSigner, PrehashVerifier},
DigestSigner, DigestVerifier, Error, Keypair, RandomizedDigestSigner, RandomizedSigner, Result,
SignatureEncoding, Signer, Verifier,
};
pub use ::signature::SignatureEncoding;
use spki::{
der::{asn1::BitString, Result as DerResult},
SignatureBitStringEncoding,
der::{Result as DerResult, asn1::BitString},
};

use crate::algorithms::pad::uint_to_be_pad;
Expand Down
10 changes: 10 additions & 0 deletions tests/proptests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,17 @@ prop_compose! {
}
}

fn config() -> ProptestConfig {
if cfg!(all(target_os = "zkvm", target_arch = "riscv32")) {
ProptestConfig::with_cases(1)
} else {
ProptestConfig::default()
}
}

proptest! {
#![proptest_config(config())]

#[test]
fn pkcs1v15_sign_roundtrip(private_key in private_key(), msg in any::<Vec<u8>>()) {
let signing_key = pkcs1v15::SigningKey::<Sha256>::new(private_key);
Expand Down
Loading