Repository: RustCrypto/password-hashes (language: rust)

Issue:
add a convenient way for common pbkdf2::<Hmac<Sha1>>(*** )
eg:
```
let mut bytes = [0u8; 16];
pbkdf2::<Hmac<Sha1>>(password, b"saltysalt", 1003, bytes.as_mut());
```
go lang : https://pkg.go.dev/golang.org/x/crypto/pbkdf2
```
dk := pbkdf2.Key([]byte(password), []byte("saltysalt"), 1003, 16, sha1.New)
```
in rust, there are  additional info:  mut bytes , Hmac,
golang's pbkdf2.Key look like more convenient



Discussion / hints:
It comes to different philosophies between Rust and Go. Go is quite happy to use dynamic dispatch and heap allocated data, while in Rust we prefer monomorphization and stack-based structures. Note that PBKDF2 is defined in terms of a keyed PRF, HMAC is just one example of such function. For example, BLAKE2 functions can be used as such function directly without HMAC.

I guess we could introduce something like this to save on `Hmac` import:
```rust
type pbkdf2_hmac<T> = pbkdf2<Hmac<T>>;
```
It also would allow users to remove the `hmac` crate from their `Cargo.toml`.

But I think we will leave everything else as-is.

P.S.: While we are comparing conveniences, Go's `[]byte(..)` looks terrible to me.
@newpavlov perhaps an allocating API like `pbkdf2_vec` which accepts an output length argument ala Go would be a useful addition?
@tarcieri
I don't think we should introduce allocating methods without a good reason. A better option would be to introduce a const generic based API which returns an array with generated key. The current API was developed long before const generics were stabilized.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7b9088e6..f1a6a96d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -101,18 +101,18 @@ dependencies = [
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.2"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
+checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
 dependencies = [
  "libc",
 ]
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.5"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -120,9 +120,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-deque"
-version = "0.8.1"
+version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
 dependencies = [
  "cfg-if",
  "crossbeam-epoch",
@@ -131,9 +131,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.9"
+version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
+checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
 dependencies = [
  "autocfg",
  "cfg-if",
@@ -145,9 +145,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.9"
+version = "0.8.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ff1f980957787286a554052d03c7aee98d99cc32e09f6d45f0a814133c87978"
+checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -165,9 +165,9 @@ dependencies = [
 
 [[package]]
 name = "crypto-common"
-version = "0.1.3"
+version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
  "generic-array",
  "typenum",
@@ -186,15 +186,15 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.6.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
 
 [[package]]
 name = "generic-array"
-version = "0.14.5"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
 dependencies = [
  "typenum",
  "version_check",
@@ -246,9 +246,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.126"
+version = "0.2.132"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
 
 [[package]]
 name = "memoffset"
@@ -271,9 +271,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.12.0"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
+checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
 
 [[package]]
 name = "password-hash"
@@ -379,7 +379,6 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 name = "scrypt"
 version = "0.11.0-pre"
 dependencies = [
- "hmac",
  "password-hash",
  "pbkdf2",
  "salsa20",
diff --git a/bcrypt-pbkdf/src/lib.rs b/bcrypt-pbkdf/src/lib.rs
index cefcc497..f9f89122 100644
--- a/bcrypt-pbkdf/src/lib.rs
+++ b/bcrypt-pbkdf/src/lib.rs
@@ -152,8 +152,9 @@ pub fn bcrypt_pbkdf(
         &mut stack_buf[..stride * BHASH_OUTPUT_SIZE]
     };
 
-    // Run the regular PBKDF2 algorithm with bhash as the MAC.
-    pbkdf2::pbkdf2::<Bhash>(&Sha512::digest(passphrase), salt, rounds, generated);
+    // Run the regular PBKDF2 algorithm with bhash as the PRF.
+    pbkdf2::pbkdf2::<Bhash>(&Sha512::digest(passphrase), salt, rounds, generated)
+        .expect("Bhash can be initialized with any key length");
 
     // Apply the bcrypt_pbkdf non-linear transformation on the output.
     for (i, out_byte) in output.iter_mut().enumerate() {
diff --git a/pbkdf2/CHANGELOG.md b/pbkdf2/CHANGELOG.md
index b18dba7d..92cc8269 100644
--- a/pbkdf2/CHANGELOG.md
+++ b/pbkdf2/CHANGELOG.md
@@ -8,8 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## Unreleased
 ### Changed
 - `simple` feature is no longer enabled by default ([#336])
+- Add new wrapper functions: `pbkdf2_array`, `pbkdf2_hmac`, and
+`pbkdf2_hmac_array`. `pbkdf2` and `pbkdf2_array` now return
+`Result<(), InvalidLength>` instead of unwrapping it internally. ([#337])
 
 [#336]: https://github.com/RustCrypto/password-hashes/pull/336
+[#337]: https://github.com/RustCrypto/password-hashes/pull/337
 
 ## 0.11.0 (2022-03-28)
 ### Changed
diff --git a/pbkdf2/Cargo.toml b/pbkdf2/Cargo.toml
index 3227659f..767932e0 100644
--- a/pbkdf2/Cargo.toml
+++ b/pbkdf2/Cargo.toml
@@ -24,12 +24,13 @@ sha2 = { version = "0.10", default-features = false, optional = true }
 
 [dev-dependencies]
 hmac = "0.12"
-hex-literal = "0.3"
+hex-literal = "0.3.3"
 sha1 = { version = "0.10", package = "sha-1" }
 sha2 = "0.10"
 streebog = "0.10"
 
 [features]
+default = ["hmac"]
 parallel = ["rayon", "std"]
 simple = ["hmac", "password-hash", "sha2"]
 std = ["password-hash/std"]
diff --git a/pbkdf2/src/lib.rs b/pbkdf2/src/lib.rs
index 504fd048..1151fdfd 100644
--- a/pbkdf2/src/lib.rs
+++ b/pbkdf2/src/lib.rs
@@ -1,26 +1,48 @@
 //! This crate implements the PBKDF2 key derivation function as specified
 //! in [RFC 2898](https://tools.ietf.org/html/rfc2898).
 //!
-//! If you are only using the low-level [`pbkdf2`] function instead of the
-//! higher-level [`Pbkdf2`] struct to produce/verify hash strings,
-//! it's recommended to disable default features in your `Cargo.toml`:
+//! # Examples
 //!
-//! ```toml
-//! [dependencies]
-//! pbkdf2 = { version = "0.11", default-features = false }
+//! PBKDF2 is defined in terms of a keyed pseudo-random function (PRF). Most
+//! commonly HMAC is used as this PRF. In such cases you can use [`pbkdf2_hmac`]
+//! and [`pbkdf2_hmac_array`] functions. The former accepts a byte slice which
+//! gets filled with generated key, while the former returns an array with
+//! generated key of requested length.
+//!
+//! ```
+//! # #[cfg(feature = "hmac")] {
+//! use hex_literal::hex;
+//! use pbkdf2::{pbkdf2_hmac, pbkdf2_hmac_array};
+//! use sha2::Sha256;
+//!
+//! let password = b"password";
+//! let salt = b"salt";
+//! // number of iterations
+//! let n = 4096;
+//! // Expected value of generated key
+//! let expected = hex!("c5e478d59288c841aa530db6845c4c8d962893a0");
+//!
+//! let mut key1 = [0u8; 20];
+//! pbkdf2_hmac::<Sha256>(password, salt, n, &mut key1);
+//! assert_eq!(key1, expected);
+//!
+//! let key2 = pbkdf2_hmac_array::<Sha256, 20>(password, salt, n);
+//! assert_eq!(key2, expected);
+//! # }
 //! ```
 //!
-//! # Usage (simple with default params)
+//! If you want to use a different PRF, then you can use [`pbkdf2`][crate::pbkdf2]
+//! and [`pbkdf2_array`] functions.
 //!
-//! Note: this example requires the `rand_core` crate with the `std` feature
-//! enabled for `rand_core::OsRng` (embedded platforms can substitute their
-//! own RNG)
+//! This crates also provides the high-level password-hashing API through
+//! the [`Pbkdf2`] struct and traits defined in the
+//! [`password-hash`][password_hash] crate.
 //!
 //! Add the following to your crate's `Cargo.toml` to import it:
 //!
 //! ```toml
 //! [dependencies]
-//! pbkdf2 = "0.10"
+//! pbkdf2 = { version = "0.12", features = ["simple"] }
 //! rand_core = { version = "0.6", features = ["std"] }
 //! ```
 //!
@@ -78,7 +100,15 @@ pub use crate::simple::{Algorithm, Params, Pbkdf2};
 #[cfg(feature = "parallel")]
 use rayon::prelude::*;
 
-use digest::{generic_array::typenum::Unsigned, FixedOutput, KeyInit, Update};
+use digest::{generic_array::typenum::Unsigned, FixedOutput, InvalidLength, KeyInit, Update};
+
+#[cfg(feature = "hmac")]
+use digest::{
+    block_buffer::Eager,
+    core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
+    generic_array::typenum::{IsLess, Le, NonZero, U256},
+    HashMarker,
+};
 
 #[inline(always)]
 fn xor(res: &mut [u8], salt: &[u8]) {
@@ -89,7 +119,7 @@ fn xor(res: &mut [u8], salt: &[u8]) {
 #[inline(always)]
 fn pbkdf2_body<PRF>(i: u32, chunk: &mut [u8], prf: &PRF, salt: &[u8], rounds: u32)
 where
-    PRF: KeyInit + Update + FixedOutput + Clone,
+    PRF: Update + FixedOutput + Clone,
 {
     for v in chunk.iter_mut() {
         *v = 0;
@@ -114,34 +144,136 @@ where
     }
 }
 
-/// Generic implementation of PBKDF2 algorithm.
-#[cfg(not(feature = "parallel"))]
+/// Generic implementation of PBKDF2 algorithm which accepts an arbitrary keyed PRF.
+///
+/// ```
+/// use hex_literal::hex;
+/// use pbkdf2::pbkdf2;
+/// use hmac::Hmac;
+/// use sha2::Sha256;
+///
+/// let mut buf = [0u8; 20];
+/// pbkdf2::<Hmac<Sha256>>(b"password", b"salt", 4096, &mut buf)
+///     .expect("HMAC can be initialized with any key length");
+/// assert_eq!(buf, hex!("c5e478d59288c841aa530db6845c4c8d962893a0"));
+/// ```
 #[inline]
-pub fn pbkdf2<PRF>(password: &[u8], salt: &[u8], rounds: u32, res: &mut [u8])
+pub fn pbkdf2<PRF>(
+    password: &[u8],
+    salt: &[u8],
+    rounds: u32,
+    res: &mut [u8],
+) -> Result<(), InvalidLength>
 where
     PRF: KeyInit + Update + FixedOutput + Clone + Sync,
 {
     let n = PRF::OutputSize::to_usize();
     // note: HMAC can be initialized with keys of any size,
     // so this panic never happens with it
-    let prf = PRF::new_from_slice(password).expect("PRF initialization failure");
+    let prf = PRF::new_from_slice(password)?;
 
-    for (i, chunk) in res.chunks_mut(n).enumerate() {
-        pbkdf2_body(i as u32, chunk, &prf, salt, rounds);
+    #[cfg(not(feature = "parallel"))]
+    {
+        for (i, chunk) in res.chunks_mut(n).enumerate() {
+            pbkdf2_body(i as u32, chunk, &prf, salt, rounds);
+        }
+    }
+    #[cfg(feature = "parallel")]
+    {
+        res.par_chunks_mut(n).enumerate().for_each(|(i, chunk)| {
+            pbkdf2_body(i as u32, chunk, &prf, salt, rounds);
+        });
     }
+
+    Ok(())
 }
 
-/// Generic implementation of PBKDF2 algorithm.
-#[cfg(feature = "parallel")]
+/// A variant of the [`pbkdf2`][crate::pbkdf2] function which returns an array
+/// instead of filling an input slice.
+///
+/// ```
+/// use hex_literal::hex;
+/// use pbkdf2::pbkdf2_array;
+/// use hmac::Hmac;
+/// use sha2::Sha256;
+///
+/// let res = pbkdf2_array::<Hmac<Sha256>, 20>(b"password", b"salt", 4096)
+///     .expect("HMAC can be initialized with any key length");
+/// assert_eq!(res, hex!("c5e478d59288c841aa530db6845c4c8d962893a0"));
+/// ```
 #[inline]
-pub fn pbkdf2<PRF>(password: &[u8], salt: &[u8], rounds: u32, res: &mut [u8])
+pub fn pbkdf2_array<PRF, const N: usize>(
+    password: &[u8],
+    salt: &[u8],
+    rounds: u32,
+) -> Result<[u8; N], InvalidLength>
 where
     PRF: KeyInit + Update + FixedOutput + Clone + Sync,
 {
-    let n = PRF::OutputSize::to_usize();
-    let prf = PRF::new_from_slice(password).expect("PRF initialization failure");
+    let mut buf = [0u8; N];
+    pbkdf2::<PRF>(password, salt, rounds, &mut buf).map(|()| buf)
+}
+
+/// A variant of the [`pbkdf2`][crate::pbkdf2] function which uses HMAC for PRF.
+/// It's generic over (eager) hash functions.
+///
+/// ```
+/// use hex_literal::hex;
+/// use pbkdf2::pbkdf2_hmac;
+/// use sha2::Sha256;
+///
+/// let mut buf = [0u8; 20];
+/// pbkdf2_hmac::<Sha256>(b"password", b"salt", 4096, &mut buf);
+/// assert_eq!(buf, hex!("c5e478d59288c841aa530db6845c4c8d962893a0"));
+/// ```
+#[cfg(feature = "hmac")]
+#[cfg_attr(docsrs, doc(cfg(feature = "hmac")))]
+pub fn pbkdf2_hmac<D>(password: &[u8], salt: &[u8], rounds: u32, res: &mut [u8])
+where
+    D: CoreProxy,
+    D::Core: Sync
+        + HashMarker
+        + UpdateCore
+        + FixedOutputCore
+        + BufferKindUser<BufferKind = Eager>
+        + Default
+        + Clone,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    crate::pbkdf2::<hmac::Hmac<D>>(password, salt, rounds, res)
+        .expect("HMAC can be initialized with any key length");
+}
 
-    res.par_chunks_mut(n).enumerate().for_each(|(i, chunk)| {
-        pbkdf2_body(i as u32, chunk, &prf, salt, rounds);
-    });
+/// A variant of the [`pbkdf2_hmac`] function which returns an array
+/// instead of filling an input slice.
+///
+/// ```
+/// use hex_literal::hex;
+/// use pbkdf2::pbkdf2_hmac_array;
+/// use sha2::Sha256;
+///
+/// assert_eq!(
+///     pbkdf2_hmac_array::<Sha256, 20>(b"password", b"salt", 4096),
+///     hex!("c5e478d59288c841aa530db6845c4c8d962893a0"),
+/// );
+/// ```
+#[cfg(feature = "hmac")]
+#[cfg_attr(docsrs, doc(cfg(feature = "hmac")))]
+pub fn pbkdf2_hmac_array<D, const N: usize>(password: &[u8], salt: &[u8], rounds: u32) -> [u8; N]
+where
+    D: CoreProxy,
+    D::Core: Sync
+        + HashMarker
+        + UpdateCore
+        + FixedOutputCore
+        + BufferKindUser<BufferKind = Eager>
+        + Default
+        + Clone,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    let mut buf = [0u8; N];
+    pbkdf2_hmac::<D>(password, salt, rounds, &mut buf);
+    buf
 }
diff --git a/pbkdf2/src/simple.rs b/pbkdf2/src/simple.rs
index 6f6b9edd..624bba56 100644
--- a/pbkdf2/src/simple.rs
+++ b/pbkdf2/src/simple.rs
@@ -1,8 +1,7 @@
 //! Implementation of the `password-hash` crate API.
 
-use crate::pbkdf2;
+use crate::pbkdf2_hmac;
 use core::{cmp::Ordering, fmt, str::FromStr};
-use hmac::Hmac;
 use password_hash::{
     errors::InvalidValue, Decimal, Error, Ident, Output, ParamsString, PasswordHash,
     PasswordHasher, Result, Salt,
@@ -52,9 +51,9 @@ impl PasswordHasher for Pbkdf2 {
         let output = Output::init_with(params.output_length, |out| {
             let f = match algorithm {
                 #[cfg(feature = "sha1")]
-                Algorithm::Pbkdf2Sha1 => pbkdf2::<Hmac<Sha1>>,
-                Algorithm::Pbkdf2Sha256 => pbkdf2::<Hmac<Sha256>>,
-                Algorithm::Pbkdf2Sha512 => pbkdf2::<Hmac<Sha512>>,
+                Algorithm::Pbkdf2Sha1 => pbkdf2_hmac::<Sha1>,
+                Algorithm::Pbkdf2Sha256 => pbkdf2_hmac::<Sha256>,
+                Algorithm::Pbkdf2Sha512 => pbkdf2_hmac::<Sha512>,
             };
 
             f(password, salt_bytes, params.rounds, out);
diff --git a/scrypt/Cargo.toml b/scrypt/Cargo.toml
index 51f87ca5..a59f2c29 100644
--- a/scrypt/Cargo.toml
+++ b/scrypt/Cargo.toml
@@ -13,8 +13,7 @@ edition = "2021"
 rust-version = "1.57"
 
 [dependencies]
-hmac = "0.12.1"
-pbkdf2 = { version = "=0.12.0-pre", default-features = false, path = "../pbkdf2" }
+pbkdf2 = { version = "=0.12.0-pre", path = "../pbkdf2" }
 salsa20 = { version = "0.10.2", default-features = false }
 sha2 = { version = "0.10", default-features = false }
 
diff --git a/scrypt/src/lib.rs b/scrypt/src/lib.rs
index 9d7ccc10..7c452473 100644
--- a/scrypt/src/lib.rs
+++ b/scrypt/src/lib.rs
@@ -54,8 +54,7 @@ extern crate alloc;
 #[cfg(feature = "std")]
 extern crate std;
 
-use hmac::Hmac;
-use pbkdf2::pbkdf2;
+use pbkdf2::pbkdf2_hmac;
 use sha2::Sha256;
 
 /// Errors for `scrypt` operations.
@@ -107,7 +106,7 @@ pub fn scrypt(
     let nr128 = n * r128;
 
     let mut b = vec![0u8; pr128];
-    pbkdf2::<Hmac<Sha256>>(password, salt, 1, &mut b);
+    pbkdf2_hmac::<Sha256>(password, salt, 1, &mut b);
 
     let mut v = vec![0u8; nr128];
     let mut t = vec![0u8; r128];
@@ -116,6 +115,6 @@ pub fn scrypt(
         romix::scrypt_ro_mix(chunk, &mut v, &mut t, n);
     }
 
-    pbkdf2::<Hmac<Sha256>>(password, &b, 1, output);
+    pbkdf2_hmac::<Sha256>(password, &b, 1, output);
     Ok(())
 }


Test patch:
diff --git a/pbkdf2/tests/data/1.c.bin b/pbkdf2/tests/data/1.c.bin
deleted file mode 100644
index 56a6051c..00000000
--- a/pbkdf2/tests/data/1.c.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-1
\ No newline at end of file
diff --git a/pbkdf2/tests/data/1.output.bin b/pbkdf2/tests/data/1.output.bin
deleted file mode 100644
index e97fff18..00000000
--- a/pbkdf2/tests/data/1.output.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-`��q�$�`/�7�
\ No newline at end of file
diff --git a/pbkdf2/tests/data/1.password.bin b/pbkdf2/tests/data/1.password.bin
deleted file mode 100644
index 7aa311ad..00000000
--- a/pbkdf2/tests/data/1.password.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-password
\ No newline at end of file
diff --git a/pbkdf2/tests/data/1.salt.bin b/pbkdf2/tests/data/1.salt.bin
deleted file mode 100644
index acdf61b5..00000000
--- a/pbkdf2/tests/data/1.salt.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-salt
\ No newline at end of file
diff --git a/pbkdf2/tests/data/2.c.bin b/pbkdf2/tests/data/2.c.bin
deleted file mode 100644
index d8263ee9..00000000
--- a/pbkdf2/tests/data/2.c.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-2
\ No newline at end of file
diff --git a/pbkdf2/tests/data/2.output.bin b/pbkdf2/tests/data/2.output.bin
deleted file mode 100644
index 794dd831..00000000
--- a/pbkdf2/tests/data/2.output.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-�lM�-o���*�A��މW
\ No newline at end of file
diff --git a/pbkdf2/tests/data/2.password.bin b/pbkdf2/tests/data/2.password.bin
deleted file mode 100644
index 7aa311ad..00000000
--- a/pbkdf2/tests/data/2.password.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-password
\ No newline at end of file
diff --git a/pbkdf2/tests/data/2.salt.bin b/pbkdf2/tests/data/2.salt.bin
deleted file mode 100644
index acdf61b5..00000000
--- a/pbkdf2/tests/data/2.salt.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-salt
\ No newline at end of file
diff --git a/pbkdf2/tests/data/3.c.bin b/pbkdf2/tests/data/3.c.bin
deleted file mode 100644
index 1b18a99c..00000000
--- a/pbkdf2/tests/data/3.c.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-4096
\ No newline at end of file
diff --git a/pbkdf2/tests/data/3.output.bin b/pbkdf2/tests/data/3.output.bin
deleted file mode 100644
index 0818153b..00000000
Binary files a/pbkdf2/tests/data/3.output.bin and /dev/null differ
diff --git a/pbkdf2/tests/data/3.password.bin b/pbkdf2/tests/data/3.password.bin
deleted file mode 100644
index 7aa311ad..00000000
--- a/pbkdf2/tests/data/3.password.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-password
\ No newline at end of file
diff --git a/pbkdf2/tests/data/3.salt.bin b/pbkdf2/tests/data/3.salt.bin
deleted file mode 100644
index acdf61b5..00000000
--- a/pbkdf2/tests/data/3.salt.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-salt
\ No newline at end of file
diff --git a/pbkdf2/tests/data/4.c.bin b/pbkdf2/tests/data/4.c.bin
deleted file mode 100644
index 1b18a99c..00000000
--- a/pbkdf2/tests/data/4.c.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-4096
\ No newline at end of file
diff --git a/pbkdf2/tests/data/4.output.bin b/pbkdf2/tests/data/4.output.bin
deleted file mode 100644
index 2620fe5a..00000000
--- a/pbkdf2/tests/data/4.output.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-=.�O������6b��J�)�L��p8
\ No newline at end of file
diff --git a/pbkdf2/tests/data/4.password.bin b/pbkdf2/tests/data/4.password.bin
deleted file mode 100644
index e27630e8..00000000
--- a/pbkdf2/tests/data/4.password.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-passwordPASSWORDpassword
\ No newline at end of file
diff --git a/pbkdf2/tests/data/4.salt.bin b/pbkdf2/tests/data/4.salt.bin
deleted file mode 100644
index c9284ea5..00000000
--- a/pbkdf2/tests/data/4.salt.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-saltSALTsaltSALTsaltSALTsaltSALTsalt
\ No newline at end of file
diff --git a/pbkdf2/tests/data/5.c.bin b/pbkdf2/tests/data/5.c.bin
deleted file mode 100644
index 1b18a99c..00000000
--- a/pbkdf2/tests/data/5.c.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-4096
\ No newline at end of file
diff --git a/pbkdf2/tests/data/5.output.bin b/pbkdf2/tests/data/5.output.bin
deleted file mode 100644
index 47b4243d..00000000
--- a/pbkdf2/tests/data/5.output.bin
+++ /dev/null
@@ -1,1 +0,0 @@
-V�j�UH	��7��4%��
\ No newline at end of file
diff --git a/pbkdf2/tests/data/5.password.bin b/pbkdf2/tests/data/5.password.bin
deleted file mode 100644
index 71c30a87..00000000
Binary files a/pbkdf2/tests/data/5.password.bin and /dev/null differ
diff --git a/pbkdf2/tests/data/5.salt.bin b/pbkdf2/tests/data/5.salt.bin
deleted file mode 100644
index 614c05dd..00000000
Binary files a/pbkdf2/tests/data/5.salt.bin and /dev/null differ
diff --git a/pbkdf2/tests/gost.rs b/pbkdf2/tests/gost.rs
deleted file mode 100644
index a007cf83..00000000
--- a/pbkdf2/tests/gost.rs
+++ /dev/null
@@ -1,89 +0,0 @@
-//! Test vectors from R 50.1.111-2016:
-//! https://tc26.ru/standard/rs/Р 50.1.111-2016.pdf
-use hex_literal::hex;
-
-#[test]
-#[rustfmt::skip]
-fn gost_test() {
-    let pbkdf2 = pbkdf2::pbkdf2::<hmac::Hmac<streebog::Streebog512>>;
-    let mut res64 = [0u8; 64];
-    let mut res100 = [0u8; 100];
-
-    pbkdf2(b"password", b"salt", 1, &mut res64);
-    assert_eq!(
-        res64,
-        hex!("
-            64770af7f748c3b1c9ac831dbcfd85c2
-            6111b30a8a657ddc3056b80ca73e040d
-            2854fd36811f6d825cc4ab66ec0a68a4
-            90a9e5cf5156b3a2b7eecddbf9a16b47
-        "),
-    );
-
-    pbkdf2(b"password", b"salt", 2, &mut res64);
-    assert_eq!(
-        res64,
-        hex!("
-            5a585bafdfbb6e8830d6d68aa3b43ac0
-            0d2e4aebce01c9b31c2caed56f0236d4
-            d34b2b8fbd2c4e89d54d46f50e47d45b
-            bac301571743119e8d3c42ba66d348de
-        "),
-    );
-
-    pbkdf2(b"password", b"salt", 4096, &mut res64);
-    assert_eq!(
-        res64,
-        hex!("
-            e52deb9a2d2aaff4e2ac9d47a41f34c2
-            0376591c67807f0477e32549dc341bc7
-            867c09841b6d58e29d0347c996301d55
-            df0d34e47cf68f4e3c2cdaf1d9ab86c3
-        "),
-    );
-
-    // this test passes, but takes more than 1 minute to
-    // execute in release mode
-    /*
-    pbkdf2(b"password", b"salt", 16777216, &mut res64);
-    assert_eq!(
-        res64,
-        hex!("
-            49e4843bba76e300afe24c4d23dc7392
-            def12f2c0e244172367cd70a8982ac36
-            1adb601c7e2a314e8cb7b1e9df840e36
-            ab5615be5d742b6cf203fb55fdc48071
-        "),
-    );
-    */
-
-    pbkdf2(
-        b"passwordPASSWORDpassword",
-        b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
-        4096,
-        &mut res100,
-    );
-    assert_eq!(
-        res100,
-        hex!("
-            b2d8f1245fc4d29274802057e4b54e0a
-            0753aa22fc53760b301cf008679e58fe
-            4bee9addcae99ba2b0b20f431a9c5e50
-            f395c89387d0945aedeca6eb4015dfc2
-            bd2421ee9bb71183ba882ceebfef259f
-            33f9e27dc6178cb89dc37428cf9cc52a
-            2baa2d3a
-        "),
-    );
-
-    pbkdf2(b"pass\0word", b"sa\0lt", 4096, &mut res64);
-    assert_eq!(
-        res64,
-        hex!("
-            50df062885b69801a3c10248eb0a27ab
-            6e522ffeb20c991c660f001475d73a4e
-            167f782c18e97e92976d9c1d970831ea
-            78ccb879f67068cdac1910740844e830
-        "),
-    );
-}
diff --git a/pbkdf2/tests/lib.rs b/pbkdf2/tests/lib.rs
deleted file mode 100644
index 2da7e820..00000000
--- a/pbkdf2/tests/lib.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-use hmac::Hmac;
-use sha1::Sha1;
-
-#[derive(Debug)]
-pub struct Test {
-    pub name: &'static str,
-    pub password: &'static [u8],
-    pub salt: &'static [u8],
-    pub c: &'static str,
-    pub output: &'static [u8],
-}
-
-#[macro_export]
-macro_rules! new_tests {
-    ( $( $name:expr ),*  ) => {
-        [$(
-            Test {
-                name: $name,
-                password: include_bytes!(concat!("data/", $name, ".password.bin")),
-                salt: include_bytes!(concat!("data/", $name, ".salt.bin")),
-                c: include_str!(concat!("data/", $name, ".c.bin")),
-                output: include_bytes!(concat!("data/", $name, ".output.bin")),
-            },
-        )*]
-    };
-}
-
-#[test]
-fn rfc6070() {
-    let tests = new_tests!("1", "2", "3", "4", "5");
-    let mut buf = [0u8; 25];
-    for test in &tests {
-        let c = test.c.parse().unwrap();
-        println!("c: {:?}", test);
-        let n = test.output.len();
-        pbkdf2::pbkdf2::<Hmac<Sha1>>(test.password, test.salt, c, &mut buf[..n]);
-        assert_eq!(&buf[..n], test.output);
-    }
-}
diff --git a/pbkdf2/tests/mod.rs b/pbkdf2/tests/mod.rs
new file mode 100644
index 00000000..4edb0717
--- /dev/null
+++ b/pbkdf2/tests/mod.rs
@@ -0,0 +1,117 @@
+#![cfg(feature = "hmac")]
+use hex_literal::hex;
+use pbkdf2::pbkdf2_hmac_array as f;
+use sha1::Sha1;
+use streebog::Streebog512;
+
+/// Tests from RFC 6070:
+/// https://www.rfc-editor.org/rfc/rfc6070
+#[test]
+fn rfc6070() {
+    assert_eq!(
+        f::<Sha1, 20>(b"password", b"salt", 1),
+        hex!("0c60c80f961f0e71f3a9b524af6012062fe037a6"),
+    );
+    assert_eq!(
+        f::<Sha1, 20>(b"password", b"salt", 2),
+        hex!("ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"),
+    );
+    assert_eq!(
+        f::<Sha1, 20>(b"password", b"salt", 4096),
+        hex!("4b007901b765489abead49d926f721d065a429c1"),
+    );
+    // this test passes, but takes a long time to execute
+    /*
+    assert_eq!(
+        f::<Sha1, 20>(b"password", b"salt", 16777216),
+        hex!("eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"),
+    );
+    */
+    assert_eq!(
+        f::<Sha1, 25>(
+            b"passwordPASSWORDpassword",
+            b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
+            4096
+        ),
+        hex!("3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"),
+    );
+    assert_eq!(
+        f::<Sha1, 16>(b"pass\0word", b"sa\0lt", 4096),
+        hex!("56fa6aa75548099dcc37d7f03425e0c3"),
+    );
+}
+
+/// Test vectors from R 50.1.111-2016:
+/// https://tc26.ru/standard/rs/Р 50.1.111-2016.pdf
+#[test]
+fn gost() {
+    assert_eq!(
+        f::<Streebog512, 64>(b"password", b"salt", 1),
+        hex!(
+            "64770af7f748c3b1c9ac831dbcfd85c2"
+            "6111b30a8a657ddc3056b80ca73e040d"
+            "2854fd36811f6d825cc4ab66ec0a68a4"
+            "90a9e5cf5156b3a2b7eecddbf9a16b47"
+        ),
+    );
+
+    assert_eq!(
+        f::<Streebog512, 64>(b"password", b"salt", 2),
+        hex!(
+            "5a585bafdfbb6e8830d6d68aa3b43ac0"
+            "0d2e4aebce01c9b31c2caed56f0236d4"
+            "d34b2b8fbd2c4e89d54d46f50e47d45b"
+            "bac301571743119e8d3c42ba66d348de"
+        ),
+    );
+
+    assert_eq!(
+        f::<Streebog512, 64>(b"password", b"salt", 4096),
+        hex!(
+            "e52deb9a2d2aaff4e2ac9d47a41f34c2"
+            "0376591c67807f0477e32549dc341bc7"
+            "867c09841b6d58e29d0347c996301d55"
+            "df0d34e47cf68f4e3c2cdaf1d9ab86c3"
+        ),
+    );
+
+    // this test passes, but takes a long time to execute
+    /*
+    assert_eq!(
+        f::<Streebog512, 64>(b"password", b"salt", 16777216),
+        hex!(
+            "49e4843bba76e300afe24c4d23dc7392"
+            "def12f2c0e244172367cd70a8982ac36"
+            "1adb601c7e2a314e8cb7b1e9df840e36"
+            "ab5615be5d742b6cf203fb55fdc48071"
+        ),
+    );
+    */
+
+    assert_eq!(
+        f::<Streebog512, 100>(
+            b"passwordPASSWORDpassword",
+            b"saltSALTsaltSALTsaltSALTsaltSALTsalt",
+            4096,
+        ),
+        hex!(
+            "b2d8f1245fc4d29274802057e4b54e0a"
+            "0753aa22fc53760b301cf008679e58fe"
+            "4bee9addcae99ba2b0b20f431a9c5e50"
+            "f395c89387d0945aedeca6eb4015dfc2"
+            "bd2421ee9bb71183ba882ceebfef259f"
+            "33f9e27dc6178cb89dc37428cf9cc52a"
+            "2baa2d3a"
+        ),
+    );
+
+    assert_eq!(
+        f::<Streebog512, 64>(b"pass\0word", b"sa\0lt", 4096),
+        hex!(
+            "50df062885b69801a3c10248eb0a27ab"
+            "6e522ffeb20c991c660f001475d73a4e"
+            "167f782c18e97e92976d9c1d970831ea"
+            "78ccb879f67068cdac1910740844e830"
+        ),
+    );
+}