diff --git a/crypto/random/doc.go b/crypto/random/doc.go new file mode 100644 index 0000000..b1fa573 --- /dev/null +++ b/crypto/random/doc.go @@ -0,0 +1,7 @@ +// Package random provides a feedable CSPRNG. +// +// CSPRNG used is fortuna: github.com/seehuhn/fortuna +// By default the CSPRNG is fed by two sources: +// - OS RNG +// - Entropy gathered by context switching +package random diff --git a/crypto/random/entropy.go b/crypto/random/entropy.go new file mode 100644 index 0000000..ce9e58b --- /dev/null +++ b/crypto/random/entropy.go @@ -0,0 +1,133 @@ +package random + +import ( + "encoding/binary" + + "github.com/tevino/abool" + + "github.com/Safing/portbase/config" + "github.com/Safing/portbase/container" +) + +var ( + rngFeeder = make(chan []byte, 0) + minFeedEntropy config.IntOption +) + +func init() { + config.Register(&config.Option{ + Name: "Minimum Feed Entropy", + Key: "random.min_feed_entropy", + Description: "The minimum amount of entropy before a entropy source is feed to the RNG, in bits.", + ExpertiseLevel: config.ExpertiseLevelDeveloper, + OptType: config.OptTypeInt, + DefaultValue: 256, + ValidationRegex: "^[0-9]{3,5}$", + }) + minFeedEntropy = config.GetAsInt("random.min_feed_entropy", 256) +} + +// The Feeder is used to feed entropy to the RNG. +type Feeder struct { + input chan *entropyData + entropy int64 + needsEntropy *abool.AtomicBool + buffer *container.Container +} + +type entropyData struct { + data []byte + entropy int +} + +// NewFeeder returns a new entropy Feeder. +func NewFeeder() *Feeder { + new := &Feeder{ + input: make(chan *entropyData, 0), + needsEntropy: abool.NewBool(true), + buffer: container.New(), + } + go new.run() + return new +} + +// NeedsEntropy returns whether the feeder is currently gathering entropy. +func (f *Feeder) NeedsEntropy() bool { + return f.needsEntropy.IsSet() +} + +// SupplyEntropy supplies entropy to to the Feeder, it will block until the Feeder has read from it. +func (f *Feeder) SupplyEntropy(data []byte, entropy int) { + f.input <- &entropyData{ + data: data, + entropy: entropy, + } +} + +// SupplyEntropyIfNeeded supplies entropy to to the Feeder, but will not block if no entropy is currently needed. +func (f *Feeder) SupplyEntropyIfNeeded(data []byte, entropy int) { + if f.needsEntropy.IsSet() { + return + } + + select { + case f.input <- &entropyData{ + data: data, + entropy: entropy, + }: + default: + } +} + +// SupplyEntropyAsInt supplies entropy to to the Feeder, it will block until the Feeder has read from it. +func (f *Feeder) SupplyEntropyAsInt(n int64, entropy int) { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(n)) + f.SupplyEntropy(b, entropy) +} + +// SupplyEntropyAsIntIfNeeded supplies entropy to to the Feeder, but will not block if no entropy is currently needed. +func (f *Feeder) SupplyEntropyAsIntIfNeeded(n int64, entropy int) { + if f.needsEntropy.IsSet() { // avoid allocating a slice if possible + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(n)) + f.SupplyEntropyIfNeeded(b, entropy) + } +} + +// CloseFeeder stops the feed processing - the responsible goroutine exits. +func (f *Feeder) CloseFeeder() { + f.input <- nil +} + +func (f *Feeder) run() { + defer f.needsEntropy.UnSet() + + for { + // gather + f.needsEntropy.Set() + gather: + for { + select { + case newEntropy := <-f.input: + if newEntropy != nil { + f.buffer.Append(newEntropy.data) + f.entropy += int64(newEntropy.entropy) + if f.entropy >= minFeedEntropy() { + break gather + } + } + case <-shutdownSignal: + return + } + } + // feed + f.needsEntropy.UnSet() + select { + case rngFeeder <- f.buffer.CompileData(): + case <-shutdownSignal: + return + } + f.buffer = container.New() + } +} diff --git a/crypto/random/entropy_test.go b/crypto/random/entropy_test.go new file mode 100644 index 0000000..1bde202 --- /dev/null +++ b/crypto/random/entropy_test.go @@ -0,0 +1,73 @@ +package random + +import ( + "testing" + "time" +) + +func TestFeeder(t *testing.T) { + + // wait for start / first round to complete + time.Sleep(1 * time.Millisecond) + + f := NewFeeder() + + // go through all functions + f.NeedsEntropy() + f.SupplyEntropy([]byte{0}, 0) + f.SupplyEntropyAsInt(0, 0) + f.SupplyEntropyIfNeeded([]byte{0}, 0) + f.SupplyEntropyAsIntIfNeeded(0, 0) + + // fill entropy + f.SupplyEntropyAsInt(0, 65535) + + // check blocking calls + + waitC := make(chan struct{}) + go func() { + f.SupplyEntropy([]byte{0}, 0) + close(waitC) + }() + select { + case <-waitC: + t.Error("call does not block!") + case <-time.After(10 * time.Millisecond): + } + + waitC = make(chan struct{}) + go func() { + f.SupplyEntropyAsInt(0, 0) + close(waitC) + }() + select { + case <-waitC: + t.Error("call does not block!") + case <-time.After(10 * time.Millisecond): + } + + // check non-blocking calls + + waitC = make(chan struct{}) + go func() { + f.SupplyEntropyIfNeeded([]byte{0}, 0) + close(waitC) + }() + select { + case <-waitC: + case <-time.After(10 * time.Millisecond): + t.Error("call blocks!") + } + + waitC = make(chan struct{}) + go func() { + f.SupplyEntropyAsIntIfNeeded(0, 0) + close(waitC) + }() + select { + case <-waitC: + case <-time.After(10 * time.Millisecond): + t.Error("call blocks!") + } + +} diff --git a/crypto/random/fullfeed.go b/crypto/random/fullfeed.go new file mode 100644 index 0000000..4ff4dcf --- /dev/null +++ b/crypto/random/fullfeed.go @@ -0,0 +1,49 @@ +package random + +import ( + "time" +) + +var ( + fullFeedDuration = 1 * time.Millisecond +) + +func getFullFeedDuration() time.Duration { + + // full feed every 5x time of reseedAfterSeconds + secsUntilFullFeed := reseedAfterSeconds() * 5 + + // full feed at most once per minute + if secsUntilFullFeed < 60 { + secsUntilFullFeed = 60 + } + + return time.Duration(secsUntilFullFeed * int64(time.Second)) +} + +func fullFeeder() { + for { + + select { + case <-time.After(fullFeedDuration): + + rngLock.Lock() + feedAll: + for { + select { + case data := <-rngFeeder: + rng.Reseed(data) + default: + break feedAll + } + } + rngLock.Unlock() + + case <-shutdownSignal: + return + } + + fullFeedDuration = getFullFeedDuration() + + } +} diff --git a/crypto/random/fullfeed_test.go b/crypto/random/fullfeed_test.go new file mode 100644 index 0000000..bc32d40 --- /dev/null +++ b/crypto/random/fullfeed_test.go @@ -0,0 +1,13 @@ +package random + +import ( + "testing" +) + +func TestFullFeeder(t *testing.T) { + for i := 0; i < 10; i++ { + go func() { + rngFeeder <- []byte{0} + }() + } +} diff --git a/crypto/random/get.go b/crypto/random/get.go new file mode 100644 index 0000000..693edec --- /dev/null +++ b/crypto/random/get.go @@ -0,0 +1,96 @@ +package random + +import ( + "errors" + "io" + "time" + + "github.com/Safing/portbase/config" +) + +var ( + // Reader provides a global instance to read from the RNG. + Reader io.Reader + + rngBytesRead int64 + rngLastFeed = time.Now() + + reseedAfterSeconds config.IntOption + reseedAfterBytes config.IntOption +) + +// reader provides an io.Reader interface +type reader struct{} + +func init() { + config.Register(&config.Option{ + Name: "Reseed after x seconds", + Key: "random.reseed_after_seconds", + Description: "Number of seconds until reseed", + ExpertiseLevel: config.ExpertiseLevelDeveloper, + OptType: config.OptTypeInt, + DefaultValue: 360, // ten minutes + ValidationRegex: "^[0-9]{2,5}$", + }) + reseedAfterSeconds = config.GetAsInt("random.reseed_after_seconds", 360) + + config.Register(&config.Option{ + Name: "Reseed after x bytes", + Key: "random.reseed_after_bytes", + Description: "Number of fetched bytes until reseed", + ExpertiseLevel: config.ExpertiseLevelDeveloper, + OptType: config.OptTypeInt, + DefaultValue: 1000000, // one megabyte + ValidationRegex: "^[0-9]{0,9}$", + }) + reseedAfterBytes = config.GetAsInt("random.reseed_after_bytes", 1000000) + + Reader = reader{} +} + +func checkEntropy() (err error) { + if !rngReady { + return errors.New("RNG is not ready yet") + } + if rngBytesRead > reseedAfterBytes() || + int64(time.Now().Sub(rngLastFeed).Seconds()) > reseedAfterSeconds() { + select { + case r := <-rngFeeder: + rng.Reseed(r) + rngBytesRead = 0 + rngLastFeed = time.Now() + case <-time.After(1*time.Second): + return errors.New("failed to get new entropy") + } + } + return nil +} + +// Read reads random bytes into the supplied byte slice. +func Read(b []byte) (n int, err error) { + rngLock.Lock() + defer rngLock.Unlock() + + if err := checkEntropy(); err != nil { + return 0, err + } + + return copy(b, rng.PseudoRandomData(uint(len(b)))), nil +} + +// Read implements the io.Reader interface +func (r reader) Read(b []byte) (n int, err error) { + return Read(b) +} + +// Bytes allocates a new byte slice of given length and fills it with random data. +func Bytes(n int) ([]byte, error) { + rngLock.Lock() + defer rngLock.Unlock() + + if err := checkEntropy(); err != nil { + return nil, err + } + + return rng.PseudoRandomData(uint(n)), nil +} diff --git a/crypto/random/osfeeder.go b/crypto/random/osfeeder.go new file mode 100644 index 0000000..aed0ea0 --- /dev/null +++ b/crypto/random/osfeeder.go @@ -0,0 +1,35 @@ +package random + +import ( + "crypto/rand" + "time" + + "github.com/Safing/portbase/log" +) + +func osFeeder() { + feeder := NewFeeder() + for { + + // get feed entropy + minEntropyBytes := int(minFeedEntropy())/8 + 1 + if minEntropyBytes < 32 { + minEntropyBytes = 64 + } + + // get entropy + osEntropy := make([]byte, minEntropyBytes) + n, err := rand.Read(osEntropy) + if err != nil { + log.Errorf("could not read entropy from os: %s", err) + time.Sleep(10 * time.Second) + } + if n != minEntropyBytes { + log.Errorf("could not read enough entropy from os: got only %d bytes instead of %d", n, minEntropyBytes) + time.Sleep(10 * time.Second) + } + + // feed + feeder.SupplyEntropy(osEntropy, minEntropyBytes*8) + } +} diff --git a/crypto/random/random.go b/crypto/random/random.go deleted file mode 100644 index d1bec28..0000000 --- a/crypto/random/random.go +++ /dev/null @@ -1,37 +0,0 @@ -package random - -import ( - "crypto/rand" - "fmt" - "io" - "math/big" -) - -// just (mostly) a proxy for now, awesome stuff comes later - -func Int(randSrc io.Reader, max *big.Int) (n *big.Int, err error) { - return rand.Int(randSrc, max) -} - -func Prime(randSrc io.Reader, bits int) (p *big.Int, err error) { - return rand.Prime(randSrc, bits) -} - -func Read(b []byte) (n int, err error) { - return rand.Read(b) -} - -func Bytes(len int) ([]byte, error) { - r := make([]byte, len) - _, err := Read(r) - if err != nil { - return nil, fmt.Errorf("failed to get random data: %s", err) - } - return r, nil -} - -type Reader struct{} - -func (r Reader) Read(b []byte) (n int, err error) { - return Read(b) -} diff --git a/crypto/random/rng.go b/crypto/random/rng.go new file mode 100644 index 0000000..5e3629b --- /dev/null +++ b/crypto/random/rng.go @@ -0,0 +1,78 @@ +package random + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" + "sync" + + "github.com/aead/serpent" + "github.com/seehuhn/fortuna" + + "github.com/Safing/portbase/config" + "github.com/Safing/portbase/modules" +) + +var ( + rng *fortuna.Generator + rngLock sync.Mutex + rngReady = false + rngCipherOption config.StringOption + + shutdownSignal = make(chan struct{}, 0) +) + +func init() { + modules.Register("random", prep, Start, stop) + + config.Register(&config.Option{ + Name: "RNG Cipher", + Key: "random.rng_cipher", + Description: "Cipher to use for the Fortuna RNG. Requires restart to take effect.", + ExpertiseLevel: config.ExpertiseLevelDeveloper, + OptType: config.OptTypeString, + DefaultValue: "aes", + ValidationRegex: "^(aes|serpent)$", + }) + rngCipherOption = config.GetAsString("random.rng_cipher", "aes") +} + +func prep() error { + return nil +} + +func newCipher(key []byte) (cipher.Block, error) { + cipher := rngCipherOption() + switch cipher { + case "aes": + return aes.NewCipher(key) + case "serpent": + return serpent.NewCipher(key) + default: + return nil, fmt.Errorf("unknown or unsupported cipher: %s", cipher) + } +} + +// Start starts the RNG. Normally, this should be only called by the portbase/modules package. +func Start() (err error) { + rngLock.Lock() + defer rngLock.Unlock() + + rng = fortuna.NewGenerator(newCipher) + rngReady = true + + // random source: OS + go osFeeder() + + // random source: goroutine ticks + go tickFeeder() + + // full feeder + go fullFeeder() + + return nil +} + +func stop() error { + return nil +} diff --git a/crypto/random/rng_test.go b/crypto/random/rng_test.go new file mode 100644 index 0000000..371ef93 --- /dev/null +++ b/crypto/random/rng_test.go @@ -0,0 +1,45 @@ +package random + +import ( + "testing" + + "github.com/Safing/portbase/config" +) + +func init() { + prep() + Start() +} + +func TestRNG(t *testing.T) { + key := make([]byte, 16) + + config.SetConfigOption("random.rng_cipher", "aes") + _, err := newCipher(key) + if err != nil { + t.Errorf("failed to create aes cipher: %s", err) + } + rng.Reseed(key) + + config.SetConfigOption("random.rng_cipher", "serpent") + _, err = newCipher(key) + if err != nil { + t.Errorf("failed to create serpent cipher: %s", err) + } + rng.Reseed(key) + + b := make([]byte, 32) + _, err = Read(b) + if err != nil { + t.Errorf("Read failed: %s", err) + } + _, err = Reader.Read(b) + if err != nil { + t.Errorf("Read failed: %s", err) + } + + _, err = Bytes(32) + if err != nil { + t.Errorf("Bytes failed: %s", err) + } +} diff --git a/crypto/random/test/.gitignore b/crypto/random/test/.gitignore new file mode 100644 index 0000000..5746850 --- /dev/null +++ b/crypto/random/test/.gitignore @@ -0,0 +1,3 @@ +test +*.bin +*.txt diff --git a/crypto/random/test/README.md b/crypto/random/test/README.md new file mode 100644 index 0000000..e7becff --- /dev/null +++ b/crypto/random/test/README.md @@ -0,0 +1,280 @@ +# Entropy Testing + +In order verify that the random package actually generates random enough entropy/data, this test program holds the core functions that generate entropy as well as some noise makers to simulate a running program. + +Please also note that output from `tickFeeder` is never used directly but fed as entropy to the actual RNG - `fortuna`. + +With `tickFeeder`, to be sure that the delivered entropy is of high enough quality, only 1 bit of entropy is expected per generated byte - ie. we gather 8 times the amount we need. The following test below is run on the raw output. + +To test the quality of entropy, first generate random data with the test program: + + go build + + ./test tickfeeder > output.bin # just the additional entropy feed + # OR + ./test fortuna > output.bin # the actual CSPRNG + + ls -lah output.bin # check filesize: should be ~1MB + +Then, run `dieharder`, a random number generator test tool: + + dieharder -a -f output.bin + +Below you can find two test outputs of `dieharder`. +Please note that around 5 tests of `dieharder` normally fail. This is expected and even desired. + +`dieharder` output of 22KB of contextswitch (`go version go1.10.3 linux/amd64` on 23.08.2018): + + #=============================================================================# + # dieharder version 3.31.1 Copyright 2003 Robert G. Brown # + #=============================================================================# + rng_name | filename |rands/second| + mt19937| output.bin| 1.00e+08 | + #=============================================================================# + test_name |ntup| tsamples |psamples| p-value |Assessment + #=============================================================================# + diehard_birthdays| 0| 100| 100|0.75124818| PASSED + diehard_operm5| 0| 1000000| 100|0.71642114| PASSED + diehard_rank_32x32| 0| 40000| 100|0.66406749| PASSED + diehard_rank_6x8| 0| 100000| 100|0.79742497| PASSED + diehard_bitstream| 0| 2097152| 100|0.68336079| PASSED + diehard_opso| 0| 2097152| 100|0.99670345| WEAK + diehard_oqso| 0| 2097152| 100|0.85930861| PASSED + diehard_dna| 0| 2097152| 100|0.77857540| PASSED + diehard_count_1s_str| 0| 256000| 100|0.27851730| PASSED + diehard_count_1s_byt| 0| 256000| 100|0.29570009| PASSED + diehard_parking_lot| 0| 12000| 100|0.51526020| PASSED + diehard_2dsphere| 2| 8000| 100|0.49199324| PASSED + diehard_3dsphere| 3| 4000| 100|0.99008122| PASSED + diehard_squeeze| 0| 100000| 100|0.95518110| PASSED + diehard_sums| 0| 100| 100|0.00015930| WEAK + diehard_runs| 0| 100000| 100|0.50091086| PASSED + diehard_runs| 0| 100000| 100|0.44091340| PASSED + diehard_craps| 0| 200000| 100|0.77284264| PASSED + diehard_craps| 0| 200000| 100|0.71027434| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.38138922| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.36661590| PASSED + sts_monobit| 1| 100000| 100|0.06209802| PASSED + sts_runs| 2| 100000| 100|0.82506539| PASSED + sts_serial| 1| 100000| 100|0.99198615| PASSED + sts_serial| 2| 100000| 100|0.85604831| PASSED + sts_serial| 3| 100000| 100|0.06613657| PASSED + sts_serial| 3| 100000| 100|0.16787860| PASSED + sts_serial| 4| 100000| 100|0.45227401| PASSED + sts_serial| 4| 100000| 100|0.43529092| PASSED + sts_serial| 5| 100000| 100|0.99912474| WEAK + sts_serial| 5| 100000| 100|0.94754128| PASSED + sts_serial| 6| 100000| 100|0.98406523| PASSED + sts_serial| 6| 100000| 100|0.92895983| PASSED + sts_serial| 7| 100000| 100|0.45965410| PASSED + sts_serial| 7| 100000| 100|0.64185152| PASSED + sts_serial| 8| 100000| 100|0.57922926| PASSED + sts_serial| 8| 100000| 100|0.52390292| PASSED + sts_serial| 9| 100000| 100|0.82722325| PASSED + sts_serial| 9| 100000| 100|0.89384819| PASSED + sts_serial| 10| 100000| 100|0.79877889| PASSED + sts_serial| 10| 100000| 100|0.49562348| PASSED + sts_serial| 11| 100000| 100|0.09217966| PASSED + sts_serial| 11| 100000| 100|0.00342361| WEAK + sts_serial| 12| 100000| 100|0.60119444| PASSED + sts_serial| 12| 100000| 100|0.20420318| PASSED + sts_serial| 13| 100000| 100|0.76867489| PASSED + sts_serial| 13| 100000| 100|0.35717970| PASSED + sts_serial| 14| 100000| 100|0.67364089| PASSED + sts_serial| 14| 100000| 100|0.98667204| PASSED + sts_serial| 15| 100000| 100|0.24328833| PASSED + sts_serial| 15| 100000| 100|0.52098866| PASSED + sts_serial| 16| 100000| 100|0.48845863| PASSED + sts_serial| 16| 100000| 100|0.61943558| PASSED + rgb_bitdist| 1| 100000| 100|0.24694812| PASSED + rgb_bitdist| 2| 100000| 100|0.75873723| PASSED + rgb_bitdist| 3| 100000| 100|0.28670990| PASSED + rgb_bitdist| 4| 100000| 100|0.41966273| PASSED + rgb_bitdist| 5| 100000| 100|0.80463973| PASSED + rgb_bitdist| 6| 100000| 100|0.44747725| PASSED + rgb_bitdist| 7| 100000| 100|0.35848420| PASSED + rgb_bitdist| 8| 100000| 100|0.56585089| PASSED + rgb_bitdist| 9| 100000| 100|0.23179559| PASSED + rgb_bitdist| 10| 100000| 100|0.83369283| PASSED + rgb_bitdist| 11| 100000| 100|0.74761235| PASSED + rgb_bitdist| 12| 100000| 100|0.50477673| PASSED + rgb_minimum_distance| 2| 10000| 1000|0.29527530| PASSED + rgb_minimum_distance| 3| 10000| 1000|0.83681186| PASSED + rgb_minimum_distance| 4| 10000| 1000|0.85939646| PASSED + rgb_minimum_distance| 5| 10000| 1000|0.90229335| PASSED + rgb_permutations| 2| 100000| 100|0.99010460| PASSED + rgb_permutations| 3| 100000| 100|0.99360922| PASSED + rgb_permutations| 4| 100000| 100|0.30113906| PASSED + rgb_permutations| 5| 100000| 100|0.60701235| PASSED + rgb_lagged_sum| 0| 1000000| 100|0.37080580| PASSED + rgb_lagged_sum| 1| 1000000| 100|0.91852932| PASSED + rgb_lagged_sum| 2| 1000000| 100|0.74568323| PASSED + rgb_lagged_sum| 3| 1000000| 100|0.64070201| PASSED + rgb_lagged_sum| 4| 1000000| 100|0.53802729| PASSED + rgb_lagged_sum| 5| 1000000| 100|0.67865656| PASSED + rgb_lagged_sum| 6| 1000000| 100|0.85161494| PASSED + rgb_lagged_sum| 7| 1000000| 100|0.37312323| PASSED + rgb_lagged_sum| 8| 1000000| 100|0.17841759| PASSED + rgb_lagged_sum| 9| 1000000| 100|0.85795513| PASSED + rgb_lagged_sum| 10| 1000000| 100|0.79843176| PASSED + rgb_lagged_sum| 11| 1000000| 100|0.21320830| PASSED + rgb_lagged_sum| 12| 1000000| 100|0.94709672| PASSED + rgb_lagged_sum| 13| 1000000| 100|0.12600611| PASSED + rgb_lagged_sum| 14| 1000000| 100|0.26780352| PASSED + rgb_lagged_sum| 15| 1000000| 100|0.07862730| PASSED + rgb_lagged_sum| 16| 1000000| 100|0.21102254| PASSED + rgb_lagged_sum| 17| 1000000| 100|0.82967141| PASSED + rgb_lagged_sum| 18| 1000000| 100|0.05818566| PASSED + rgb_lagged_sum| 19| 1000000| 100|0.01010140| PASSED + rgb_lagged_sum| 20| 1000000| 100|0.17941782| PASSED + rgb_lagged_sum| 21| 1000000| 100|0.98442639| PASSED + rgb_lagged_sum| 22| 1000000| 100|0.30352772| PASSED + rgb_lagged_sum| 23| 1000000| 100|0.56855155| PASSED + rgb_lagged_sum| 24| 1000000| 100|0.27280405| PASSED + rgb_lagged_sum| 25| 1000000| 100|0.41141889| PASSED + rgb_lagged_sum| 26| 1000000| 100|0.25389013| PASSED + rgb_lagged_sum| 27| 1000000| 100|0.10313177| PASSED + rgb_lagged_sum| 28| 1000000| 100|0.76610028| PASSED + rgb_lagged_sum| 29| 1000000| 100|0.97903830| PASSED + rgb_lagged_sum| 30| 1000000| 100|0.51216732| PASSED + rgb_lagged_sum| 31| 1000000| 100|0.98578832| PASSED + rgb_lagged_sum| 32| 1000000| 100|0.95078719| PASSED + rgb_kstest_test| 0| 10000| 1000|0.24930712| PASSED + dab_bytedistrib| 0| 51200000| 1|0.51100031| PASSED + dab_dct| 256| 50000| 1|0.28794956| PASSED + Preparing to run test 207. ntuple = 0 + dab_filltree| 32| 15000000| 1|0.93283449| PASSED + dab_filltree| 32| 15000000| 1|0.36488075| PASSED + Preparing to run test 208. ntuple = 0 + dab_filltree2| 0| 5000000| 1|0.94036105| PASSED + dab_filltree2| 1| 5000000| 1|0.30118240| PASSED + Preparing to run test 209. ntuple = 0 + dab_monobit2| 12| 65000000| 1|0.00209003| WEAK + +`dieharder` of 1MB of fortuna (`go version go1.10.3 linux/amd64` on 23.08.2018): + + #=============================================================================# + # dieharder version 3.31.1 Copyright 2003 Robert G. Brown # + #=============================================================================# + rng_name | filename |rands/second| + mt19937| output.bin| 8.44e+07 | + #=============================================================================# + test_name |ntup| tsamples |psamples| p-value |Assessment + #=============================================================================# + diehard_birthdays| 0| 100| 100|0.94302153| PASSED + diehard_operm5| 0| 1000000| 100|0.08378380| PASSED + diehard_rank_32x32| 0| 40000| 100|0.02062049| PASSED + diehard_rank_6x8| 0| 100000| 100|0.43787871| PASSED + diehard_bitstream| 0| 2097152| 100|0.15713023| PASSED + diehard_opso| 0| 2097152| 100|0.79331996| PASSED + diehard_oqso| 0| 2097152| 100|0.54138750| PASSED + diehard_dna| 0| 2097152| 100|0.06957205| PASSED + diehard_count_1s_str| 0| 256000| 100|0.21653644| PASSED + diehard_count_1s_byt| 0| 256000| 100|0.96539542| PASSED + diehard_parking_lot| 0| 12000| 100|0.21306362| PASSED + diehard_2dsphere| 2| 8000| 100|0.40750466| PASSED + diehard_3dsphere| 3| 4000| 100|0.99827314| WEAK + diehard_squeeze| 0| 100000| 100|0.70994607| PASSED + diehard_sums| 0| 100| 100|0.42729005| PASSED + diehard_runs| 0| 100000| 100|0.08118125| PASSED + diehard_runs| 0| 100000| 100|0.99226204| PASSED + diehard_craps| 0| 200000| 100|0.49803401| PASSED + diehard_craps| 0| 200000| 100|0.84011191| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.40135552| PASSED + marsaglia_tsang_gcd| 0| 10000000| 100|0.53311975| PASSED + sts_monobit| 1| 100000| 100|0.96903259| PASSED + sts_runs| 2| 100000| 100|0.55734041| PASSED + sts_serial| 1| 100000| 100|0.69041819| PASSED + sts_serial| 2| 100000| 100|0.61728694| PASSED + sts_serial| 3| 100000| 100|0.70299864| PASSED + sts_serial| 3| 100000| 100|0.36332027| PASSED + sts_serial| 4| 100000| 100|0.57627216| PASSED + sts_serial| 4| 100000| 100|0.95046929| PASSED + sts_serial| 5| 100000| 100|0.79824554| PASSED + sts_serial| 5| 100000| 100|0.62786166| PASSED + sts_serial| 6| 100000| 100|0.84103529| PASSED + sts_serial| 6| 100000| 100|0.89083859| PASSED + sts_serial| 7| 100000| 100|0.69686380| PASSED + sts_serial| 7| 100000| 100|0.79436099| PASSED + sts_serial| 8| 100000| 100|0.84082295| PASSED + sts_serial| 8| 100000| 100|0.95915719| PASSED + sts_serial| 9| 100000| 100|0.48200567| PASSED + sts_serial| 9| 100000| 100|0.10836112| PASSED + sts_serial| 10| 100000| 100|0.45470523| PASSED + sts_serial| 10| 100000| 100|0.97608829| PASSED + sts_serial| 11| 100000| 100|0.89344380| PASSED + sts_serial| 11| 100000| 100|0.31959825| PASSED + sts_serial| 12| 100000| 100|0.43415812| PASSED + sts_serial| 12| 100000| 100|0.27845148| PASSED + sts_serial| 13| 100000| 100|0.50590833| PASSED + sts_serial| 13| 100000| 100|0.39585514| PASSED + sts_serial| 14| 100000| 100|0.55566778| PASSED + sts_serial| 14| 100000| 100|0.57138798| PASSED + sts_serial| 15| 100000| 100|0.12315118| PASSED + sts_serial| 15| 100000| 100|0.41728831| PASSED + sts_serial| 16| 100000| 100|0.23202389| PASSED + sts_serial| 16| 100000| 100|0.84883373| PASSED + rgb_bitdist| 1| 100000| 100|0.45137388| PASSED + rgb_bitdist| 2| 100000| 100|0.93984739| PASSED + rgb_bitdist| 3| 100000| 100|0.85148557| PASSED + rgb_bitdist| 4| 100000| 100|0.77062397| PASSED + rgb_bitdist| 5| 100000| 100|0.79511260| PASSED + rgb_bitdist| 6| 100000| 100|0.86150140| PASSED + rgb_bitdist| 7| 100000| 100|0.98572979| PASSED + rgb_bitdist| 8| 100000| 100|0.73302973| PASSED + rgb_bitdist| 9| 100000| 100|0.39660028| PASSED + rgb_bitdist| 10| 100000| 100|0.13167592| PASSED + rgb_bitdist| 11| 100000| 100|0.87937846| PASSED + rgb_bitdist| 12| 100000| 100|0.80619403| PASSED + rgb_minimum_distance| 2| 10000| 1000|0.38189429| PASSED + rgb_minimum_distance| 3| 10000| 1000|0.21164619| PASSED + rgb_minimum_distance| 4| 10000| 1000|0.91875064| PASSED + rgb_minimum_distance| 5| 10000| 1000|0.27897081| PASSED + rgb_permutations| 2| 100000| 100|0.22927506| PASSED + rgb_permutations| 3| 100000| 100|0.80827585| PASSED + rgb_permutations| 4| 100000| 100|0.38750474| PASSED + rgb_permutations| 5| 100000| 100|0.18938169| PASSED + rgb_lagged_sum| 0| 1000000| 100|0.72234187| PASSED + rgb_lagged_sum| 1| 1000000| 100|0.28633796| PASSED + rgb_lagged_sum| 2| 1000000| 100|0.52961866| PASSED + rgb_lagged_sum| 3| 1000000| 100|0.99876080| WEAK + rgb_lagged_sum| 4| 1000000| 100|0.39603203| PASSED + rgb_lagged_sum| 5| 1000000| 100|0.01004618| PASSED + rgb_lagged_sum| 6| 1000000| 100|0.89539065| PASSED + rgb_lagged_sum| 7| 1000000| 100|0.55558774| PASSED + rgb_lagged_sum| 8| 1000000| 100|0.40063365| PASSED + rgb_lagged_sum| 9| 1000000| 100|0.30905028| PASSED + rgb_lagged_sum| 10| 1000000| 100|0.31161899| PASSED + rgb_lagged_sum| 11| 1000000| 100|0.76729775| PASSED + rgb_lagged_sum| 12| 1000000| 100|0.36416009| PASSED + rgb_lagged_sum| 13| 1000000| 100|0.21062168| PASSED + rgb_lagged_sum| 14| 1000000| 100|0.17580591| PASSED + rgb_lagged_sum| 15| 1000000| 100|0.54465457| PASSED + rgb_lagged_sum| 16| 1000000| 100|0.39394806| PASSED + rgb_lagged_sum| 17| 1000000| 100|0.81572681| PASSED + rgb_lagged_sum| 18| 1000000| 100|0.98821505| PASSED + rgb_lagged_sum| 19| 1000000| 100|0.86755786| PASSED + rgb_lagged_sum| 20| 1000000| 100|0.37832948| PASSED + rgb_lagged_sum| 21| 1000000| 100|0.52001140| PASSED + rgb_lagged_sum| 22| 1000000| 100|0.83595676| PASSED + rgb_lagged_sum| 23| 1000000| 100|0.22643336| PASSED + rgb_lagged_sum| 24| 1000000| 100|0.96475696| PASSED + rgb_lagged_sum| 25| 1000000| 100|0.49570837| PASSED + rgb_lagged_sum| 26| 1000000| 100|0.71327165| PASSED + rgb_lagged_sum| 27| 1000000| 100|0.07344404| PASSED + rgb_lagged_sum| 28| 1000000| 100|0.86374872| PASSED + rgb_lagged_sum| 29| 1000000| 100|0.24892548| PASSED + rgb_lagged_sum| 30| 1000000| 100|0.14314375| PASSED + rgb_lagged_sum| 31| 1000000| 100|0.27884009| PASSED + rgb_lagged_sum| 32| 1000000| 100|0.66637341| PASSED + rgb_kstest_test| 0| 10000| 1000|0.13954587| PASSED + dab_bytedistrib| 0| 51200000| 1|0.54278716| PASSED + dab_dct| 256| 50000| 1|0.71177390| PASSED + Preparing to run test 207. ntuple = 0 + dab_filltree| 32| 15000000| 1|0.51006153| PASSED + dab_filltree| 32| 15000000| 1|0.91162889| PASSED + Preparing to run test 208. ntuple = 0 + dab_filltree2| 0| 5000000| 1|0.15507188| PASSED + dab_filltree2| 1| 5000000| 1|0.16787382| PASSED + Preparing to run test 209. ntuple = 0 + dab_monobit2| 12| 65000000| 1|0.28347219| PASSED diff --git a/crypto/random/test/main.go b/crypto/random/test/main.go new file mode 100644 index 0000000..3c3af2b --- /dev/null +++ b/crypto/random/test/main.go @@ -0,0 +1,121 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "os" + "runtime" + "time" + + "github.com/Safing/portbase/crypto/random" +) + +func noise() { + // do some aes ctr for noise + + key, _ := hex.DecodeString("6368616e676520746869732070617373") + data := []byte("some plaintext x") + + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } + + iv := make([]byte, aes.BlockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + panic(err) + } + + stream := cipher.NewCTR(block, iv) + for { + stream.XORKeyStream(data, data) + } + +} + +func main() { + // generates 1MB and writes to stdout + + runtime.GOMAXPROCS(1) + + if len(os.Args) < 2 { + fmt.Printf("usage: ./%s {fortuna|tickfeeder}\n", os.Args[0]) + os.Exit(1) + } + + os.Stderr.WriteString("writing 1MB to stdout, a \".\" will be printed at every 1024 bytes.\n") + + var bytesWritten int + + switch os.Args[1] { + case "fortuna": + + random.Start() + + for { + b, err := random.Bytes(64) + if err != nil { + panic(err) + } + os.Stdout.Write(b) + + bytesWritten += 64 + if bytesWritten%1024 == 0 { + os.Stderr.WriteString(".") + } + if bytesWritten%65536 == 0 { + fmt.Fprintf(os.Stderr, "\n%d bytes written\n", bytesWritten) + } + if bytesWritten >= 1000000 { + os.Stderr.WriteString("\n") + break + } + } + + os.Exit(0) + case "tickfeeder": + + go noise() + + var value int64 + var pushes int + + for { + time.Sleep(10 * time.Nanosecond) + + value = (value << 1) | (time.Now().UnixNano() % 2) + pushes++ + + if pushes >= 64 { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(value)) + // fmt.Fprintf(os.Stderr, "write: %d\n", value) + os.Stdout.Write(b) + bytesWritten += 8 + if bytesWritten%1024 == 0 { + os.Stderr.WriteString(".") + } + if bytesWritten%65536 == 0 { + fmt.Fprintf(os.Stderr, "\n%d bytes written\n", bytesWritten) + } + pushes = 0 + } + + if bytesWritten >= 1000000 { + os.Stderr.WriteString("\n") + break + } + } + + os.Exit(0) + default: + fmt.Printf("usage: %s {fortuna|tickfeeder}\n", os.Args[0]) + os.Exit(1) + } + +} diff --git a/crypto/random/tickfeeder.go b/crypto/random/tickfeeder.go new file mode 100644 index 0000000..8eb7f8f --- /dev/null +++ b/crypto/random/tickfeeder.go @@ -0,0 +1,61 @@ +package random + +import ( + "time" +) + +var ( + tickDuration = 1 * time.Millisecond +) + +func getTickDuration() time.Duration { + + // be ready in 1/10 time of reseedAfterSeconds + msecsAvailable := reseedAfterSeconds() * 100 + // ex.: reseed after 10 minutes: msecsAvailable = 36000 + // have full entropy after 5 minutes + + // one tick generates 0,125 bits of entropy + ticksNeeded := minFeedEntropy() * 8 + // ex.: minimum entropy is 256: ticksNeeded = 2048 + + // msces between ticks + tickMsecs := msecsAvailable / ticksNeeded + // ex.: tickMsecs = 17(,578125) + + // use a minimum of 10 msecs per tick for good entropy + // it would take 21 seconds to get full 256 bits of entropy with 10msec ticks + if tickMsecs < 10 { + tickMsecs = 10 + } + + return time.Duration(tickMsecs * int64(time.Millisecond)) +} + +// tickFeeder is a really simple entropy feeder that adds the least significant bit of the current nanosecond unixtime to its pool every time it 'ticks'. +// The more work the program does, the better the quality, as the internal schedular cannot immediately run the goroutine when it's ready. +func tickFeeder() { + + var value int64 + var pushes int + feeder := NewFeeder() + + for { + select { + case <-time.After(tickDuration): + + value = (value << 1) | (time.Now().UnixNano() % 2) + + pushes++ + if pushes >= 64 { + feeder.SupplyEntropyAsInt(value, 8) + pushes = 0 + } + + tickDuration = getTickDuration() + + case <-shutdownSignal: + return + } + } +}