| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package entropy implements a CPU jitter-based SP 800-90B entropy source. |
| package entropy |
| |
| import ( |
| "crypto/internal/fips140deps/time" |
| "errors" |
| "sync/atomic" |
| "unsafe" |
| ) |
| |
| // Version returns the version of the entropy source. |
| // |
| // This is independent of the FIPS 140-3 module version, in order to reuse the |
| // ESV certificate across module versions. |
| func Version() string { |
| return "v1.0.0" |
| } |
| |
| // ScratchBuffer is a large buffer that will be written to using atomics, to |
| // generate noise from memory access timings. Its contents do not matter. |
| type ScratchBuffer [1 << 25]byte |
| |
| // Seed returns a 384-bit seed with full entropy. |
| // |
| // memory is passed in to allow changing the allocation strategy without |
| // modifying the frozen and certified entropy source in this package. |
| // |
| // Seed returns an error if the entropy source startup health tests fail, which |
| // has a non-negligible chance of happening. |
| func Seed(memory *ScratchBuffer) ([48]byte, error) { |
| // Collect w = 1024 samples, each certified to provide no less than h = 0.5 |
| // bits of entropy, for a total of hᵢₙ = w × h = 512 bits of entropy, over |
| // nᵢₙ = w × n = 8192 bits of input data. |
| var samples [1024]byte |
| if err := Samples(samples[:], memory); err != nil { |
| return [48]byte{}, err |
| } |
| |
| // Use a vetted unkeyed conditioning component, SHA-384, with nw = 384 and |
| // nₒᵤₜ = 384. Per the formula in SP 800-90B Section 3.1.5.1.2, the output |
| // entropy hₒᵤₜ is: |
| // |
| // sage: n_in = 8192 |
| // sage: n_out = 384 |
| // sage: nw = 384 |
| // sage: h_in = 512 |
| // sage: P_high = 2^(-h_in) |
| // sage: P_low = (1 - P_high) / (2^n_in - 1) |
| // sage: n = min(n_out, nw) |
| // sage: ψ = 2^(n_in - n) * P_low + P_high |
| // sage: U = 2^(n_in - n) + sqrt(2 * n * 2^(n_in - n) * ln(2)) |
| // sage: ω = U * P_low |
| // sage: h_out = -log(max(ψ, ω), 2) |
| // sage: h_out.n() |
| // 384.000000000000 |
| // |
| // According to Implementation Guidance D.K, Resolution 19, since |
| // |
| // - the conditioning component is vetted, |
| // - hᵢₙ = 512 ≥ nₒᵤₜ + 64 = 448, and |
| // - nₒᵤₜ ≤ security strength of SHA-384 = 384 (per SP 800-107 Rev. 1, Table 1), |
| // |
| // we can claim the output has full entropy. |
| return SHA384(&samples), nil |
| } |
| |
| // Samples starts a new entropy source, collects the requested number of |
| // samples, conducts startup health tests, and returns the samples or an error |
| // if the health tests fail. |
| // |
| // The health tests have a non-negligible chance of failing. |
| func Samples(samples []uint8, memory *ScratchBuffer) error { |
| if len(samples) < 1024 { |
| return errors.New("entropy: at least 1024 samples are required for startup health tests") |
| } |
| s := newSource(memory) |
| for range 4 { |
| // Warm up the source to avoid any initial bias. |
| _ = s.Sample() |
| } |
| for i := range samples { |
| samples[i] = s.Sample() |
| } |
| if err := RepetitionCountTest(samples); err != nil { |
| return err |
| } |
| if err := AdaptiveProportionTest(samples); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| type source struct { |
| memory *ScratchBuffer |
| lcgState uint32 |
| previous int64 |
| } |
| |
| func newSource(memory *ScratchBuffer) *source { |
| return &source{ |
| memory: memory, |
| lcgState: uint32(time.HighPrecisionNow()), |
| previous: time.HighPrecisionNow(), |
| } |
| } |
| |
| // touchMemory performs a write to memory at the given index. |
| // |
| // The memory slice is passed in and may be shared across sources e.g. to avoid |
| // the significant (~500µs) cost of zeroing a new allocation on every [Seed] call. |
| func touchMemory(memory *ScratchBuffer, idx uint32) { |
| idx = idx / 4 * 4 // align to 32 bits |
| u32 := (*uint32)(unsafe.Pointer(&memory[idx])) |
| last := atomic.LoadUint32(u32) |
| atomic.SwapUint32(u32, last+13) |
| } |
| |
| func (s *source) Sample() uint8 { |
| // Perform a few memory accesses in an unpredictable pattern to expose the |
| // next measurement to as much system noise as possible. |
| memory, lcgState := s.memory, s.lcgState |
| if memory == nil { // remove the nil check from the inlined touchMemory calls |
| panic("entropy: nil memory buffer") |
| } |
| for range 64 { |
| lcgState = 1664525*lcgState + 1013904223 |
| // Discard the lower bits, which tend to fall into short cycles. |
| idx := (lcgState >> 6) & (1<<25 - 1) |
| touchMemory(memory, idx) |
| } |
| s.lcgState = lcgState |
| |
| t := time.HighPrecisionNow() |
| sample := t - s.previous |
| s.previous = t |
| |
| // Reduce the symbol space to 256 values, assuming most of the entropy is in |
| // the least-significant bits, which represent the highest-resolution timing |
| // differences. |
| return uint8(sample) |
| } |
| |
| // RepetitionCountTest implements the repetition count test from SP 800-90B |
| // Section 4.4.1. It returns an error if any symbol is repeated C = 41 or more |
| // times in a row. |
| // |
| // This C value is calculated from a target failure probability α = 2⁻²⁰ and a |
| // claimed min-entropy per symbol h = 0.5 bits, using the formula in SP 800-90B |
| // Section 4.4.1. |
| // |
| // sage: α = 2^-20 |
| // sage: H = 0.5 |
| // sage: 1 + ceil(-log(α, 2) / H) |
| // 41 |
| func RepetitionCountTest(samples []uint8) error { |
| x := samples[0] |
| count := 1 |
| for _, y := range samples[1:] { |
| if y == x { |
| count++ |
| if count >= 41 { |
| return errors.New("entropy: repetition count health test failed") |
| } |
| } else { |
| x = y |
| count = 1 |
| } |
| } |
| return nil |
| } |
| |
| // AdaptiveProportionTest implements the adaptive proportion test from SP 800-90B |
| // Section 4.4.2. It returns an error if any symbol appears C = 410 or more |
| // times in the last W = 512 samples. |
| // |
| // This C value is calculated from a target failure probability α = 2⁻²⁰, a |
| // window size W = 512, and a claimed min-entropy per symbol h = 0.5 bits, using |
| // the formula in SP 800-90B Section 4.4.2, equivalent to the Microsoft Excel |
| // formula 1+CRITBINOM(W, power(2,(−H)),1−α). |
| // |
| // sage: from scipy.stats import binom |
| // sage: α = 2^-20 |
| // sage: H = 0.5 |
| // sage: W = 512 |
| // sage: C = 1 + binom.ppf(1 - α, W, 2**(-H)) |
| // sage: ceil(C) |
| // 410 |
| func AdaptiveProportionTest(samples []uint8) error { |
| var counts [256]int |
| for i, x := range samples { |
| counts[x]++ |
| if i >= 512 { |
| counts[samples[i-512]]-- |
| } |
| if counts[x] >= 410 { |
| return errors.New("entropy: adaptive proportion health test failed") |
| } |
| } |
| return nil |
| } |