180 lines
3.9 KiB
Go
180 lines
3.9 KiB
Go
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
|
||
}
|