diff --git a/Cargo.lock b/Cargo.lock index 003ff7a42..feee9050e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1036,6 +1036,7 @@ dependencies = [ "cbc", "der", "des", + "getrandom 0.4.2", "hex-literal", "pbkdf2", "rand_core 0.10.1", diff --git a/pkcs5/Cargo.toml b/pkcs5/Cargo.toml index 43e0fe8f1..8383bfa53 100644 --- a/pkcs5/Cargo.toml +++ b/pkcs5/Cargo.toml @@ -24,6 +24,7 @@ aes = { version = "0.9", optional = true, default-features = false } cbc = { version = "0.2", optional = true } des = { version = "0.9", optional = true, default-features = false } pbkdf2 = { version = "0.13", optional = true, default-features = false, features = ["hmac"] } +getrandom = { version = "0.4", optional = true, features = ["sys_rng"] } rand_core = { version = "0.10", optional = true, default-features = false } scrypt = { version = "0.12", optional = true, default-features = false } sha1 = { version = "0.11", optional = true, default-features = false } @@ -37,7 +38,9 @@ alloc = [] 3des = ["dep:des", "pbes2"] des-insecure = ["dep:des", "pbes2"] +getrandom = ["dep:getrandom", "rand_core"] pbes2 = ["dep:aes", "dep:cbc", "dep:pbkdf2", "dep:scrypt", "dep:sha2"] +rand_core = ["dep:rand_core"] sha1-insecure = ["dep:sha1", "pbes2"] [lints] diff --git a/pkcs5/src/lib.rs b/pkcs5/src/lib.rs index 56ca6c246..59aaac373 100644 --- a/pkcs5/src/lib.rs +++ b/pkcs5/src/lib.rs @@ -40,7 +40,15 @@ pub use scrypt; #[cfg(all(feature = "alloc", feature = "pbes2"))] use alloc::vec::Vec; -/// Supported PKCS#5 password-based encryption schemes. +/// Configuration for supported PKCS#5 password-based encryption schemes. +/// +///
+/// Security Warning +/// +/// This type should not be used to encrypt multiple plaintexts under the same IV/salt values. +/// +/// Instead, new values should be randomly generated for every usage. +///
#[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] #[allow(clippy::large_enum_variant)] @@ -57,13 +65,25 @@ pub enum EncryptionScheme { } impl EncryptionScheme { + /// Generate PBES2 parameters using recommended algorithm settings and parameters (salt/IV) + /// generated using the system's secure random number generator. + /// + /// # Panics + /// In the event the system's secure random generator experiences an internal failure. + #[cfg(all(feature = "pbes2", feature = "getrandom"))] + #[must_use] + #[track_caller] + pub fn generate() -> Self { + Self::Pbes2(pbes2::Parameters::generate()) + } + /// Attempt to decrypt the given ciphertext, allocating and returning a byte vector containing /// the plaintext. /// /// # Errors /// Returns an error if the algorithm specified in this scheme's parameters is unsupported - /// (e.g. PBES1 is completely unsupported), or if the ciphertext is malformed (e.g. not a - /// multiple of a block mode's padding). + /// (e.g. PBES1 is completely unsupported), or if the ciphertext is malformed (e.g. ciphertext + /// length is not a multiple of a block mode's padding). #[cfg(all(feature = "alloc", feature = "pbes2"))] pub fn decrypt(&self, password: impl AsRef<[u8]>, ciphertext: &[u8]) -> Result> { match self { diff --git a/pkcs5/src/pbes2.rs b/pkcs5/src/pbes2.rs index a53cf2af6..aed1cb4ef 100644 --- a/pkcs5/src/pbes2.rs +++ b/pkcs5/src/pbes2.rs @@ -18,7 +18,7 @@ use der::{ asn1::{AnyRef, ObjectIdentifier, OctetStringRef}, }; -#[cfg(feature = "rand_core")] +#[cfg(all(feature = "pbes2", feature = "rand_core"))] use rand_core::TryCryptoRng; #[cfg(all(feature = "alloc", feature = "pbes2"))] @@ -67,6 +67,18 @@ const DES_BLOCK_SIZE: usize = 8; /// encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} } /// ``` /// +/// These define a set of algorithms for password-based key derivation, as well as a salt value +/// (typically randomly generated) to provide to the KDF algorithm, along with an encryption +/// algorithm and its associated IV/nonce (typically randomly generated). +/// +///
+/// Security Warning +/// +/// This type should not be used to encrypt multiple plaintexts under the same IV/salt values. +/// +/// Instead, new values should be randomly generated for every usage. +///
+/// /// [RFC 8018 Appendix A.4]: https://tools.ietf.org/html/rfc8018#appendix-A.4 #[derive(Clone, Debug, Eq, PartialEq)] pub struct Parameters { @@ -79,15 +91,27 @@ pub struct Parameters { impl Parameters { /// Default length of an initialization vector. - #[cfg(feature = "rand_core")] + #[cfg(all(feature = "pbes2", feature = "rand_core"))] const DEFAULT_IV_LEN: usize = AES_BLOCK_SIZE; /// Default length of a salt for password hashing. - #[cfg(feature = "rand_core")] + #[cfg(all(feature = "pbes2", feature = "rand_core"))] const DEFAULT_SALT_LEN: usize = 16; - /// Generate PBES2 parameters using the recommended algorithm settings and - /// a randomly generated salt and IV. + /// Generate PBES2 parameters using recommended algorithm settings and parameters (salt/IV) + /// generated using the system's secure random number generator. + /// + /// # Panics + /// In the event the system's secure random generator experiences an internal failure. + #[cfg(all(feature = "pbes2", feature = "getrandom"))] + #[must_use] + #[track_caller] + pub fn generate() -> Self { + Self::generate_recommended(&mut getrandom::SysRng).expect("random generation failure") + } + + /// Generate PBES2 parameters using the recommended algorithm settings and a randomly generated + /// salt and IV. /// /// This is currently an alias for [`Parameters::generate_scrypt`]. See that method /// for more information. @@ -109,7 +133,7 @@ impl Parameters { /// /// # Errors /// Returns [`Error::Rng`] in the event the random number generator `R` fails. - #[cfg(feature = "rand_core")] + #[cfg(all(feature = "pbes2", feature = "rand_core"))] pub fn generate_pbkdf2(rng: &mut R) -> Result { let mut iv = [0u8; Self::DEFAULT_IV_LEN]; rng.try_fill_bytes(&mut iv).map_err(|_| Error::Rng)?;