Skip to content

Zeroize raw-byte private-key parameters on destroy - #201

Merged
Xor-el merged 2 commits into
masterfrom
feature/privkey-wipe
Sep 10, 2026
Merged

Xor-el merged 2 commits into
masterfrom
feature/privkey-wipe

Conversation

@Xor-el

@Xor-el Xor-el commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

Private-key parameter classes that hold their secret as raw bytes did not wipe it
when destroyed, so the scalar lingered in freed memory until overwritten by chance.
These objects are interface-refcounted, so Destroy runs deterministically when the
last reference releases, which makes the wipe reliable rather than best-effort. This
closes an inconsistency: symmetric key material (TKeyParameter) and transient
HPKE/DHKEM buffers are already wiped, but the long-lived asymmetric private keys were
not.

Secret-hygiene hardening only: no public behaviour change, no wire-output change.

What changed

Byte-scalar private keys (already own a private copy of the scalar): add a wiping
destructor.

  • TX25519PrivateKeyParameters
  • TX448PrivateKeyParameters
  • TEd25519PrivateKeyParameters
  • TEd448PrivateKeyParameters
  • TBip340SchnorrPrivateKeyParameters

PQC private keys (constructors previously stored their arrays by reference): take
private copies of the secret fields on construction so the destructor can wipe them
without touching a public value that may be shared with the corresponding public key.
Public fields are left untouched.

  • TMlKemPrivateKeyParameters - wipe FSeed, FEncoding.
  • TMlDsaPrivateKeyParameters - wipe FK, FTr, FS1, FS2, FT0, FSeed;
    public FRho/FT1 untouched.
  • TSlhDsaPrivateKeyParameters - wipe FSk.Seed, FSk.Prf; public FPk untouched.

Tests: new PrivateKeyWipeTests builds an ML-KEM and an ML-DSA key, captures the
secret buffer by reference, drops the key, and asserts the buffer is zeroed. (A nested
helper is used so the compiler's hidden interface temporaries are released and the
destructor actually runs before the assertion.) The byte-scalar and SLH-DSA keys expose
no by-reference seam, so their identical wipe is covered by the existing key-agreement,
signature and KEM suites.

Private-key parameter classes that hold their secret as raw bytes did not wipe it
when destroyed, so the scalar lingered in freed memory until overwritten. These
objects are interface-refcounted, so Destroy runs deterministically when the last
reference releases, making the wipe reliable rather than best-effort.

Add a wiping destructor to the byte-scalar private keys (X25519, X448, Ed25519,
Ed448, Bip340 Schnorr), which already own a private copy of the scalar.

For the ML-KEM, ML-DSA and SLH-DSA private keys, whose constructors previously
stored their arrays by reference, take private copies of the secret fields on
construction so the destructor can wipe them without touching a public value that
may be shared with the corresponding public key; the public fields are left as-is.

Add PrivateKeyWipeTests, which builds an ML-KEM and an ML-DSA key, captures the
secret buffer by reference, drops the key, and asserts the buffer is zeroed.
@Xor-el
Xor-el merged commit a3ac7cb into master Sep 10, 2026
28 checks passed
@Xor-el
Xor-el deleted the feature/privkey-wipe branch September 10, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant