Skip to content

kem/hybrid: derive the same key pair from a seed every time - #699

Merged
bwesterb merged 3 commits into
cloudflare:mainfrom
lenamonj:hybrid-derive-key-pair-deterministic
Sep 7, 2026
Merged

bwesterb merged 3 commits into
cloudflare:mainfrom
lenamonj:hybrid-derive-key-pair-deterministic

Conversation

@lenamonj

@lenamonj lenamonj commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

cScheme.DeriveKeyPair read its seed through crypto/ecdh's GenerateKey, which calls randutil.MaybeReadByte and so consumes an extra byte of the reader on a random subset of calls. One seed produced different key pairs, and EncapsulateDeterministically inherited it. The P-256 hybrid KEMs are built on this scheme.

The scalar is now sampled from the SHAKE stream directly and handed to NewPrivateKey, with the top byte masked for P-521 and out-of-range candidates redrawn.

Two tests derive from one seed repeatedly; on main they fail within a few derivations.


Devin Review

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread kem/hybrid/ckem.go
Comment on lines +145 to +155
for ctr := 0; ctr < 256; ctr++ {
_, _ = h.Read(candidate)
candidate[0] &= bitmask
privKey, err := sch.curve.NewPrivateKey(candidate)
if err != nil {
// Out of range or zero; draw the next candidate.
continue
}
sk := cPrivateKey{scheme: sch, key: privKey}
pk := cPublicKey{scheme: sch, key: privKey.PublicKey()}
return &pk, &sk

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Key derivation leaks rejection timing

DeriveKeyPair exits after the first valid secret-derived scalar. Its branch and runtime expose how many candidates were rejected, violating the constant-time policy.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the sampling loop RFC 9180 section 7.1.3 specifies, and the one hpke's shortKEM.DeriveKeyPair already runs on these curves. A candidate is rejected only when it falls outside [1, n-1], with probability about 2^-32 for P-256 and P-384 and far smaller for P-521, and the rejected bytes are discarded, so the count reveals nothing about the accepted scalar.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed

Comment thread kem/hybrid/ckem.go Outdated
pk := cPublicKey{scheme: sch, key: pubKey}

return &pk, &sk
// crypto/ecdh's GenerateKey may read an extra byte from the reader

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More precisely: ecdh GenerateKey sometimes reads an extra byte on purpose to break exactly what we tried to do here.

@bwesterb

bwesterb commented Sep 7, 2026

Copy link
Copy Markdown
Member

Adjust the comment and this is good to merge.

@lenamonj

lenamonj commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Adjusted the comment.

@bwesterb
bwesterb self-requested a review September 7, 2026 14:18
@bwesterb
bwesterb merged commit 388f7d5 into cloudflare:main Sep 7, 2026
13 checks passed
@lenamonj
lenamonj deleted the hybrid-derive-key-pair-deterministic branch September 7, 2026 16:04
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.

2 participants