hynek / argon2-cffi

Secure Password Hashes for Python
https://argon2-cffi.readthedocs.io/
MIT License
549 stars 47 forks source link

RFC is no longer a draft (RFC9106); default parameter choice out of date #101

Closed B-McDonnell closed 2 years ago

B-McDonnell commented 3 years ago

Argon2 now has an official informational RFC, not just a draft: RFC 9106. This change occurred on September 7th.

The previous RFC draft is referenced in two locations:

  1. PasswordHasher's docstring
  2. The "Parameters" documentation

and default parameters (no longer in line with the RFC) are implemented in PasswordHasher.

I believe the parameter choice changes were made in #41 in 2018. Since then, the draft went through revisions 4 through 13 before being marked as done. Since then, the Parameter Choice section has changed (diff here).

The RFC now recommends the following:

  1. If a uniformly safe option that is not tailored to your application or hardware is acceptable, select Argon2id with t=1 iteration, p=4 lanes, m=2^(21) (2 GiB of RAM), 128-bit salt, and 256-bit tag size. This is the FIRST RECOMMENDED option.

  2. If much less memory is available, a uniformly safe option is Argon2id with t=3 iterations, p=4 lanes, m=2^(16) (64 MiB of RAM), 128-bit salt, and 256-bit tag size. This is the SECOND RECOMMENDED option.

  3. Otherwise, start with selecting the type y. If you do not know the difference between the types or you consider side-channel attacks to be a viable threat, choose Argon2id.

  4. Select p=4 lanes.

  5. Figure out the maximum amount of memory that each call can afford and translate it to the parameter m.

  6. Figure out the maximum amount of time (in seconds) that each call can afford.

  7. Select the salt length. A length of 128 bits is sufficient for all applications but can be reduced to 64 bits in the case of space constraints.

  8. Select the tag length. A length of 128 bits is sufficient for most applications, including key derivation. If longer keys are needed, select longer tags.

  9. If side-channel attacks are a viable threat or if you're uncertain, enable the memory-wiping option in the library call.

  10. Run the scheme of type y, memory m, and p lanes using a different number of passes t. Figure out the maximum t such that the running time does not exceed the affordable time. If it even exceeds for t = 1, reduce m accordingly.

  11. Use Argon2 with determined values m, p, and t.

Major changes:

  1. There are now two recommended general use options for different memory requirements.
  2. Four lanes instead of twice the number of dedicated cores.
  3. There is no longer a recommendation for the maximum memory.
  4. There is no longer a recommendation for the maximum time.
  5. The RFC references the memory-wiping function for side-channel attack mitigation.

argon-cffi's parameter choice should be updated to reflect the official RFC and documentation should be updated.

Currently, the default parameters are as follows:

DEFAULT_RANDOM_SALT_LENGTH = 16
DEFAULT_HASH_LENGTH = 16
DEFAULT_TIME_COST = 2
DEFAULT_MEMORY_COST = 102400
DEFAULT_PARALLELISM = 8

They should be adapted to one of the recommended general use options:

# Default (high memory use)
DEFAULT_RANDOM_SALT_LENGTH = 16    # 128-bit salt
DEFAULT_HASH_LENGTH = 32           # 256-bit tag size
DEFAULT_TIME_COST = 1              # t = 1 iteration
DEFAULT_MEMORY_COST = 2097152      # m=2^(21) (2 GiB of RAM)
DEFAULT_PARALLELISM = 4            # p = 4 lanes

# Low memory use
DEFAULT_RANDOM_SALT_LENGTH = 16    # 128-bit salt
DEFAULT_HASH_LENGTH = 32           # 256-bit tag size
DEFAULT_TIME_COST = 3              # t = 3 iterations
DEFAULT_MEMORY_COST = 65536        # m=2^(16) (64 MiB of RAM)
DEFAULT_PARALLELISM = 4            # p = 4 lanes

It may also be useful to set some flag for which set of default parameters may be preferred.

Finally, having a utility to automatically find t given the other parameters, as per the following:

  1. Run the scheme of type y, memory m, and p lanes using a different number of passes t. Figure out the maximum t such that the running time does not exceed the affordable time. If it even exceeds for t = 1, reduce m accordingly.

It's time for everyone to make good use of check_needs_rehash!

B-McDonnell commented 3 years ago

It may also be useful to set some flag for which set of default parameters may be preferred.

Some possible implementations:

  1. Boolean reduced_memory. Causes defaults to be overwritten by reduced memory defaults.
  2. Passing a parameter object. Could be part of the constructor and override defaults or separate from constructor as a class method:
    class PasswordHasher:
        ...
        @classmethod
        def with_default_parameters(cls, parameters: Parameters) -> PasswordHasher:
            # note: DefaultParameters and LowMemoryDefeaultParameters objects
            # would need to be defined elsewhere
            return PasswordHasher(
                time_cost=parameter.time_cost,
                ...
            )

    (This isn't a complete implementation, as it doesn't allow for parameter override --- just a proof of concept)

  3. LowMemoryPasswordHasher child class.
hynek commented 2 years ago

Hi Brendan, sorry for the long silence. When you opened this issue I was traveling (!!!!) and I've been procrastinating on a release ever since because it's a big topic.

I think the best way forward is making the low-mem case the default and allow to create hashers from Parameters as per your option 2.

It was so clear that they had to publish the final version a few days after I published a basically non-release. 🤪

B-McDonnell commented 2 years ago

I think the best way forward is making the low-mem case the default and allow to create hashers from Parameters as per your option 2.

Would this provide a default high memory Parameters object? e.g. something like

from argon2 import PasswordHasher, HighMemoryParameters

high_mem_hasher = PasswordHasher.from_parameters(HighMemoryParameters)
hynek commented 2 years ago

Yeah, I’d supply both RFC options at least. Maybe additionally the current defaults for people who want to keep them. Could live in a separate module argon2.profiles.RFC9106HighMemory or something.