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 
202 void RNGClass::begin(const char *tag, int eepromAddress)
203 {
204  // Save the EEPROM address for use by save().
205  address = eepromAddress;
206 
207  // Initialize the ChaCha20 input block from the saved seed.
208  memcpy_P(block, tagRNG, sizeof(tagRNG));
209  memcpy_P(block + 4, initRNG, sizeof(initRNG));
210  if (eeprom_read_byte((const uint8_t *)address) == 'S') {
211  // We have a saved seed: XOR it with the initialization block.
212  for (int posn = 0; posn < 12; ++posn) {
213  block[posn + 4] ^=
214  eeprom_read_dword((const uint32_t *)(address + posn * 4 + 1));
215  }
216  }
217 
218  // No entropy credits for the saved seed.
219  credits = 0;
220 
221  // Trigger an automatic save once the entropy credits max out.
222  firstSave = 1;
223 
224  // Rekey the random number generator immediately.
225  rekey();
226 
227  // Stir in the supplied tag data but don't credit any entropy to it.
228  if (tag)
229  stir((const uint8_t *)tag, strlen(tag));
230 
231  // Re-save the seed to obliterate the previous value and to ensure
232  // that if the system is reset without a call to save() that we won't
233  // accidentally generate the same sequence of random data again.
234  save();
235 }
236 
250 {
251  #define MAX_NOISE_SOURCES (sizeof(noiseSources) / sizeof(noiseSources[0]))
252  if (count < MAX_NOISE_SOURCES)
253  noiseSources[count++] = &source;
254 }
255 
272 void RNGClass::setAutoSaveTime(uint16_t minutes)
273 {
274  if (!minutes)
275  minutes = 1; // Just in case.
276  timeout = ((uint32_t)minutes) * 60000U;
277 }
278 
296 void RNGClass::rand(uint8_t *data, size_t len)
297 {
298  // Decrease the amount of entropy in the pool.
299  if (len > (credits / 8))
300  credits = 0;
301  else
302  credits -= len * 8;
303 
304  // Generate the random data.
305  uint8_t count = 0;
306  while (len > 0) {
307  // Force a rekey if we have generated too many blocks in this request.
308  if (count >= RNG_REKEY_BLOCKS) {
309  rekey();
310  count = 1;
311  } else {
312  ++count;
313  }
314 
315  // Increment the low counter word and generate a new keystream block.
316  ++(block[12]);
317  ChaCha::hashCore(stream, block, RNG_ROUNDS);
318 
319  // Copy the data to the return buffer.
320  if (len < 64) {
321  memcpy(data, stream, len);
322  break;
323  } else {
324  memcpy(data, stream, 64);
325  data += 64;
326  len -= 64;
327  }
328  }
329 
330  // Force a rekey after every request.
331  rekey();
332 }
333 
373 bool RNGClass::available(size_t len) const
374 {
375  if (len >= (RNG_MAX_CREDITS / 8))
376  return credits >= RNG_MAX_CREDITS;
377  else
378  return len <= (credits / 8);
379 }
380 
406 void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
407 {
408  // Increase the entropy credit.
409  if ((credit / 8) >= len)
410  credit = len * 8;
411  if ((RNG_MAX_CREDITS - credits) > credit)
412  credits += credit;
413  else
414  credits = RNG_MAX_CREDITS;
415 
416  // Process the supplied input data.
417  if (len > 0) {
418  // XOR the data with the ChaCha input block in 48 byte
419  // chunks and rekey the ChaCha cipher for each chunk to mix
420  // the data in. This should scatter any "true entropy" in
421  // the input across the entire block.
422  while (len > 0) {
423  size_t templen = len;
424  if (templen > 48)
425  templen = 48;
426  uint8_t *output = ((uint8_t *)block) + 16;
427  len -= templen;
428  while (templen > 0) {
429  *output++ ^= *data++;
430  --templen;
431  }
432  rekey();
433  }
434  } else {
435  // There was no input data, so just force a rekey so we
436  // get some mixing of the state even without new data.
437  rekey();
438  }
439 
440  // Save if this is the first time we have reached max entropy.
441  // This provides some protection if the system is powered off before
442  // the first auto-save timeout occurs.
443  if (firstSave && credits >= RNG_MAX_CREDITS) {
444  firstSave = 0;
445  save();
446  }
447 }
448 
476 {
477  // Generate random data from the current state and save
478  // that as the seed. Then force a rekey.
479  ++(block[12]);
480  ChaCha::hashCore(stream, block, RNG_ROUNDS);
481  eeprom_write_block(stream, (void *)(address + 1), 48);
482  eeprom_update_byte((uint8_t *)address, 'S');
483  rekey();
484  timer = millis();
485 }
486 
494 {
495  // Stir in the entropy from all registered noise sources.
496  for (uint8_t posn = 0; posn < count; ++posn)
497  noiseSources[posn]->stir();
498 
499  // Save the seed if the auto-save timer has expired.
500  if ((millis() - timer) >= timeout)
501  save();
502 }
503 
524 {
525  clean(block);
526  clean(stream);
527  for (int posn = 0; posn < SEED_SIZE; ++posn)
528  eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
529 }
530 
534 void RNGClass::rekey()
535 {
536  // Rekey the cipher for the next request by generating a new block.
537  // This is intended to make it difficult to wind the random number
538  // backwards if the state is captured later. The first 16 bytes of
539  // "block" remain set to "tagRNG".
540  ++(block[12]);
541  ChaCha::hashCore(stream, block, RNG_ROUNDS);
542  memcpy(block + 4, stream, 48);
543 
544  // Permute the high word of the counter using the system microsecond
545  // counter to introduce a little bit of non-stir randomness for each
546  // request. Note: If random data is requested on a predictable schedule
547  // then this may not help very much. It is still necessary to stir in
548  // high quality entropy data on a regular basis using stir().
549  block[13] ^= micros();
550 }
void save()
Saves the random seed to EEPROM.
Definition: RNG.cpp:475
void rand(uint8_t *data, size_t len)
Generates random bytes into a caller-supplied buffer.
Definition: RNG.cpp:296
void begin(const char *tag, int eepromAddress)
Initializes the random number generator.
Definition: RNG.cpp:202
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:249
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:523
bool available(size_t len) const
Determine if there is sufficient entropy available for a specific request size.
Definition: RNG.cpp:373
void loop()
Run periodic housekeeping tasks on the random number generator.
Definition: RNG.cpp:493
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:53
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:253
void stir(const uint8_t *data, size_t len, unsigned int credit=0)
Stirs additional entropy data into the random pool.
Definition: RNG.cpp:406
void setAutoSaveTime(uint16_t minutes)
Sets the amount of time between automatic seed saves.
Definition: RNG.cpp:272