ArduinoLibs
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Groups Pages
RNG.cpp
1 /*
2  * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include "RNG.h"
24 #include "NoiseSource.h"
25 #include "ChaCha.h"
26 #include "Crypto.h"
27 #include "utility/ProgMemUtil.h"
28 #include <Arduino.h>
29 #include <avr/eeprom.h>
30 #include <string.h>
31 
117 RNGClass RNG;
118 
124 // Number of ChaCha hash rounds to use for random number generation.
125 #define RNG_ROUNDS 20
126 
127 // Force a rekey after this many blocks of random data.
128 #define RNG_REKEY_BLOCKS 16
129 
130 // Maximum entropy credit that can be contained in the pool.
131 #define RNG_MAX_CREDITS 384
132 
135 // Tag for 256-bit ChaCha20 keys. This will always appear in the
136 // first 16 bytes of the block. The remaining 48 bytes are the seed.
137 static const char tagRNG[16] PROGMEM = {
138  'e', 'x', 'p', 'a', 'n', 'd', ' ', '3',
139  '2', '-', 'b', 'y', 't', 'e', ' ', 'k'
140 };
141 
142 // Initialization seed. This is the ChaCha20 output of hashing
143 // "expand 32-byte k" followed by 48 bytes set to the numbers 1 to 48.
144 // The ChaCha20 output block is then truncated to the first 48 bytes.
145 //
146 // This value is intended to start the RNG in a semi-chaotic state if
147 // we don't have a previously saved seed in EEPROM.
148 static const uint8_t initRNG[48] PROGMEM = {
149  0xB0, 0x2A, 0xAE, 0x7D, 0xEE, 0xCB, 0xBB, 0xB1,
150  0xFC, 0x03, 0x6F, 0xDD, 0xDC, 0x7D, 0x76, 0x67,
151  0x0C, 0xE8, 0x1F, 0x0D, 0xA3, 0xA0, 0xAA, 0x1E,
152  0xB0, 0xBD, 0x72, 0x6B, 0x2B, 0x4C, 0x8A, 0x7E,
153  0x34, 0xFC, 0x37, 0x60, 0xF4, 0x1E, 0x22, 0xA0,
154  0x0B, 0xFB, 0x18, 0x84, 0x60, 0xA5, 0x77, 0x72
155 };
156 
168  : address(0)
169  , credits(0)
170  , firstSave(1)
171  , timer(0)
172  , timeout(3600000UL) // 1 hour in milliseconds
173  , count(0)
174 {
175 }
176 
181 {
182  clean(block);
183  clean(stream);
184 }
185 
203 void RNGClass::begin(const char *tag, int eepromAddress)
204 {
205  // Save the EEPROM address for use by save().
206  address = eepromAddress;
207 
208  // Initialize the ChaCha20 input block from the saved seed.
209  memcpy_P(block, tagRNG, sizeof(tagRNG));
210  memcpy_P(block + 4, initRNG, sizeof(initRNG));
211  if (eeprom_read_byte((const uint8_t *)address) == 'S') {
212  // We have a saved seed: XOR it with the initialization block.
213  for (int posn = 0; posn < 12; ++posn) {
214  block[posn + 4] ^=
215  eeprom_read_dword((const uint32_t *)(address + posn * 4 + 1));
216  }
217  }
218 
219  // No entropy credits for the saved seed.
220  credits = 0;
221 
222  // Trigger an automatic save once the entropy credits max out.
223  firstSave = 1;
224 
225  // Rekey the random number generator immediately.
226  rekey();
227 
228  // Stir in the supplied tag data but don't credit any entropy to it.
229  if (tag)
230  stir((const uint8_t *)tag, strlen(tag));
231 
232  // Re-save the seed to obliterate the previous value and to ensure
233  // that if the system is reset without a call to save() that we won't
234  // accidentally generate the same sequence of random data again.
235  save();
236 }
237 
251 {
252  #define MAX_NOISE_SOURCES (sizeof(noiseSources) / sizeof(noiseSources[0]))
253  if (count < MAX_NOISE_SOURCES)
254  noiseSources[count++] = &source;
255 }
256 
273 void RNGClass::setAutoSaveTime(uint16_t minutes)
274 {
275  if (!minutes)
276  minutes = 1; // Just in case.
277  timeout = ((uint32_t)minutes) * 60000U;
278 }
279 
297 void RNGClass::rand(uint8_t *data, size_t len)
298 {
299  // Decrease the amount of entropy in the pool.
300  if (len > (credits / 8))
301  credits = 0;
302  else
303  credits -= len * 8;
304 
305  // Generate the random data.
306  uint8_t count = 0;
307  while (len > 0) {
308  // Force a rekey if we have generated too many blocks in this request.
309  if (count >= RNG_REKEY_BLOCKS) {
310  rekey();
311  count = 1;
312  } else {
313  ++count;
314  }
315 
316  // Increment the low counter word and generate a new keystream block.
317  ++(block[12]);
318  ChaCha::hashCore(stream, block, RNG_ROUNDS);
319 
320  // Copy the data to the return buffer.
321  if (len < 64) {
322  memcpy(data, stream, len);
323  break;
324  } else {
325  memcpy(data, stream, 64);
326  data += 64;
327  len -= 64;
328  }
329  }
330 
331  // Force a rekey after every request.
332  rekey();
333 }
334 
374 bool RNGClass::available(size_t len) const
375 {
376  if (len >= (RNG_MAX_CREDITS / 8))
377  return credits >= RNG_MAX_CREDITS;
378  else
379  return len <= (credits / 8);
380 }
381 
407 void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
408 {
409  // Increase the entropy credit.
410  if ((credit / 8) >= len)
411  credit = len * 8;
412  if ((RNG_MAX_CREDITS - credits) > credit)
413  credits += credit;
414  else
415  credits = RNG_MAX_CREDITS;
416 
417  // Process the supplied input data.
418  if (len > 0) {
419  // XOR the data with the ChaCha input block in 48 byte
420  // chunks and rekey the ChaCha cipher for each chunk to mix
421  // the data in. This should scatter any "true entropy" in
422  // the input across the entire block.
423  while (len > 0) {
424  size_t templen = len;
425  if (templen > 48)
426  templen = 48;
427  uint8_t *output = ((uint8_t *)block) + 16;
428  len -= templen;
429  while (templen > 0) {
430  *output++ ^= *data++;
431  --templen;
432  }
433  rekey();
434  }
435  } else {
436  // There was no input data, so just force a rekey so we
437  // get some mixing of the state even without new data.
438  rekey();
439  }
440 
441  // Save if this is the first time we have reached max entropy.
442  // This provides some protection if the system is powered off before
443  // the first auto-save timeout occurs.
444  if (firstSave && credits >= RNG_MAX_CREDITS) {
445  firstSave = 0;
446  save();
447  }
448 }
449 
458 {
459  source.stir();
460 }
461 
489 {
490  // Generate random data from the current state and save
491  // that as the seed. Then force a rekey.
492  ++(block[12]);
493  ChaCha::hashCore(stream, block, RNG_ROUNDS);
494  eeprom_write_block(stream, (void *)(address + 1), 48);
495  eeprom_update_byte((uint8_t *)address, 'S');
496  rekey();
497  timer = millis();
498 }
499 
507 {
508  // Stir in the entropy from all registered noise sources.
509  for (uint8_t posn = 0; posn < count; ++posn)
510  noiseSources[posn]->stir();
511 
512  // Save the seed if the auto-save timer has expired.
513  if ((millis() - timer) >= timeout)
514  save();
515 }
516 
538 {
539  clean(block);
540  clean(stream);
541  for (int posn = 0; posn < SEED_SIZE; ++posn)
542  eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
543 }
544 
548 void RNGClass::rekey()
549 {
550  // Rekey the cipher for the next request by generating a new block.
551  // This is intended to make it difficult to wind the random number
552  // backwards if the state is captured later. The first 16 bytes of
553  // "block" remain set to "tagRNG".
554  ++(block[12]);
555  ChaCha::hashCore(stream, block, RNG_ROUNDS);
556  memcpy(block + 4, stream, 48);
557 
558  // Permute the high word of the counter using the system microsecond
559  // counter to introduce a little bit of non-stir randomness for each
560  // request. Note: If random data is requested on a predictable schedule
561  // then this may not help very much. It is still necessary to stir in
562  // high quality entropy data on a regular basis using stir().
563  block[13] ^= micros();
564 }
void save()
Saves the random seed to EEPROM.
Definition: RNG.cpp:488
void rand(uint8_t *data, size_t len)
Generates random bytes into a caller-supplied buffer.
Definition: RNG.cpp:297
void begin(const char *tag, int eepromAddress)
Initializes the random number generator.
Definition: RNG.cpp:203
virtual void stir()=0
Stirs entropy from this noise source into the global random number pool.
Abstract base class for random noise sources.
Definition: NoiseSource.h:29
~RNGClass()
Destroys this random number generator instance.
Definition: RNG.cpp:180
void addNoiseSource(NoiseSource &source)
Adds a noise source to the random number generator.
Definition: RNG.cpp:250
RNGClass()
Constructs a new random number generator instance.
Definition: RNG.cpp:167
void destroy()
Destroys the data in the random number pool and the saved seed in EEPROM.
Definition: RNG.cpp:537
bool available(size_t len) const
Determine if there is sufficient entropy available for a specific request size.
Definition: RNG.cpp:374
void loop()
Run periodic housekeeping tasks on the random number generator.
Definition: RNG.cpp:506
Pseudo random number generator suitable for cryptography.
Definition: RNG.h:31
static const int SEED_SIZE
Size of a saved random number seed in EEPROM space.
Definition: RNG.h:54
static void hashCore(uint32_t *output, const uint32_t *input, uint8_t rounds)
Executes the ChaCha hash core on an input memory block.
Definition: ChaCha.cpp:230
void stir(const uint8_t *data, size_t len, unsigned int credit=0)
Stirs additional entropy data into the random pool.
Definition: RNG.cpp:407
void setAutoSaveTime(uint16_t minutes)
Sets the amount of time between automatic seed saves.
Definition: RNG.cpp:273