Skip to content

Add pkcs5 argument to OpenSSL::KDF.pbkdf2_hmac - #1120

Open
xnox wants to merge 1 commit into
ruby:masterfrom
xnox:pbkdf2-pkcs5
Open

xnox wants to merge 1 commit into
ruby:masterfrom
xnox:pbkdf2-pkcs5

Conversation

@xnox

@xnox xnox commented Oct 4, 2026

Copy link
Copy Markdown

With OpenSSL 3.0 and later, OpenSSL::KDF.pbkdf2_hmac now derives the key with EVP_KDF_fetch() and EVP_KDF_derive(), passing all arguments as OSSL_PARAMs (as ossl_pkcs5_pbkdf2_hmac_ex() does in OpenSSL), including OSSL_KDF_PARAM_PKCS5.

The new pkcs5: keyword argument controls OpenSSL's SP 800-132 lower bound checks:

  • pkcs5: 0 enforces them: iterations >= 1000, salt >= 128 bits, derived key >= 112 bits, non-empty password (>= 8 bytes with the FIPS provider).
  • pkcs5: 1 (the default) bypasses them.

The default matches the behavior of PKCS5_PBKDF2_HMAC() prior to the OpenSSL 4.0 release, and allows existing data to be decrypted, such as Active Record encrypted attributes, which use a salt that is too short by default (rails/rails#58937).

With the FIPS provider, bypassing the checks might still block the operation, or raise the non-approved usage indicator. Other libraries (LibreSSL, AWS-LC, OpenSSL < 3.0) keep using PKCS5_PBKDF2_HMAC() and ignore the argument.

The docs also note that OpenSSL's lower bounds are minimums, and point to the OWASP Password Storage Cheat Sheet for security-sensitive parameters.

Tested against OpenSSL 3.5.5 (via a ruby/ruby build): test/openssl/test_kdf.rb passes, including a new test that pkcs5: 0 rejects the RFC 6070 c=1 parameters and pkcs5: 1 derives the expected value. Not tested with FIPS, LibreSSL or AWS-LC.

🤖 Generated with Claude Code

With OpenSSL 3.0 and later, derive the key with EVP_KDF_fetch() and
EVP_KDF_derive(), passing all arguments as OSSL_PARAMs, including
OSSL_KDF_PARAM_PKCS5. The new pkcs5: keyword argument controls the
SP 800-132 lower bound checks: 0 enforces them, and 1 (the default)
bypasses them.

The default matches the behavior of PKCS5_PBKDF2_HMAC() prior to the
OpenSSL 4.0 release, and allows existing data to be decrypted, such as
Active Record encrypted attributes, which use a salt that is too short
by default (rails/rails#58937).

Other libraries continue to use PKCS5_PBKDF2_HMAC() and ignore the
argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rhenium

rhenium commented Oct 5, 2026

Copy link
Copy Markdown
Member

Generally, ruby/openssl should respect OpenSSL's defaults, which in this case seems to mean leaving the pkcs5 OSSL_PARAM unset.

We should expose the option if needed, but I'm leaning towards a generic interface to EVP_KDF_derive() rather than switching implementations of .pbkdf2_hmac by the OpenSSL version or adding an awkward pkcs5: kwarg.

I'll try to make time to finish #906.

@xnox

xnox commented Oct 5, 2026

Copy link
Copy Markdown
Author

Generally, ruby/openssl should respect OpenSSL's defaults, which in this case seems to mean leaving the pkcs5 OSSL_PARAM unset.

Ack! I didn't know if ruby wants to be explicit or not, will adjust - because openssl default is quite finiky:

  • when unset, default provider defaults to non-enforcing and fips provider defaults to enforcing
  • meaning with default provider there is opt-in; but fips provider there is opt-out

Will adjust to match that verbatim.

We should expose the option if needed, but I'm leaning towards a generic interface to EVP_KDF_derive() rather than switching implementations of .pbkdf2_hmac by the OpenSSL version or adding an awkward pkcs5: kwarg.

the pkcs5 arg is specific to that PBKDF2 derive; there are other more generic params such as key-check / size-check etc to enforce input key lengths; and output key lengths - those are more generic params but again provider dependent "ignored by default provider, have some action with fips provider".

At least the pkcs5 param does actually do something with both default and fips providers.

I'll try to make time to finish #906.

Yeah generic EVP_KDF would be nice; but we may still need to expose and allow setting pkcs5 param - because as it stands all existing ActiveRecord encrypted cookies and secrets fail - and "rails new" fails on a fips system with ruby and openssl4. See rails/rails#58937

This branch has not been deployed

No deployments
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.

2 participants