Files
xgcl/grand/drng/entropy/entropy_pool.go
T
2026-05-27 23:03:00 +08:00

180 lines
3.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package entropy
import (
"crypto/rand"
"encoding/binary"
"errors"
"sync"
"time"
"xdx.jelly/xgcl/grand/drng/internal"
)
var OutOfEntropyErr = errors.New("out of entropy")
// Read reads at most 32 bytes into p.
// For efficiency, reads bytes are always multiples of 4.
func (e *EntropyPool) Read(p []byte) (int, error) {
entropyPool.mu.Lock()
if entropyPool.entropySize <= 0 {
return 0, OutOfEntropyErr
}
// n = min(8, entropyPool.entropySize, len(p) / 4)
n := 8
if entropyPool.entropySize < 8 {
n = entropyPool.entropySize
}
if (len(p) / 4) < n {
n = len(p) / 4
}
for i := 0; i < n; i++ {
x, _ := entropyPool.get() // err should not happen
binary.BigEndian.PutUint32(p[4*i:], x)
}
entropyPool.mu.Unlock()
go e.update() // update the entropy pool for next reading.
return 4 * n, nil
}
// EntropyPool 系统熵池
type EntropyPool struct {
mu sync.Mutex //使用互斥锁保证独占性
pool [128]uint32 //熵池,128个字(512字节)
// i - j 之间是未取的熵(mod 128)
i int //熵索引开始
j int //熵索引结束
entropySize int //熵池中可用熵值
source []EntropySource
}
// entropySource 熵源
type EntropySource interface {
GetEntropy(minEntropy int64, minEntropyInputLength int64, maxEntropyInputLength int64) ([]byte, error)
}
var entropyPool *EntropyPool
var initEntropyPool sync.Once
func init() {
initEntropyPool.Do(
func() {
entropyPool = newEntropyPool()
})
go func() {
// 定时从系统中获取
tickle := time.NewTicker(10 * time.Second)
for {
select {
case <-tickle.C:
entropyPool.update()
}
}
}()
}
func GetEntropyPool() *EntropyPool {
return entropyPool
}
func newEntropyPool() *EntropyPool {
// FIXME: SysTimeEntropySource
p := &EntropyPool{
source: []EntropySource{&OSEntropySource{}, &SysTimeEntropySource{}},
// source: []drng.EntropySource{&OSEntropySource{}},
}
buf := make([]byte, 128*4)
_, _ = rand.Reader.Read(buf)
for i := 0; i < 128; i++ {
p.pool[i] = binary.BigEndian.Uint32(buf[4*i:])
}
// 从每个熵源获取至少256比特
p.update()
// 对buf置0
for i := range buf {
buf[i] = 0
}
// for i := range e {
// e[i] = 0
// }
return p
}
func (e *EntropyPool) get() (uint32, error) {
if e.entropySize <= 0 {
return 0, OutOfEntropyErr
}
n := e.pool[e.i]
e.i = (e.i + 1) & 127
e.entropySize--
return n, nil
}
// lshr 添加新的熵数据g到熵池中。按照GM/T 0105 A.3的方式
func (e *EntropyPool) lshr(g uint32) {
var table = [8]uint32{0, 0x3b6e20c8, 0x76dc4190, 0x4db26158, 0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278}
temp := g ^ e.pool[e.j]
for _, i := range []int{1, 25, 51, 76, 103} {
temp ^= e.pool[(e.j+i)&127]
}
temp = (temp >> 3) ^ table[temp&7]
e.pool[e.j] = temp
e.j = (e.j + 1) & 127
// 置0
temp = 0
_ = temp
}
// Update 熵池更新
func (e *EntropyPool) update() {
e.mu.Lock()
defer e.mu.Unlock()
// 设置超时时间
tickle := time.NewTicker(100 * time.Millisecond)
defer tickle.Stop()
for i := 0; e.entropySize < 128; i = (i + 1) % len(e.source) {
select {
case <-tickle.C:
break
default:
s := e.source[i]
b, _ := s.GetEntropy(4, 4, internal.MaxEntropyInputLength)
if len(b) >= 4 {
e.lshr(binary.BigEndian.Uint32(b))
e.entropySize++
}
}
}
}
func (e *EntropyPool) GetEntropy(minEntropy int64, minEntropyInputLength int64, maxEntropyInputLength int64) ([]byte, error) {
e.mu.Lock()
defer e.mu.Unlock()
if minEntropyInputLength < internal.MinEntropyInputLength || maxEntropyInputLength > internal.MaxEntropyInputLength {
return nil, internal.ErrEntropyLength
}
entropy := make([]byte, minEntropyInputLength)
buf := make([]byte, 4)
var i int
for i = 0; int64(i) < minEntropyInputLength && e.entropySize > 0; i += 4 {
binary.BigEndian.PutUint32(buf, e.pool[e.i])
copy(entropy[i:], buf)
e.i = (e.i + 1) & 127
e.entropySize--
}
return entropy[:i], nil
}