From c17e6fb663ea51963ba5617c5a736e237e952058 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 27 Aug 2023 17:48:47 -0600 Subject: [PATCH] [WIP] pkcs5: simple `pbes2::Params` constructors Adds constructors which handle generating PHF salts and cipher IVs from a provided RNG. --- Cargo.lock | 1 + pkcs5/Cargo.toml | 2 ++ pkcs5/src/lib.rs | 3 ++ pkcs5/src/pbes2.rs | 69 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index db83bb246..b24959f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1022,6 +1022,7 @@ dependencies = [ "des", "hex-literal", "pbkdf2", + "rand_core", "scrypt", "sha1", "sha2", diff --git a/pkcs5/Cargo.toml b/pkcs5/Cargo.toml index 57ab0a215..366562a5f 100644 --- a/pkcs5/Cargo.toml +++ b/pkcs5/Cargo.toml @@ -23,6 +23,7 @@ cbc = { version = "0.1.2", optional = true } aes = { version = "0.8.3", optional = true, default-features = false } des = { version = "0.8.1", optional = true, default-features = false } pbkdf2 = { version = "0.12.1", optional = true, default-features = false } +rand_core = { version = "0.6.4", optional = true, default-features = false } scrypt = { version = "0.11", optional = true, default-features = false } sha1 = { version = "0.10.1", optional = true, default-features = false } sha2 = { version = "0.10.2", optional = true, default-features = false } @@ -34,6 +35,7 @@ hex-literal = "0.4" alloc = [] 3des = ["dep:des", "pbes2"] des-insecure = ["dep:des", "pbes2"] +getrandom = ["rand_core/getrandom"] pbes2 = ["dep:aes", "dep:cbc", "dep:pbkdf2", "dep:scrypt", "dep:sha2"] sha1-insecure = ["dep:sha1", "pbes2"] diff --git a/pkcs5/src/lib.rs b/pkcs5/src/lib.rs index f8439ad90..550cd0d5e 100644 --- a/pkcs5/src/lib.rs +++ b/pkcs5/src/lib.rs @@ -21,6 +21,9 @@ //! the [`Decode`] and [`Encode`] traits from the [`der`] crate, and can be //! used for decoding/encoding PKCS#5 `AlgorithmIdentifier` fields. //! +//! The [`pbes2::Parameters`] struct can be used to generate new encryption +//! parameters when encrypting new keys. +//! //! [RFC 8018]: https://tools.ietf.org/html/rfc8018 #[cfg(all(feature = "alloc", feature = "pbes2"))] diff --git a/pkcs5/src/pbes2.rs b/pkcs5/src/pbes2.rs index 095ae1ddb..50483dd5e 100644 --- a/pkcs5/src/pbes2.rs +++ b/pkcs5/src/pbes2.rs @@ -18,6 +18,9 @@ use der::{ Decode, DecodeValue, Encode, EncodeValue, ErrorKind, Length, Reader, Sequence, Tag, Writer, }; +#[cfg(feature = "rand_core")] +use rand_core::CryptoRngCore; + #[cfg(all(feature = "alloc", feature = "pbes2"))] use alloc::vec::Vec; @@ -75,6 +78,42 @@ pub struct Parameters { } impl Parameters { + /// Default length of an initialization vector. + #[cfg(feature = "rand_core")] + const DEFAULT_IV_LEN: usize = AES_BLOCK_SIZE; + + /// Default length of a salt for password hashing. + #[cfg(feature = "rand_core")] + const DEFAULT_SALT_LEN: usize = 16; + + /// Generate PBES2 parameters using the recommended algorithm settings and + /// a randomly generated salt and IV. + /// + /// This is currently an alias for [`Parameters::scrypt`]. See that method + /// for more information. + #[cfg(all(feature = "pbes2", feature = "rand_core"))] + pub fn recommended(rng: &mut impl CryptoRngCore) -> Self { + Self::scrypt(rng) + } + + /// Generate PBES2 parameters using PBKDF2 as the password hashing + /// algorithm, using that algorithm's recommended algorithm settings + /// (OWASP recommended default: 600,000 rounds) along with a randomly + /// generated salt and IV. + /// + /// This will use AES-256-CBC as the encryption algorithm and SHA-256 as + /// the hash function for PBKDF2. + #[cfg(feature = "rand_core")] + pub fn pbkdf2(rng: &mut impl CryptoRngCore) -> Self { + let mut iv = [0u8; Self::DEFAULT_IV_LEN]; + rng.fill_bytes(&mut iv); + + let mut salt = [0u8; Self::DEFAULT_SALT_LEN]; + rng.fill_bytes(&mut salt); + + Self::pbkdf2_sha256_aes256cbc(600_000, &salt, iv).expect("invalid PBKDF2 parameters") + } + /// Initialize PBES2 parameters using PBKDF2-SHA256 as the password-based /// key derivation function and AES-128-CBC as the symmetric cipher. pub fn pbkdf2_sha256_aes128cbc( @@ -99,6 +138,36 @@ impl Parameters { Ok(Self { kdf, encryption }) } + /// Generate PBES2 parameters using scrypt as the password hashing + /// algorithm, using that algorithm's recommended algorithm settings + /// along with a randomly generated salt and IV. + /// + /// This will use AES-256-CBC as the encryption algorithm. + /// + /// scrypt parameters are deliberately chosen to retain compatibility with + /// OpenSSL v3. See [RustCrypto/formats#1205] for more information. + /// Parameter choices are as follows: + /// + /// - `log_n`: 14 + /// - `r`: 8 + /// - `p`: 1 + /// - salt length: 16 + /// + /// [RustCrypto/formats#1205]: https://github.com/RustCrypto/formats/issues/1205 + #[cfg(all(feature = "pbes2", feature = "rand_core"))] + pub fn scrypt(rng: &mut impl CryptoRngCore) -> Self { + let mut iv = [0u8; Self::DEFAULT_IV_LEN]; + rng.fill_bytes(&mut iv); + + let mut salt = [0u8; Self::DEFAULT_SALT_LEN]; + rng.fill_bytes(&mut salt); + + scrypt::Params::new(14, 8, 1, 32) + .ok() + .and_then(|params| Self::scrypt_aes256cbc(params, &salt, iv).ok()) + .expect("invalid scrypt parameters") + } + /// Initialize PBES2 parameters using scrypt as the password-based /// key derivation function and AES-128-CBC as the symmetric cipher. ///