Source file src/crypto/internal/fips140/entropy/entropy.go
1 // Copyright 2025 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package entropy implements a CPU jitter-based SP 800-90B entropy source. 6 package entropy 7 8 import ( 9 "crypto/internal/fips140deps/time" 10 "errors" 11 "sync/atomic" 12 "unsafe" 13 ) 14 15 // Version returns the version of the entropy source. 16 // 17 // This is independent of the FIPS 140-3 module version, in order to reuse the 18 // ESV certificate across module versions. 19 func Version() string { 20 return "v1.0.0" 21 } 22 23 // ScratchBuffer is a large buffer that will be written to using atomics, to 24 // generate noise from memory access timings. Its contents do not matter. 25 type ScratchBuffer [1 << 25]byte 26 27 // Seed returns a 384-bit seed with full entropy. 28 // 29 // memory is passed in to allow changing the allocation strategy without 30 // modifying the frozen and certified entropy source in this package. 31 // 32 // Seed returns an error if the entropy source startup health tests fail, which 33 // has a non-negligible chance of happening. 34 func Seed(memory *ScratchBuffer) ([48]byte, error) { 35 // Collect w = 1024 samples, each certified to provide no less than h = 0.5 36 // bits of entropy, for a total of hᵢₙ = w × h = 512 bits of entropy, over 37 // nᵢₙ = w × n = 8192 bits of input data. 38 var samples [1024]byte 39 if err := Samples(samples[:], memory); err != nil { 40 return [48]byte{}, err 41 } 42 43 // Use a vetted unkeyed conditioning component, SHA-384, with nw = 384 and 44 // nₒᵤₜ = 384. Per the formula in SP 800-90B Section 3.1.5.1.2, the output 45 // entropy hₒᵤₜ is: 46 // 47 // sage: n_in = 8192 48 // sage: n_out = 384 49 // sage: nw = 384 50 // sage: h_in = 512 51 // sage: P_high = 2^(-h_in) 52 // sage: P_low = (1 - P_high) / (2^n_in - 1) 53 // sage: n = min(n_out, nw) 54 // sage: ψ = 2^(n_in - n) * P_low + P_high 55 // sage: U = 2^(n_in - n) + sqrt(2 * n * 2^(n_in - n) * ln(2)) 56 // sage: ω = U * P_low 57 // sage: h_out = -log(max(ψ, ω), 2) 58 // sage: h_out.n() 59 // 384.000000000000 60 // 61 // According to Implementation Guidance D.K, Resolution 19, since 62 // 63 // - the conditioning component is vetted, 64 // - hᵢₙ = 512 ≥ nₒᵤₜ + 64 = 448, and 65 // - nₒᵤₜ ≤ security strength of SHA-384 = 384 (per SP 800-107 Rev. 1, Table 1), 66 // 67 // we can claim the output has full entropy. 68 return SHA384(&samples), nil 69 } 70 71 // Samples starts a new entropy source, collects the requested number of 72 // samples, conducts startup health tests, and returns the samples or an error 73 // if the health tests fail. 74 // 75 // The health tests have a non-negligible chance of failing. 76 func Samples(samples []uint8, memory *ScratchBuffer) error { 77 if len(samples) < 1024 { 78 return errors.New("entropy: at least 1024 samples are required for startup health tests") 79 } 80 s := newSource(memory) 81 for range 4 { 82 // Warm up the source to avoid any initial bias. 83 _ = s.Sample() 84 } 85 for i := range samples { 86 samples[i] = s.Sample() 87 } 88 if err := RepetitionCountTest(samples); err != nil { 89 return err 90 } 91 if err := AdaptiveProportionTest(samples); err != nil { 92 return err 93 } 94 return nil 95 } 96 97 type source struct { 98 memory *ScratchBuffer 99 lcgState uint32 100 previous int64 101 } 102 103 func newSource(memory *ScratchBuffer) *source { 104 return &source{ 105 memory: memory, 106 lcgState: uint32(time.HighPrecisionNow()), 107 previous: time.HighPrecisionNow(), 108 } 109 } 110 111 // touchMemory performs a write to memory at the given index. 112 // 113 // The memory slice is passed in and may be shared across sources e.g. to avoid 114 // the significant (~500µs) cost of zeroing a new allocation on every [Seed] call. 115 func touchMemory(memory *ScratchBuffer, idx uint32) { 116 idx = idx / 4 * 4 // align to 32 bits 117 u32 := (*uint32)(unsafe.Pointer(&memory[idx])) 118 last := atomic.LoadUint32(u32) 119 atomic.SwapUint32(u32, last+13) 120 } 121 122 func (s *source) Sample() uint8 { 123 // Perform a few memory accesses in an unpredictable pattern to expose the 124 // next measurement to as much system noise as possible. 125 memory, lcgState := s.memory, s.lcgState 126 if memory == nil { // remove the nil check from the inlined touchMemory calls 127 panic("entropy: nil memory buffer") 128 } 129 for range 64 { 130 lcgState = 1664525*lcgState + 1013904223 131 // Discard the lower bits, which tend to fall into short cycles. 132 idx := (lcgState >> 6) & (1<<25 - 1) 133 touchMemory(memory, idx) 134 } 135 s.lcgState = lcgState 136 137 t := time.HighPrecisionNow() 138 sample := t - s.previous 139 s.previous = t 140 141 // Reduce the symbol space to 256 values, assuming most of the entropy is in 142 // the least-significant bits, which represent the highest-resolution timing 143 // differences. 144 return uint8(sample) 145 } 146 147 // RepetitionCountTest implements the repetition count test from SP 800-90B 148 // Section 4.4.1. It returns an error if any symbol is repeated C = 41 or more 149 // times in a row. 150 // 151 // This C value is calculated from a target failure probability α = 2⁻²⁰ and a 152 // claimed min-entropy per symbol h = 0.5 bits, using the formula in SP 800-90B 153 // Section 4.4.1. 154 // 155 // sage: α = 2^-20 156 // sage: H = 0.5 157 // sage: 1 + ceil(-log(α, 2) / H) 158 // 41 159 func RepetitionCountTest(samples []uint8) error { 160 x := samples[0] 161 count := 1 162 for _, y := range samples[1:] { 163 if y == x { 164 count++ 165 if count >= 41 { 166 return errors.New("entropy: repetition count health test failed") 167 } 168 } else { 169 x = y 170 count = 1 171 } 172 } 173 return nil 174 } 175 176 // AdaptiveProportionTest implements the adaptive proportion test from SP 800-90B 177 // Section 4.4.2. It returns an error if any symbol appears C = 410 or more 178 // times in the last W = 512 samples. 179 // 180 // This C value is calculated from a target failure probability α = 2⁻²⁰, a 181 // window size W = 512, and a claimed min-entropy per symbol h = 0.5 bits, using 182 // the formula in SP 800-90B Section 4.4.2, equivalent to the Microsoft Excel 183 // formula 1+CRITBINOM(W, power(2,(−H)),1−α). 184 // 185 // sage: from scipy.stats import binom 186 // sage: α = 2^-20 187 // sage: H = 0.5 188 // sage: W = 512 189 // sage: C = 1 + binom.ppf(1 - α, W, 2**(-H)) 190 // sage: ceil(C) 191 // 410 192 func AdaptiveProportionTest(samples []uint8) error { 193 var counts [256]int 194 for i, x := range samples { 195 counts[x]++ 196 if i >= 512 { 197 counts[samples[i-512]]-- 198 } 199 if counts[x] >= 410 { 200 return errors.New("entropy: adaptive proportion health test failed") 201 } 202 } 203 return nil 204 } 205