Skip to content

ChaCha20: change the current API (and recommendations) #9017

@woodruffw

Description

@woodruffw

This is a tie-together for Cryptography's side of the discussions in C2SP/wycheproof#90 and https://bugs.chromium.org/p/boringssl/issues/detail?id=614.

Status quo

The current ChaCha20 API in Cryptography is defined as follows:

algorithms.ChaCha20(key, nonce)

...where nonce is a bytes of exactly len(nonce) == 16. This means that it contains both the "nonce" and "counter" fields used by ChaCha20, and leaves it to the underlying implementation to determine the appropriate nonce/counter split.

As part of that, the current documentation also encourages uses to fully randomize the nonce input, with os.urandom(16):

https://cryptography.io/en/latest/hazmat/primitives/symmetric-encryption/#cryptography.hazmat.primitives.ciphers.algorithms.ChaCha20

Finally, the API implies that the underlying ChaCha20 implementation implementation is RFC 7539, when the two current underlying implementations (OpenSSL and LibreSSL) are not (due to a 64/64 nonce/ctr split rather than 96/32).

Put together, these three points mean that the current ChaCha20 API's internal implementations are more likely to diverge from RFC 7539 than they otherwise would be: if a user randomly initializes nonce, then the randomly initialized interior counter may be much closer to RFC 7539's block limit than a user might otherwise expect.

I have two proposals for resolving this: one that forces all implementations to behave as if they're RFC 7539 compliant, and another that explicitly separates the API into 64/64 and 96/32 implementations.

Proposal 1

My first proposal is a semi-breaking change. Behaviorally:

  1. The API itself will change, with nonce becoming a 12-byte nonce rather than a 16-byte nonce;
  2. Internally, the ChaCha20 API will initialize the 32-bit counter (to 0) and combine it with the nonce.
  3. Optionally, the API could grow a new counter= argument, which would allow the user to explicitly initialize the counter to a specific value in [0, 2^32).

In effect, this would make the non-RFC-compliant implementations of ChaCha20 behave as if they're compliant within the well-defined parameters of the RFC (i.e., up to 256 GB of enciphering), and would ensure that the majority of users never exceed those parameters (since most users will not need to manually initialize the counter).

Notably, this would be a breaking API change in terms of public API parameters, but a non-breaking change in terms of documented behavior (since it would be a bugfix to enforce compliance with RFC 7539, as documented).

Proposal 2

My second proposal is non-breaking, but requires a documentation correction and a new API for the RFC 7539-compliant version of ChaCha20. Behaviorally:

  1. The existing ChaCha20 API's documentation will be updated to emphasize that it isn't intended to be RFC 7539 compliant. We'd also add test cases to ensure compliance with the 64/64 variant, e.g. at what would be the 32-bit counter overflow point.
  2. We'd add a new ChaCha20RFC API (with a better name than that), which would then support the RFC 7539 variant. This API would then use the pre-existing ChaCha20 API with a fixed 32-bit nonce, forcing all implementations to behave as if they're compliant (at least within the bounds defined by the RFC -- exceeding those bounds will still exhibit divergences).

This would be non-breaking. However, if a little bit of breakage is acceptable, I think combining this with a small change to ChaCha20 to make nonce only 8 bytes (and allow an optional 64-bit counter) would be the best of both worlds: doing so would eliminate any confusion over initial high counter values, and would make the RFC-compliant version's interior use of the non-RFC version slightly more explicit.

See also #8956.

cc @facutuesca

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions