Skip to content

Conversation

@tarcieri
Copy link
Member

Traits which provide an API for interacting with Key Derivation Functions.

We have several of these located at https://github.com/rustcrypto/kdfs and password-based KDFs at https://github.com/RustCrypto/password-hashes but the APIs for using these are typically just free functions, whereas traits could provide a common API.

@tarcieri tarcieri requested a review from newpavlov May 31, 2025 14:41
@tarcieri tarcieri marked this pull request as draft May 31, 2025 14:44
@tarcieri tarcieri force-pushed the kdf branch 2 times, most recently from 1dd9bad to 7d87e21 Compare May 31, 2025 14:46
@tarcieri
Copy link
Member Author

I opened this up for discussion primarily, and also reserved the kdf crate name with the minimal proposed API.

I think we probably need a lot more traits than this, particularly ones for representing the "expand" and "extract" steps in proper KDFs like HKDF.

@tarcieri
Copy link
Member Author

tarcieri commented May 31, 2025

Another important question: output size limits and fallibility. Should we have a try_derive/try_expand and an Error type?

How about a trait with an associated constant or typenum type that defines the maximum output size?

@daxpedda
Copy link
Contributor

daxpedda commented Jun 2, 2025

I am assuming that "key stretching functions", e.g. Argon2, Scrypt and such, don't fall under this trait?

@tarcieri
Copy link
Member Author

tarcieri commented Jun 2, 2025

@daxpedda I think it would be good to eventually support password-based KDFs. I think they wind up having a very similar API to other KDFs, to the point it might just be a marker trait and some usage guidelines.

That said, it would probably be good to focus on traits for what's in https://github.com/rustcrypto/kdfs for now

@daxpedda
Copy link
Contributor

daxpedda commented Jun 2, 2025

FWIW: I don't have much feedback for this kind of interface, but for OPAQUE we would like to have a trait for key stretching functions.

@tarcieri
Copy link
Member Author

I thought I'd collect the individual functions we're trying to abstract over:

KDFs

ansi-x963-kdf

pub fn derive_key_into<D>(secret: &[u8], shared_info: &[u8], key: &mut [u8]) -> Result<(), Error>

bake-kdf

pub fn bake_kdf(x: &[u8], s: &[u8], c: u128) -> [u8; 32]

hkdf

    pub fn new(salt: Option<&[u8]>, ikm: &[u8]) -> Self
    pub fn expand(&self, info: &[u8], okm: &mut [u8]) -> Result<(), InvalidLength>

kbkdf

This one's a bit unusual so I'm just going to show the usage:

Counter::<HmacSha256, HmacSha256>::default()
    .derive(Params::builder(b"secret").with_label(b"label").build())
    .unwrap()

one-step-kdf

pub fn derive_key_into<D>(secret: &[u8], other_info: &[u8], key: &mut [u8]) -> Result<(), Error>

PBKDFs

argon2

pub fn hash_password_into(&self, pwd: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()> 

balloon-hash

 pub fn hash_into(&self, pwd: &[u8], salt: &[u8], output: &mut [u8]) -> Result<()>

bcrypt-pbkdf

pub fn bcrypt_pbkdf(passphrase: impl AsRef<[u8]>, salt: &[u8], rounds: u32 output: &mut [u8]) -> Result<(), Error>

pbkdf2

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

scrypt

pub fn scrypt(password: &[u8], salt: &[u8], params: &Params, output: &mut [u8]) -> Result<(), errors::InvalidOutputLen>

yescrypt

pub fn yescrypt(passwd: &[u8], salt: &[u8], params: &Params, out: &mut [u8]) -> Result<()>

@tarcieri
Copy link
Member Author

It seems like a one-shot API which accepts IKM/password and salt, and can access &self for e.g. parameters could cover quite a few cases:

pub trait Kdf {
    fn derive_key(&self, secret: &[u8], salt: &[u8], out: &mut [u8]) -> Result<()>;
}

As for PBKDFs, perhaps a marker trait for where it's acceptable to use a password as secret:

pub trait Pbkdf: Kdf {}

Notably having a trait that wraps the low-level KDF APIs for PBKDFs (as opposed to the high-level password hash string APIs in password-hash) would be useful for PAKEs, where we currently lack good traits for making it easy to plug various PBKDFs into PAKE algorithms.

@newpavlov
Copy link
Member

newpavlov commented Jan 14, 2026

Sounds good to me!

Is it correct to interpret other_info as "salt" in the one-step-kdf case? Am I correct that in the hkdf case the salt argument will be used as "salt" in the trait impl and not info?

What about the Error type? Do you plan to define it in kdf or make it an associated type?

@tarcieri
Copy link
Member Author

tarcieri commented Jan 14, 2026

Is it correct to interpret other_info as "salt" in the one-step-kdf case? Am I correct that in the hkdf case the salt argument will be used as "salt" in the trait impl and not info?

These are all good questions.

The "other information" parameter of One-Step KDF seems to have fairly open-ended usage, per my interpretation of NIST SP 800-56C. It's described as:

other information (as determined by the particular implementation of the key establishment scheme and/or key-derivation function)

I think it would be fine to pass salt through to both One-Step KDF and HKDF, but perhaps with the caveat that salt may be interpreted slightly different than you might with a PBKDF: where you would typically use a unique, random salt with a PBKDF, for general KDF use a salt may (or may not) be a static application-specific identifier which is reused across multiple invocations.

Re: errors, I'll need to look at the error types in the various KDF crates to see if they're actually useful, and if not we can just have a common error type, which is probably where I'll start anyway as that's simpler.

@newpavlov
Copy link
Member

for general KDF use a salt may (or may not) be a static application-specific identifier which is reused across multiple invocations

Maybe it's better to use something like shared_info instead of salt then? We could note in the trait docs that some algorithms (particularly PBKDFs) call this parameter as "salt".

Traits which provide an API for interacting with Key Derivation
Functions.

We have several of these located at https://github.com/rustcrypto/kdfs
and password-based KDFs at https://github.com/RustCrypto/password-hashes
but the APIs for using these are typically just free functions, whereas
traits could provide a common API.
@tarcieri tarcieri changed the title [WIP] kdf crate kdf crate Jan 14, 2026
@tarcieri tarcieri marked this pull request as ready for review January 14, 2026 16:44
@tarcieri
Copy link
Member Author

tarcieri commented Jan 14, 2026

Just pushed up my suggested new changes and removed draft.

Maybe it's better to use something like shared_info instead of salt then?

HKDF still calls it a salt, and I think "salt" is still an exceedingly common term for it. It's also the parameter that gets passed as a salt in PBKDFs.

I think it would probably be more confusing to call it something else other than salt.

@newpavlov
Copy link
Member

newpavlov commented Jan 14, 2026

But I don't think it's correct to call customization strings (info, shared_info, other_info, etc.) as "salt" either, especially when we use static strings for them.

HKDF still calls it a salt

HKDF explicitly distinguishes between info and salt. I think passing the trait method argument to info would be more natural, i.e. the salt in this case would be an initialization parameter for Hkdf struct which in turn implements the trait.

@tarcieri
Copy link
Member Author

But I don't think it's correct to call customization strings (info, shared_info, other_info, etc.) as "salt" either, especially when we use static strings for them.

Well, that's why I want to pass the salt parameter to salt in HKDF. I think most HKDF usages use a salt, whereas they may or may not use info and if they do, this trait isn't going to be expressive enough to cover that case.

For One-Shot KDF I think it's fine to pass salt as shared_info as there's no additional salt parameter for it to be ambiguous with like in HKDF, and it seems to serve the same purpose as what HKDF calls a salt.

HKDF explicitly distinguishes between info and salt. I think passing the trait method argument to info would be a slightly more natural

Can you show me an HKDF use case which uses info but does not use salt? Because that's the only such use case where such a trait impl would be useful.

I think it's much more common to configure salt but leave info unspecified.

@newpavlov
Copy link
Member

For example, in QUIC salt is fixed (so it could've been omitted) and then initialized value is used with different infos to generate different keys. In TLS 1.3 key schedule IKM is zeros, while salt is derived from handshake secret, the extracted state is again used several times to generate keys with different labels.

On Wiki "salt" is defined as:

a salt is random data fed as an additional input to a one-way function that hashes data, a password or passphrase

@newpavlov
Copy link
Member

newpavlov commented Jan 14, 2026

I guess it does not really matter. The trait method accepts key, so the extract-expand feature of HKDF maps poorly to the trait either way.

If we are to keep salt as the parameter name, I think it's worth to explicitly mention in the docs that it can be a fixed string and that some algorithms name this parameter as "info".

@tarcieri tarcieri merged commit 0d18c7d into master Jan 14, 2026
79 checks passed
@tarcieri tarcieri deleted the kdf branch January 14, 2026 18:35
@tarcieri
Copy link
Member Author

Thanks for the approval. I think it's good to have something landed to iterate on.

I'll note that while in some applications like password hash strings (including the phc crate) or in HKDF there's generally a recommendation that the salt is a high entropy random value, in the sorts of low-level APIs these traits are trying to abstract over there really aren't any requirements and the salt is just an arbitrary-length user-supplied bytestring which may even be completely empty (HKDF has a somewhat first-class concept of that).

At the end of the day, KDFs have a secret and non-secret input, and it's just a bikeshed debate about what to call the non-secret input (we could consider simply calling the other input non_secret rather than salt to cover all bases).

Your comment did get me thinking though that your suggestion of using the non-secret HKDF input as info rather than salt is actually probably better, because then the trait can (almost) be impl'd on (Generic)HkdfExtract, getting the ambient salt via self, and then passing the user-supplied value to the expand operation. The one problem is HkdfExtract::finalize consumes self, which I guess is another consideration in the design of this trait.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants