Skip to content

pbkdf2::pbkdf2 has an unconditional PRF: Sync bound; correctly \!Sync MAC types cannot use it even single-threaded #2361

@MarkAtwood

Description

@MarkAtwood

Repo: RustCrypto/traits (affects pbkdf2 crate)
Labels: api-design, pbkdf2

Background

pbkdf2::pbkdf2 has this signature (simplified):

pub fn pbkdf2<PRF: Mac + Sync>(prf: PRF, salt: &[u8], rounds: u32, res: &mut [u8])
    -> Result<(), InvalidLength>

The Sync bound exists to support the parallel feature, which distributes
PBKDF2 rounds across a thread pool. When multiple threads run rounds
concurrently, they need to share the PRF instance — hence Sync.

The problem

The Sync bound is present unconditionally, even when the parallel feature
is disabled and the function runs entirely on a single thread. A type that is
correctly !Sync — because it holds interior mutable state that is unsafe to
share — cannot call pbkdf2, even in a single-threaded program with
parallel = false.

This is a logic error in the API: Sync is a sharing guarantee, and no
sharing occurs when parallel is off. Requiring it in that case excludes
legitimate types for no benefit.

In our implementation

Discovered while implementing wolfcrypt, a RustCrypto backend wrapping wolfCrypt — a FIPS 140-3 validated C cryptographic library with hardware dispatch via WOLF_CRYPTO_CB.

wolfCrypt's EVP-based digest types (WolfSha256, etc.) are !Sync. The
EVP_MD_CTX C struct contains interior mutable state — counter values, partial
block buffers — that is updated on every call. It is not safe to read from two
threads simultaneously. Marking these types Sync would be unsound.

Because pbkdf2::pbkdf2 requires PRF: Sync unconditionally, this does not
compile even in a single-threaded binary with parallel = false:

pbkdf2::pbkdf2::<hmac::SimpleHmac<WolfSha256>>(password, salt, rounds, &mut out)?;
// error[E0277]: `WolfSha256` cannot be shared between threads safely
//   = help: the trait `Sync` is not implemented for `WolfSha256`
//   note: required by a bound in `pbkdf2`

Proposed change

Gate the Sync bound on the parallel feature flag:

#[cfg(feature = "parallel")]
pub fn pbkdf2<PRF: Mac + Clone + Sync>(
    prf: PRF, salt: &[u8], rounds: u32, res: &mut [u8],
) -> Result<(), InvalidLength> { ... }

#[cfg(not(feature = "parallel"))]
pub fn pbkdf2<PRF: Mac + Clone>(
    prf: PRF, salt: &[u8], rounds: u32, res: &mut [u8],
) -> Result<(), InvalidLength> { ... }

This is fully backward-compatible: all existing callers use Sync types and
are unaffected. Callers with !Sync types gain access to the function when
parallel is not enabled. This is the smallest and most self-contained change
in this set of issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions