// setInitialSeed sets the initial seed for the Generator. An // attempt is made to obtain seeds which differ between machines and // between reboots. To achieve this, the following information is // incorporated into the seed: the current time of day, account // information for the current user, and information about the // installed network interfaces. In addition, if available, random // bytes from the random number generator in the crypto/rand package // are used. func (gen *Generator) setInitialSeed() { // source 1: system random number generator buffer := make([]byte, keySize) n, _ := io.ReadFull(rand.Reader, buffer) if n > 0 { trace.T("fortuna/seed", trace.PrioInfo, "mixing %d bytes from crypto/rand into the seed", n) gen.Reseed(buffer) } // source 2: current time of day now := time.Now() trace.T("fortuna/seed", trace.PrioInfo, "mixing the current time into the seed") gen.Reseed(int64ToBytes(now.UnixNano())) // source 3: try different files with timer information, interrupt // counts, etc. for _, fname := range []string{"/proc/timer_list", "/proc/stat"} { buffer, _ = ioutil.ReadFile(fname) if len(buffer) > 0 { trace.T("fortuna/seed", trace.PrioInfo, "mixing %d bytes from %q into the seed", len(buffer), fname) gen.Reseed(buffer) } } // source 4: user name and login details user, _ := user.Current() if user != nil { trace.T("fortuna/seed", trace.PrioInfo, "mixing information about the current user into the seed") gen.Reseed([]byte(user.Uid)) gen.Reseed([]byte(user.Gid)) gen.Reseed([]byte(user.Username)) gen.Reseed([]byte(user.Name)) gen.Reseed([]byte(user.HomeDir)) } // source 5: network interfaces ifaces, _ := net.Interfaces() if ifaces != nil { trace.T("fortuna/seed", trace.PrioInfo, "mixing network interface information into the seed") for _, iface := range ifaces { gen.ReseedInt64(int64(iface.MTU)) gen.Reseed([]byte(iface.Name)) gen.Reseed(iface.HardwareAddr) gen.ReseedInt64(int64(iface.Flags)) } } }
func (acc *Accumulator) tryReseeding() []byte { now := time.Now() acc.poolMutex.Lock() defer acc.poolMutex.Unlock() if acc.poolZeroSize >= minPoolSize && now.After(acc.nextReseed) { acc.nextReseed = now.Add(minReseedInterval) acc.poolZeroSize = 0 acc.reseedCount++ seed := make([]byte, 0, numPools*sha256d.Size) pools := []string{} for i := uint(0); i < numPools; i++ { x := 1 << i if acc.reseedCount%x != 0 { break } seed = acc.pool[i].Sum(seed) acc.pool[i].Reset() pools = append(pools, strconv.Itoa(int(i))) } trace.T("fortuna/seed", trace.PrioInfo, "reseeding from pools %s", strings.Join(pools, " ")) return seed } return nil }
// NewEntropyTimeStampSink returns a channel through which timing data // can be submitted to the Accumulator's entropy pools. The current // time should be written to the returned channel regularly to add // entropy to the state of the random number generator. The submitted // times should be chosen such that they cannot be (completely) known // to an attacker. Typical sources of randomness include the arrival // times of network packets or the times of key-presses by the user. // // The channel can be closed by the caller to indicate that no more // entropy will be sent via this channel. func (acc *Accumulator) NewEntropyTimeStampSink() chan<- time.Time { source := acc.allocateSource() c := make(chan time.Time, channelBufferSize) acc.sources.Add(1) go func() { defer acc.sources.Done() seq := uint(0) lastRequest := time.Now() loop: for { select { case now, ok := <-c: if !ok { break loop } dt := now.Sub(lastRequest) lastRequest = now trace.T("fortuna/entropy", trace.PrioDebug, "adding time stamp data from source %d to pool %d", source, seq%numPools) acc.addRandomEvent(source, seq, int64ToBytes(int64(dt))) seq++ case <-acc.stopSources: break loop } } }() return c }
// Reseed uses the current generator state and the given seed value to // update the generator state. Care is taken to make sure that // knowledge of the new state after a reseed does not allow to // reconstruct previous output values of the generator. // // This is like the ReseedInt64() method, but the seed is given as a // byte slice instead of as an int64. func (gen *Generator) Reseed(seed []byte) { hash := sha256d.New() hash.Write(gen.key) hash.Write(seed) gen.setKey(hash.Sum(nil)) gen.inc() trace.T("fortuna/generator", trace.PrioVerbose, "seed updated") }
// Read and update the seed file. // // If the seed file is empty, reading the seed file is omitted. After // (potentially) reading the contents of the seed file, new seed data // is written to the file. In case the seed file is corrupted or has // insecure file permissions, an error is returned. func (acc *Accumulator) updateSeedFile() error { fi, err := acc.seedFile.Stat() if err != nil { return err } else if fi.Mode()&os.FileMode(0077) != 0 { trace.T("fortuna/seed", trace.PrioError, "seed file %q has insecure permissions, aborted", acc.seedFile.Name()) return ErrInsecureSeed } _, err = acc.seedFile.Seek(0, os.SEEK_SET) if err != nil { return err } acc.genMutex.Lock() // To prevent attacks we keep the PRNG locked until the new seed // file is safely written to disk. defer acc.genMutex.Unlock() n := fi.Size() if n == seedFileSize { seed := make([]byte, seedFileSize) _, err := io.ReadFull(acc.seedFile, seed) if err != nil || isZero(seed) { trace.T("fortuna/seed", trace.PrioError, "seed file %q is corrupted, not used: %s", acc.seedFile.Name(), err) return ErrCorruptedSeed } trace.T("fortuna/seed", trace.PrioInfo, "mixing %q into the seed", acc.seedFile.Name()) acc.gen.Reseed(seed) } else if n != 0 { trace.T("fortuna/seed", trace.PrioError, "seed file %q has invalid length %d, aborted", acc.seedFile.Name(), n) return ErrCorruptedSeed } seed := acc.randomDataUnlocked(seedFileSize) return doWriteSeed(acc.seedFile, seed) }
func main() { trace.Register(printTrace, "", trace.PrioDebug) rng, err := fortuna.NewRNG(seedFileName) if err != nil { panic("cannot initialise the RNG: " + err.Error()) } defer rng.Close() // entropy source 1: submit some randomness from crypto/rand once a minute go func() { sink1 := rng.NewEntropyDataSink() for _ = range time.Tick(time.Minute) { buffer := make([]byte, 4) n, _ := rand.Read(buffer) sink1 <- buffer[:n] } }() // entropy source 2: submit time between requests sink2 := rng.NewEntropyTimeStampSink() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { sink2 <- time.Now() sizeStr := r.URL.Query().Get("len") size, _ := strconv.ParseInt(sizeStr, 0, 32) if size <= 0 { size = 16 } w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) io.CopyN(w, rng, size) trace.T("main", trace.PrioInfo, "sent %d random bytes for %q", size, r.RequestURI) }) listenAddr := ":8080" trace.T("main", trace.PrioInfo, "listening on http://localhost%s/", listenAddr) err = http.ListenAndServe(listenAddr, nil) if err != nil { trace.T("main", trace.PrioCritical, "%s", err.Error()) } }
// PseudoRandomData returns a slice of n pseudo-random bytes. The // result can be used as a replacement for a sequence of n uniformly // distributed and independent bytes. func (gen *Generator) PseudoRandomData(n uint) []byte { numBlocks := gen.numBlocks(n) res := make([]byte, 0, numBlocks*uint(len(gen.counter))) for numBlocks > 0 { count := numBlocks if count > maxBlocks { count = maxBlocks } res = gen.generateBlocks(res, count) numBlocks -= count newKey := gen.generateBlocks(nil, gen.numBlocks(keySize)) gen.setKey(newKey[:keySize]) } trace.T("fortuna/generator", trace.PrioVerbose, "generated %d pseudo-random bytes", n) return res[:n] }
func doWriteSeed(f *os.File, seed []byte) error { _, err := f.Seek(0, os.SEEK_SET) if err != nil { return err } n, err := f.Write(seed) if err != nil || n != len(seed) { if err == nil { err = &os.PathError{Op: "write", Path: f.Name(), Err: nil} } return err } err = f.Sync() if err != nil { return err } trace.T("fortuna/seed", trace.PrioInfo, "writing new seed data to %q", f.Name()) return nil }
// NewEntropyDataSink returns a channel through which data can be // submitted to the Accumulator's entropy pools. Data should be // written to the returned channel periodically to add entropy to the // state of the random number generator. The written data should be // derived from quantities which change between calls and which cannot // be (completely) known to an attacker. Typical sources of // randomness include noise from a microphone/camera, CPU cycle // counters, or the number of processes running on the system. // // If the data written to the channel is longer than 32 bytes, the // data is hashed internally and the hash is submitted to the entropy // pools instead of the data itself. // // The channel can be closed by the caller to indicate that no more // entropy will be sent via this channel. func (acc *Accumulator) NewEntropyDataSink() chan<- []byte { source := acc.allocateSource() c := make(chan []byte, channelBufferSize) acc.sources.Add(1) go func() { defer acc.sources.Done() seq := uint(0) loop: for { select { case data, ok := <-c: if !ok { break loop } if len(data) > 32 { hash := sha256.New() hash.Write(data) data = hash.Sum(nil) } trace.T("fortuna/entropy", trace.PrioDebug, "adding %d bytes from source %d to pool %d", len(data), source, seq%numPools) acc.addRandomEvent(source, seq, data) seq++ case <-acc.stopSources: break loop } } }() return c }