808bits

FIPS 140-3 mode panics on a 12-byte key every Bitcoin wallet uses

2026-08-05 · updated 2026-10-30 · 8 min

Go’s FIPS 140-3 module refuses the first step of every Bitcoin wallet. BIP32, the key derivation standard behind effectively every wallet and custody platform, computes its master key as HMAC-SHA512 over the seed, with the literal string Bitcoin seed as the HMAC key. That string is twelve ASCII characters, 96 bits. FIPS 140-3 puts a 112-bit floor on HMAC keys, and in Go’s strict mode the call panics:

// BIP32 master key derivation (test vector 1)
seed, _ := hex.DecodeString("000102030405060708090a0b0c0d0e0f")
mac := hmac.New(sha512.New, []byte("Bitcoin seed"))
mac.Write(seed)
I := mac.Sum(nil) // left half: master key, right half: chain code
$ GODEBUG=fips140=only go run bip32.go
panic: crypto/hmac: use of keys shorter than 112 bits is not
allowed in FIPS 140-only mode

I hit this implementing BIP32 under Go 1.25.1 with the module in its strict only mode, which refuses every non-approved call. In the default on mode the same code runs, because Go only promises approved behaviour for approved operations, and the derivation is not one. There is no workaround. The key is a protocol constant, and changing it derives different wallets. The whole BIP32 tree, and everything above it (BIP39, BIP44, every hardware wallet and custody platform), cannot be offered as an approved service.

The construction is not weak. HMAC-SHA512 with a public constant key is being used as a key derivation function, and the seed supplies the entropy. The 112-bit rule was written for secret authentication keys and is being applied to a constant that was never one. The compliance framework has no way to see the difference, and that is the whole argument of this piece in miniature.

What the mode costs

Since Go 1.24 can flip FIPS mode with an environment variable, the same binary on the same machine makes a clean experiment. These are from my workstation, an i7-10510U laptop, Go 1.25.1, GODEBUG=fips140=on against off. The absolute times wobble with CPU frequency scaling between runs. The ratios held.

OperationNormalFIPS modeCost
ECDSA P-256 keygen17 µs110 µs6 to 9x slower
crypto/rand 32 bytes85 ns355 ns3 to 6x slower
ECDSA P-256 sign34 µs30 µsnone
AES-256-GCM, 16 KiB5.8 µs5.0 µsnone

The pattern is what the standard predicts. Bulk encryption and signing are untouched, since it is the same implementation with no per-operation test. Key generation pays for the pairwise consistency test, a full sign-and-verify cycle on every generated key pair, which Go’s own documentation calls “especially relevant for ephemeral keys”, and a TLS handshake generates one. Random bytes pay for the deterministic random bit generator that approved mode puts between the hardware noise source and the caller. None of it buys resistance to any attack. The pairwise test exists to catch a module that generates corrupt keys, a failure I could find no field history of, while the side channels that did extract keys from certified devices are out of scope at every level anyone buys. Approved mode also forbids some faster algorithms outright, so no ChaCha20-Poly1305 on hardware without AES acceleration and no Argon2 for passwords.

Where custody turns it off

A sales engineer at one of the major HSM vendors told me that over 90 percent of their customers who buy FIPS-enabled HSMs run them with FIPS mode disabled. I cannot verify that number and would not build on it alone. The mechanism behind it is in public product manuals, and in digital-asset custody it is structural.

Bitcoin and Ethereum sign with ECDSA over secp256k1, a curve NIST kept out of FIPS 186-5. When commenters on the draft asked for it, NIST answered that it “does not believe that the secp256k1 curve offers compelling advantages over the NIST-recommended curves”, and SP 800-186 says the curve “is allowed to be used for blockchain-related applications”. So the standard allows it, and whether a given validated module has it in its approved mode is decided certificate by certificate. AWS CloudHSM offers secp256k1 in both modes and Ed25519 only on non-FIPS-mode clusters, and the mode is fixed when the cluster is created. The rest of the stack is no better off. Ethereum’s Keccak-256 predates the padding change that became SHA-3, so it is not the FIPS 202 function despite the family name. RIPEMD-160, in every Bitcoin address, was never approved. Then the twelve-byte constant above sits under all of it.

A custody operator who flips their HSM into approved mode locks themselves out of part of what they exist to hold. So they buy the FIPS-enabled model for the procurement line, run it with the mode off, and from that day the certificate describes a configuration they do not operate.

What the certificate covers

The certificate attests that a specific module, at a specific firmware version, in a specific configuration, implements approved algorithms correctly and meets the design requirements of its level: self-tests at start-up, keys that can be zeroized, and from Level 2 up some resistance or response to physical tampering. The application calling the module, the access control around it, the people holding the operator cards and the ceremony that put the first keys inside are all outside the boundary and are never examined. In my experience most deployed HSM estates drift from their validated configuration within months, and nobody’s dashboard turns red when it happens.

The incident record says what that scope leaves out. Infineon’s RSA key generation shipped factorable keys in FIPS 140-2 and Common Criteria certified chips from at least 2012 until ROCA in 2017, in the one function the validation exists to scrutinise. Its ECDSA shipped a non-constant-time inversion for 14 years and about 80 Common Criteria evaluations until EUCLEAK in 2024, which hit the YubiKey 5 FIPS series and the YubiHSM 2 FIPS. Dual_EC_DRBG was an approved generator for eight years until NIST withdrew it in 2014, and every certificate that covered it was accurate the whole time. And in 2019 the FIPS requirements caused a flaw rather than missing one: the YubiKey FIPS series firmware 4.4.2 and 4.4.4 left predictable bits from the mandatory power-up self-tests in the random buffer, up to 80 of the 256 in an ECDSA nonce. The consumer YubiKey on the same shelf was fine. Yubico shipped the fixed firmware 4.4.5 from 30 April 2019 while its certification was still in progress, so for a while the replacement keys were the uncertified ones.

That choice is the freeze. The validation wait has run 620 to 728 days for 140-3 submissions by Corsec’s tracking, so a vendor with a security fix chooses between shipping it and falling off the list, or shipping the validated build with the known flaw, and a customer whose contract demands an active certificate is paying for the second choice. Go shows the same thing without an incident. The native module that replaced years of BoringCrypto and vendor forks holds certificate 5247, and the certificate covers version 1.0.0, frozen from Go 1.24. A later version was still in the in-process queue on 29 October, so a binary built today against the certified module runs cryptography three releases old. That is not a Go failure. It is the system working as designed.

None of this is an argument against buying validated. The programme killed off a generation of snake-oil cryptography, and “no validation, no purchase” is still a sensible floor. On 21 September every remaining FIPS 140-2 certificate, 481 of them, moved to the historical list, and a lot of procurement effort went into the 140-3 paper that replaced them. The paper moved. The modules behind it did not.

In the custody and key-management audits I have sat through, a competent auditor accepts the certificate in about thirty seconds. It is one line in the evidence request, satisfied by a number they can look up themselves. Then they spend hours on what the certificate cannot answer: is the module running the validated firmware in approved mode, who generated the keys and who watched, who holds the quorum shares today and what happened to the shares of the engineer who left in March, where the backups are cloned to, and whether the ceremony logs say what the policy says they say. The list is five questions long and none of them is about the module. The first one, in every audit I remember, was whether the mode was on.

Correction, 30 October. An earlier version said CloudHSM offers secp256k1 only outside FIPS mode (only Ed25519 is restricted), that Yubico’s 2019 replacements waited for certification (they shipped while it was pending), that the validation queue ran twelve to eighteen months (Corsec’s figures are 620 to 728 days), that Dual_EC was approved for a decade (2006 to 2014), and that the certified Go module was two releases behind (three since Go 1.27 in August). The secp256k1 quotation was from NIST’s Federal Register reply, not SP 800-186, and now quotes the standard.

Rewritten on 30 October 2026 from the August original. The 21 September sunset is now past tense, and one line was corrected: an earlier version said the algorithms cryptocurrency runs on are not FIPS-approved. SP 800-186 has allowed secp256k1 for blockchain applications since February 2023. The benchmark, the BIP32 program and the incident links are unchanged.