diff --git a/.github/workflows/pkcs5.yml b/.github/workflows/pkcs5.yml
index 80dfef981..6a0b03a4c 100644
--- a/.github/workflows/pkcs5.yml
+++ b/.github/workflows/pkcs5.yml
@@ -38,7 +38,7 @@ jobs:
           toolchain: ${{ matrix.rust }}
           targets: ${{ matrix.target }}
       - uses: RustCrypto/actions/cargo-hack-install@master
-      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std
+      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features getrandom,std
 
 # TODO(tarcieri): re-enable this when we're not using unpublished prerelease dependencies
 #  minimal-versions:
diff --git a/Cargo.lock b/Cargo.lock
index d0a015eb0..87e2d9e81 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1061,6 +1061,7 @@ dependencies = [
  "des",
  "hex-literal 0.4.1",
  "pbkdf2",
+ "rand_core",
  "scrypt",
  "sha1",
  "sha2",
diff --git a/pkcs5/Cargo.toml b/pkcs5/Cargo.toml
index 7f84aee58..7e57dd0fe 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.6", optional = true, default-features = false }
 sha2 = { version = "0.10.8", 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.
     ///