Skip to content

Fix HKDF and HMAC with SHA-3 under system crypto backends#2360

Merged
qmuntal merged 7 commits into
microsoft/release-branch.go1.26from
dev/mclayton/fix-2356-hkdf-sha3
Jun 12, 2026
Merged

Fix HKDF and HMAC with SHA-3 under system crypto backends#2360
qmuntal merged 7 commits into
microsoft/release-branch.go1.26from
dev/mclayton/fix-2356-hkdf-sha3

Conversation

@michelle-clayton-work

Copy link
Copy Markdown
Contributor

Problem

In 1.26, SHA-3 is routed through the system crypto backend (e.g. OpenSSL on
Linux). When a backend is active and supports SHA-3, sha3.New256 (and the
other constructors) return a SHA3 whose backend hash is stored in boringS,
while the embedded pure-Go digest s is left as a zero value.

fips140hash_sha3Unwrap returned &s.s — that empty Go digest — whose
Size() and BlockSize() are both 0. Unwrapping is used by HKDF and HMAC,
so:

  • HKDF computed limit := Size() * 255 == 0 and rejected any positive key
    length with the misleading error hkdf: requested key length too large.
  • HMAC received a BlockSize() of 0, writing the key into a zero-rate
    sponge whose absorb loop never makes progress — an infinite hang.

Both symptoms are specific to system-crypto builds; the pure-Go backend
(GOEXPERIMENT=nosystemcrypto) and upstream Go are unaffected.

Fix

  • Return boringS from fips140hash_sha3Unwrap when the SHA3 is
    backend-backed, and widen the sha3Unwrap linkname signature to hash.Hash.
  • Reorder hmac.New so boring.NewHMAC runs after the hash is unwrapped,
    so the backend receives its own hash type instead of the wrapped *SHA3.

Testing

  • hkdf.Key(sha3.New256, …) now succeeds.
  • HMAC-SHA3 completes and matches both the pure-Go backend and OpenSSL's
    openssl mac -digest SHA3-256.

Related:

Backport the crypto/sha3, crypto/internal/fips140hash and crypto/hmac
hunks of d06bfbe (PR #2149) to release-branch.go1.26. Fixes #2356.

When a system crypto backend is active and supports SHA-3, sha3.New256
(and friends) return a SHA3 whose backend hash is stored in boringS while
the embedded Go digest s is left zero. fips140hash_sha3Unwrap returned
&s.s, the empty Go digest, whose Size() is 0, so HKDF computed
limit := 0 * 255 and rejected any positive key length with the misleading
error "hkdf: requested key length too large". Return boringS when the
SHA3 is backend-backed, and widen the sha3Unwrap linkname to hash.Hash.

Also reorder hmac.New so boring.NewHMAC runs after the hash is unwrapped,
so the backend receives its own hash type instead of the wrapped *SHA3.
Copilot AI review requested due to automatic review settings June 10, 2026 20:07
@michelle-clayton-work michelle-clayton-work requested a review from a team as a code owner June 10, 2026 20:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a system-crypto–backend-specific SHA-3 wrapping bug that broke consumers relying on correct hash.Hash metadata (notably HKDF and HMAC), by ensuring the unwrap path returns a functional backend hash and by unwrapping before backend HMAC selection.

Changes:

  • Update the SHA-3 unwrap linkname path to return the backend-backed hash when present (and widen the linkname signature to hash.Hash).
  • Reorder crypto/hmac.New so the hash constructor is unwrapped before attempting boring.NewHMAC, preventing backend type mismatches.
  • (Also in this patch) expand SHA-3/SHAKE/CSHAKE routing through the system-crypto backend.
Comments suppressed due to low confidence (1)

patches/0004-Use-crypto-backends.patch:2995

  • The PR description’s “Fix” section describes changes limited to sha3 unwrap + HMAC ordering, but this patch also introduces broader SHA-3/SHAKE/CSHAKE routing through the system-crypto backend (e.g., Sum256/SumSHAKE256 and constructors now selecting backend implementations). If this broader behavior change is intended, please reflect it in the PR description (or consider splitting it out) so reviewers understand the full scope and risk surface.
--- a/src/crypto/sha3/sha3.go
+++ b/src/crypto/sha3/sha3.go
@@ -8,6 +8,7 @@ package sha3
 
 import (
 	"crypto"
+	boring "crypto/internal/backend"
 	"crypto/internal/fips140/sha3"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread patches/0004-Use-crypto-backends.patch
Comment thread patches/0004-Use-crypto-backends.patch
Cover the two failure modes fixed in the previous commit:

  * TestHKDFSHA3 derives a 32-byte key with SHA3-256 and fails if HKDF
    reports "requested key length too large", the symptom of the
    backend-backed SHA-3 hash reporting Size() == 0. Backends that do not
    implement HKDF with SHA-3 (for example macOS CryptoKit) skip instead.

  * TestHMACSHA3 builds an HMAC with SHA3-256 and checks it returns,
    guarding against the infinite loop. The macOS CryptoKit backend
    advertises SHA-3 hashing but cannot compute HMAC-SHA3 and aborts the
    process, so the test skips there; that is a separate backend
    limitation.
Address review feedback: skip TestHKDFSHA3 only when the macOS CryptoKit
backend genuinely lacks HKDF-SHA3, and fail on any other error instead of
turning unexpected failures into skips. This keeps the test guarding
behavior on Linux/OpenSSL and Windows/CNG.
The CI darwin builders run on a macOS where CryptoKit does not expose
SHA-3 hashing, so boring.SupportsHash(crypto.SHA3_256) is false there and
TestHKDFSHA3/TestHMACSHA3 fell through to a failure instead of skipping.

Skip on the CryptoKit backend purely by build (boring.Enabled and
GOOS=="darwin"), which is independent of the macOS version, since
CryptoKit never supports HKDF or HMAC with SHA-3. OpenSSL, CNG, and the
pure Go path still run the assertions. Drop the now-unused crypto import.
Comment thread patches/0004-Use-crypto-backends.patch Outdated
+// constructor received the still-wrapped *sha3.SHA3 instead of the unwrapped
+// hash. The hash must now be unwrapped before the backend sees it.
+func TestHMACSHA3(t *testing.T) {
+ if boring.Enabled && runtime.GOOS == "darwin" {

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.

hmac.New returns nil if the backend doesn't support the given hash. You can skip when that happens instead of hardcoding darwin here.

Comment thread patches/0004-Use-crypto-backends.patch Outdated
+ // not issue #2356. OpenSSL, CNG, and the pure Go path all support it, so
+ // only skip on the CryptoKit backend and fail otherwise rather than
+ // hiding an unexpected error.
+ if boring.Enabled && runtime.GOOS == "darwin" {

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.

If this is a real issue, then create an issue to track it, thanks!

@michelle-clayton-work michelle-clayton-work Jun 11, 2026

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.

@qmuntal qmuntal merged commit 47f4511 into microsoft/release-branch.go1.26 Jun 12, 2026
42 checks passed
@qmuntal qmuntal deleted the dev/mclayton/fix-2356-hkdf-sha3 branch June 12, 2026 07:14
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.

3 participants