Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto/pbkdf2: add package #69488

Open
qmuntal opened this issue Sep 16, 2024 · 22 comments
Open

crypto/pbkdf2: add package #69488

qmuntal opened this issue Sep 16, 2024 · 22 comments
Labels
Proposal Proposal-Accepted Proposal-Crypto Proposal related to crypto packages or other security issues
Milestone

Comments

@qmuntal
Copy link
Contributor

qmuntal commented Sep 16, 2024

Important

Nov 20, 2024: The latest version of the proposal is here.

Proposal Details

I propose to move the golang.org/x/crypto/pbkdf2 package into the standard library with the name crypto/pbkdf2. This proposal is a #65269 spin off, as requested by the following comment from @rsc:

I agree with this in principle but there should be review of individual packages rather than a blank check to usher significant amounts of never-reviewed API into the standard library.
#65269 (comment)

pbkdf2 is a low-hanging fruit here as it only exports a single function. I propose to move it to the standard library as is, just adding an error return parameter in case we want to implement some additional parameter validation in the future (e.g. minimum salt length or minimum iterations). The proposed API looks like this:

// Key derives a key from the password, salt and iteration count,
// returning a []byte of length keyLen that can be used as cryptographic key.
// The key is derived based on the method described as PBKDF2 with the
// HMAC variant using the supplied hash function.
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) ([]byte, error)

The pbkdf2.Key function is enough to cover most pbkdf2 use cases. As a data point, OpenSSL exports the PKCS5_PBKDF2_HMAC function, accepting the same inputs as pbkdf2.Key.

@golang/security

@qmuntal qmuntal added Proposal Proposal-Crypto Proposal related to crypto packages or other security issues labels Sep 16, 2024
@gabyhelp
Copy link

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

@gopherbot gopherbot added this to the Proposal milestone Sep 16, 2024
@ianlancetaylor ianlancetaylor moved this to Incoming in Proposals Sep 16, 2024
@ericlagergren
Copy link
Contributor

My one hangup about this is that new projects shouldn't be using PBKDF2 without a good reason. There are significantly better password-based KDFs, like Argon2 or even scrypt.

IMO, it's misleading to move this into the stdlib without updating the docs to point people to better algorithms.

@qmuntal
Copy link
Contributor Author

qmuntal commented Sep 16, 2024

My one hangup about this is that new projects shouldn't be using PBKDF2 without a good reason. There are significantly better password-based KDFs, like Argon2 or even scrypt.

Reasons aside, pbkdf2 is much more popular that scrypt and argon2, at least if we look the number of times the corresponding golang.org/x/crypto package is imported. As of writing this, pbkdf2 is imported by 7081 modules, scrypt 3227 modules and argon2 1559 modules.

Having said this, it is fine for me to add any doc explaining better alternatives with the corresponding reasoning.

@rolandshoemaker
Copy link
Member

We've had higher level discussions of providing a more opaque password hashing API, wherein you just have a password you want to hash, and we return what we consider a secure hash (and also implement the verification side to transparently support multiple hashes, so we can easily rotate what we return). I think that is the long term solution to these kinds of problems where people are still using an older, less ideal method simply because they don't know they should update.

In the short-term adding pbkdf2 to the standard library seems mostly fine to me. In the long term we'll probably want to continue to provide these primitives anyway, since people will continue to need them for backwards and third-party compatibility.

I think the other option is to leave it in x/crypto as one of the "frozen" libraries we don't expect to see any further development. There are currently no other open issues for the library (and only 2 closed historical issues), so perhaps that would be reasonable if we don't expect any new API additions, and don't have any plans to make any other substantial changes to the package.

If we do add it, we should definitely add a note to the documentation though pointing out you should only be using this for compat reasons, and pointing to the better choices.

cc @FiloSottile

@qmuntal
Copy link
Contributor Author

qmuntal commented Sep 17, 2024

Thanks for the insights @rolandshoemaker.

I think the other option is to leave it in x/crypto as one of the "frozen" libraries we don't expect to see any further development. There are currently no other open issues for the library (and only 2 closed historical issues), so perhaps that would be reasonable if we don't expect any new API additions, and don't have any plans to make any other substantial changes to the package.

In the issue description I mentioned a small API change that could make pbkdf2 a little-bit harder to misuse: adding an error return value so we can validate the parameters and fail if they are not "secure" enough. I'm specifically thinking about implementing NIST SP 800-132 recommendations:

  • Minimum iteration count of 1000.
  • The length of the randomly-generated portion of the salt shall be at least
    128 bits.
  • The key length shall be at least 112 bits in length.

@rolandshoemaker
Copy link
Member

Oh sorry, I somehow completely glossed over this while reading through this. In that case I think it's reasonable to move into the standard library with these specific changes.

@meling
Copy link

meling commented Oct 11, 2024

In the issue description I mentioned a small API change that could make pbkdf2 a little-bit harder to misuse: adding an error return value so we can validate the parameters and fail if they are not "secure" enough. I'm specifically thinking about implementing NIST SP 800-132 recommendations:

  • Minimum iteration count of 1000.
  • The length of the randomly-generated portion of the salt shall be at least
    128 bits.
  • The key length shall be at least 112 bits in length.

Would such bad inputs qualify for a panic, rather than an error? (My understanding is that these are programming mistakes, not user input, and thus it might be better to panic.)

@aclements aclements moved this from Incoming to Active in Proposals Oct 23, 2024
@aclements
Copy link
Member

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.

@dpifke
Copy link
Contributor

dpifke commented Oct 23, 2024

Would such bad inputs qualify for a panic, rather than an error? (My understanding is that these are programming mistakes, not user input, and thus it might be better to panic.)

For backwards compatibility, I think we need an "escape hatch" to derive a key using older, non-secure parameters. For instance, I have backups written with software that predates the SP 800-132 recommendations that I would like to still be able to read using modern tools.

@aclements
Copy link
Member

For backwards compatibility, I think we need an "escape hatch" to derive a key using older, non-secure parameters.

There are two ways to deal with this. One is that we always compute and return the hash, but also return an error of a particular type if the parameters aren't secure. That way the caller can check for and discard that error if necessary. Another option would be a GODEBUG. (Possibly we do both and the GODEBUG would simply suppress the error return.)

@FiloSottile
Copy link
Contributor

I'm specifically thinking about implementing NIST SP 800-132 recommendations:

  • Minimum iteration count of 1000.
  • The length of the randomly-generated portion of the salt shall be at least
    128 bits.
  • The key length shall be at least 112 bits in length.

These are NIST recommendations and I am not sure we should enforce them across the board. For example salt-less PBKDF makes sense in some settings. This is what GODEBUG=fips140=enforce is for. We shouldn't let FIPS requirements infect the default mode. (Also, the minimum iteration count is a recommendation, not a shall.)

I guess adding the error return makes sense for future-proofing, although it makes switching package harder, but I don't think we need to think about GODEBUGs and new errors.

Since we're breaking the API anyway, should we move keyLen at the end like hkdf, scrypt, and argon2, and h at the beginning like hmac and hkdf?

func Key(h func() hash.Hash, password, salt []byte, iter, keyLen int) ([]byte, error)

Also, like in #61477 (comment), should we make it generic over hash.Hash?

And maybe password should be a string?

@qmuntal
Copy link
Contributor Author

qmuntal commented Nov 5, 2024

These are NIST recommendations and I am not sure we should enforce them across the board. For example salt-less PBKDF makes sense in some settings. This is what GODEBUG=fips140=enforce is for. We shouldn't let FIPS requirements infect the default mode.

I'm fine not implementing those FIPS restrictions by default if they are not mandatory. If we have the error return then we can implement whatever opt-in or opt-out requirements we want in the future.

Since we're breaking the API anyway, should we move keyLen at the end like hkdf, scrypt, and argon2, and h at the beginning like hmac and hkdf?

Good point, agree we should do this, else it will cause confusion when working with different KDFs.

Also, like in #61477 (comment), should we make it generic over hash.Hash?

And maybe password should be a string?

I don't have an opinion on these changes. Both would work for me.

@aclements
Copy link
Member

Also, like in #61477 (comment), should we make it generic over hash.Hash?

In that case, the API would be

func Key[H hash.Hash](h func() H, password, salt []byte, iter, keyLen int) ([]byte, error)

right?

This seems pretty good to me.

@rsc
Copy link
Contributor

rsc commented Nov 12, 2024

This API looks good to me. I looked at the usage of generics, and it is only being used for type-checking, not in a way that will trigger multiple copies of the code.

One small note. I'd like to see us use good names for type parameters much the same way we use good names for function arguments. For containers type T may be a special case (even then I am not sure), but certainly the rest of the time, let's use names that actually make sense in error messages. In this case, s/H/Hash/.

@FiloSottile
Copy link
Contributor

Taking the opportunity to disambiguate password and salt with types, let's do

func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLen int) ([]byte, error)

@rsc
Copy link
Contributor

rsc commented Nov 12, 2024

Whatever type we decide for salt should be the same as in #61477, right?

@FiloSottile
Copy link
Contributor

Whatever type we decide for salt should be the same as in #61477, right?

See #61477 (comment)

@aclements
Copy link
Member

Just a minor nit: in this proposal the key length argument is keyLen and in #61477, it's length. Naming it length makes the documentation a little awkward "of length length". Maybe keyLength in both?

@aclements aclements moved this from Active to Likely Accept in Proposals Nov 13, 2024
@aclements
Copy link
Member

Based on the discussion above, this proposal seems like a likely accept.

The proposal is to add a new crypto/pbkdf2 package with the following API:

// Key derives a key from the password, salt and iteration count,
// returning a []byte of length keyLength that can be used as cryptographic key.
// The key is derived based on the method described as PBKDF2 with the
// HMAC variant using the supplied hash function.
func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error)

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/628135 mentions this issue: crypto/pbkdf2: init package

@aclements
Copy link
Member

No change in consensus, so accepted. 🎉
This issue now tracks the work of implementing the proposal.

The proposal is to add a new crypto/pbkdf2 package with the following API:

// Key derives a key from the password, salt and iteration count,
// returning a []byte of length keyLength that can be used as cryptographic key.
// The key is derived based on the method described as PBKDF2 with the
// HMAC variant using the supplied hash function.
func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error)

@aclements aclements changed the title proposal: crypto/pbkdf2: add package crypto/pbkdf2: add package Nov 21, 2024
@aclements aclements modified the milestones: Proposal, Backlog Nov 21, 2024
@dmitshur dmitshur modified the milestones: Backlog, Go1.24 Nov 21, 2024
gopherbot pushed a commit that referenced this issue Nov 21, 2024
This commit imports the x/crypto/pbkdf2 package as described in the
linked proposal. The code is unchanged with the exception of a few
small updates to reflect feedback from the proposal comment period:

* the Key function is made generic over a hash.Hash
* the h function is moved to be the first argument
* keyLen is renamed to keyLength
* an error return is added
* the unit tests were moved to the pbkdf2_test package

Updates #69488

Change-Id: If72f854daeb65a5c7fbe45ebd341e63a33340624
Reviewed-on: https://go-review.googlesource.com/c/go/+/628135
Reviewed-by: Dmitri Shuralyov <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Russ Cox <[email protected]>
@cpu
Copy link
Contributor

cpu commented Nov 22, 2024

I think this can be closed since #628135 landed - I used the wrong linkage in the description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Proposal Proposal-Accepted Proposal-Crypto Proposal related to crypto packages or other security issues
Projects
Status: Accepted
Development

No branches or pull requests