Repository: go
Issue: proposal: x/crypto/argon2: add password hashing/verification wrapper
Labels: Proposal,Proposal-Crypto

I propose that we extend `x/crypto/argon2` to include a wrapper for password hashing and verification, similar to what currently exists in the `x/crypto/bcrypt` package. This would provide convenient methods for developers to generate hashed passwords and compare them, using Argon2's state-of-the-art password hashing scheme.

## Background
`x/crypto/bcrypt` package provides two crucial functions: [`GenerateFromPassword`](https://github.com/golang/crypto/blob/bc7d1d1eb54b3530da4f5ec31625c95d7df40231/bcrypt/bcrypt.go#L95-L104) and [`CompareHashAndPassword`](https://github.com/golang/crypto/blob/bc7d1d1eb54b3530da4f5ec31625c95d7df40231/bcrypt/bcrypt.go#L108-L125). These make handling passwords quite straightforward for developers while still ensuring a high level of security.

However, as Argon2 is the reigning winner of the Password Hashing Competition, it would be beneficial for developers to have an equally convenient and familiar way of handling passwords using Argon2 within the Go standard library. The `x/crypto/argon2` package provides an interface to the Argon2 functionality, but it lacks the same developer-friendly methods for password hashing and verification.

Additionally, [NIST 800-63B](https://pages.nist.gov/800-63-3/sp800-63b.html) recommends using a secret value of at least 112 bits. Using `secret` is implemented in [`deriveKey`](https://github.com/golang/crypto/blob/bc7d1d1eb54b3530da4f5ec31625c95d7df40231/argon2/argon2.go#L100-L116), but the [`Key`](https://github.com/golang/crypto/blob/bc7d1d1eb54b3530da4f5ec31625c95d7df40231/argon2/argon2.go#L72-L74) and [`IDKey`](https://github.com/golang/crypto/blob/bc7d1d1eb54b3530da4f5ec31625c95d7df40231/argon2/argon2.go#L96-L98) functions pass a `nil` value when calling it.

## Current Usage
There is currently a well-written wrapper implementation with https://github.com/alexedwards/argon2id (329 ⭐) that is used in [~370 .go files](https://github.com/search?q=%22alexedwards%2Fargon2id%22+language%3AGo&type=code&l=Go) across public Github. However, it doesn't support using a NIST-recommended secret value, and given that it exists outside of the core Go packages, its long-term maintainability cannot be guaranteed.

`bcrypt.GenerateFromPassword` is [used across ~18.8k files](https://github.com/search?q=%22bcrypt.GenerateFromPassword%22+language%3AGo&type=code&l=Go), and `bcrypt.CompareHashAndPassword` is [used across ~16.9k files](https://github.com/search?q=%22bcrypt.CompareHashAndPassword%22+language%3AGo&type=code&l=Go). Of these, I suspect there is a fair number of developers who would rather be using Argon2, but they instead opted for bcrypt simply because the wrappers didn't exist or because they didn't want to rely on a third-party library for cryptography.

We're currently using a custom fork of `x/crypto/argon2` at my job to accomplish these goals, but I would *much* rather be using something from the core packages.

## Proposal
I propose adding three exported functions to the `x/crypto/argon2` package:

```
// IDKeyWithSecret derives a key from the password, salt, secret (also known as "pepper"),
// and cost parameters using Argon2id returning a byte slice of length keyLen that can be used as
// cryptographic key. The CPU cost and parallelism degree must be greater than
// zero.
func IDKeyWithSecret(password, salt, secret []byte, time, memory uint32, threads uint8, keyLen uint32) []byte {}


// GenerateFromPassword returns the hash of the password using Argon2id with the given optional secret
// and parameters for `keyLength` (length of the generated key), `time` (number of iterations over memory),
// `memory` (in kibibytes), and `threads`. If 0 is passed for keyLength, time, memory, or
// threads, they will be replaced by default values keyLength=128, time=3, memory=65536, threads=4. A random salt
// of length 16 bytes will be generated.
//
// Example usage:
// hashedPassword := argon2.GenerateFromPassword([]byte("mypassword"), nil, 3,  8*256, 2, 0)
// hashedPasswordSec := argon2.GenerateFromPassword([]byte("mypassword"), []byte("optionalsecretpepper"), 3,  8*256, 2, 0)
func GenerateFromPassword(password, secret []byte, time, memory uint32, threads uint8, keyLength uint32) ([]byte, error) {}


// CompareHashAndPassword compares an Argon2 hashed password and an optional secret with a possible
// plaintext equivalent. Returns nil on success, or an error on failure.
func CompareHashAndPassword(hashedPassword, plaintext, secret []byte) error {}
```

## Benefits
- Increases usability and developer-friendliness of the `x/crypto/argon2` package
- Encourages the use of a more modern and secure password-hashing algorithm
- Offers a standardized way to handle Argon2 hashed passwords in Go, reducing the risk of implementation errors


[cc: @FiloSottile, we chatted about this at a high level at GothamGo on 2023-06-09]