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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions