Skip to content

Security: Prevent keystream reuse when cloning or resuming stateful cipher workers #173

Open

Description

@officialfrancismendoza

Summary

The new key-management architecture introduced in #172 raises a security concern around cloning and suspending stateful cipher workers.

Some cipher workers, including stream ciphers and AEAD implementations, can be cloned while an encryption/decryption operation is in progress. Cloning a worker may duplicate its internal state, including the current nonce, counter, and keystream position.

If both copies are subsequently used for encryption under the same key, they may generate identical keystream bytes.

This is especially dangerous for stream ciphers such as ChaCha20 and AES-CTR, where keystream reuse can compromise message confidentiality.

Related PRs:

Security implications

For a stream cipher:

C = P XOR K

Where:

C = Ciphertext

P = Plaintext

K = Generated keystream

If two workers are cloned at the same state and subsequently encrypt different plaintexts using the same keystream:

C1 XOR C2 = P1 XOR P2

The keystream cancels, potentially revealing information about both plaintexts.

For AEAD implementations, reusing the same key/nonce combination may additionally undermine authentication guarantees.

The concern extends to Suspendable and SuspendableKeyed interfaces if saved state can be restored multiple times, allowing multiple workers to continue from the same cryptographic state.

Note: This is a potential API misuse vulnerability, not a claim that the current implementation automatically reuses nonces or keystreams.

Affected interfaces

The following should be reviewed:

  • Clone implementations on stateful encryption/decryption workers.
  • Suspendable and SuspendableKeyed implementations.
  • Resume operations that accept previously serialized cryptographic state.
  • AEAD workers whose internal state includes nonce/counter or authentication state.
  • APIs that allow multiple workers to be initialized from the same key/nonce combination.

In #157, implementing SuspendableKeyed for ChaCha20-Poly1305 was explicitly deferred because of these concerns.

Potential solutions

Option 1: Remove Clone from stateful cipher workers

Prevent workers from being duplicated while they hold an active cryptographic state.

Advantages:

  • Compile-time prevention of accidental worker cloning.
  • Simple security model.
  • Consistent with Rust's ownership principles.

Disadvantages:

  • Breaking API change.
  • May affect legitimate use cases involving cloned state.

Recommendation: Strong default for encryption workers.

Option 2: Restrict or redesign SuspendableKeyed

Treat suspended encryption state as a one-time continuation capability.

Possible approaches include:

  • Consuming the state when resuming.
  • Avoiding Clone on in-memory suspended-state objects.
  • Requiring explicit unsafe or hazardous APIs for state duplication.
  • Introducing state-reuse tracking where appropriate.

Important limitation: Consuming an in-memory state does not prevent a caller from copying serialized checkpoint bytes before resuming.

Preventing replay across serialized copies requires additional mechanisms, such as persistent state tracking or monotonic counters, and cannot be guaranteed through Rust ownership alone.

Advantages:

  • Reduces accidental state reuse.
  • Makes security expectations explicit.

Disadvantages:

  • Potentially breaking API changes.
  • Persistent replay prevention introduces additional complexity.

Option 3: Separate encryption and decryption capabilities

Apply stricter rules to encryption workers than to decryption workers.

For example:

  • Encryption workers are non-cloneable.
  • Decryption workers may support cloning or replay when appropriate.
  • Encryption resume operations require additional safeguards.

Advantages:

  • Preserves useful decryption functionality.
  • Targets restrictions at the highest-risk operations.

Disadvantages:

  • More complex trait bounds and APIs.
  • Does not eliminate misuse through copied serialized state.

Option 4: Document the risks and retain the existing API

Keep the current interfaces but document that cloning or restoring an encryption state can result in keystream reuse.

Advantages:

  • Minimal implementation effort.
  • No breaking changes.

Disadvantages:

  • Relies entirely on callers following security guidance.
  • Does not prevent accidental misuse.

Recommendation: Useful as defense-in-depth, but insufficient as the only mitigation for a misuse-resistant cryptographic API.

Proposed direction

I recommend:

  1. Remove Clone from active encryption workers by default, unless an algorithm-specific security justification exists.
  2. Review whether SuspendableKeyed should be implemented for stateful encryption workers at all.
  3. If encryption-state restoration is supported, explicitly define its replay and state-ownership semantics.
  4. Distinguish encryption from decryption where different restrictions are appropriate.
  5. Add compile-fail tests verifying that restricted worker types cannot be cloned.
  6. Add regression tests covering suspension/resumption and the handling of duplicated checkpoints.

Questions for maintainers

  • Should Clone be permitted on active encryption workers?
  • Is SuspendableKeyed intended to support resumable encryption, or primarily state persistence for other purposes?
  • Should the library guarantee misuse resistance against duplicated state, or explicitly place that responsibility on callers?
  • Should encryption and decryption workers have different cloning and suspension capabilities?

The goal is to establish consistent security semantics across the cipher APIs before adding SuspendableKeyed support to ChaCha20-Poly1305 and other stateful AEAD implementations.

Activity

  1. changed the issue type fromtoon Oct 28, 2026
  2. added this to the 0.1.4alpha milestone on Oct 28, 2026
  3. dghgit commented on Oct 28, 2026

    @dghgit
    Contributor

    Looking at the initial release note on why this was set up for ciphers it seems that if ciphers were dyn capable, people could Box where alloc is okay, or use a static where it isn't. In both cases this should prevent misuse and accidents. With the constructors out of the encryptor/decryptor traits the only thing remaining in 0.1.4 is the way Algorithm (replace the consts with methods) is defined and the use of Sized (which we can fence for the *_out_len functions).

  4. officialfrancismendoza commented on Oct 28, 2026

    @officialfrancismendoza
    ContributorAuthor

    Thanks @dghgit. IMO making the cipher traits dyn-compatible makes sense and should reduce the need for suspension just to retain workers.

    My main concern is ensuring we prevent state duplication through other paths as well. Removing Clone from active encryption workers is a good default. But serialized checkpoints can still be copied and restored multiple times, potentially reusing the keystream.

    I think we should keep active encryption workers non-cloneable by default and restrict or explicitly document encryption-state restoration. That should give us a clearer security model without overcomplicating the API. What do you think?

  5. dghgit commented on Oct 30, 2026

    @dghgit
    Contributor

    Given what kind of things people will ping us for on the security front, I'd agree on the non-cloneable. I don't think we've run into a situation in Java or C# to date where this sort of thing was required either - so I think specifying another trait and, perhaps, hazmatting it's usage would be the way to go, but I would wait till someone is knocking on the door with a real issue before trying to add it (even then, it may turn out what's required is sufficiently specialised that a "one off approach" might be required).

    I'd suggest we come up with a plan for doing a "dyn" sweep for the symmetric key algorithms which we can apply once the remaining ciphers have been removed and merged in, based off key-rework-memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions