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  source.added();
255  }
256 }
257 
274 void RNGClass::setAutoSaveTime(uint16_t minutes)
275 {
276  if (!minutes)
277  minutes = 1; // Just in case.
278  timeout = ((uint32_t)minutes) * 60000U;
279 }
280 
298 void RNGClass::rand(uint8_t *data, size_t len)
299 {
300  // Decrease the amount of entropy in the pool.
301  if (len > (credits / 8))
302  credits = 0;
303  else
304  credits -= len * 8;
305 
306  // Generate the random data.
307  uint8_t count = 0;
308  while (len > 0) {
309  // Force a rekey if we have generated too many blocks in this request.
310  if (count >= RNG_REKEY_BLOCKS) {
311  rekey();
312  count = 1;
313  } else {
314  ++count;
315  }
316 
317  // Increment the low counter word and generate a new keystream block.
318  ++(block[12]);
319  ChaCha::hashCore(stream, block, RNG_ROUNDS);
320 
321  // Copy the data to the return buffer.
322  if (len < 64) {
323  memcpy(data, stream, len);
324  break;
325  } else {
326  memcpy(data, stream, 64);
327  data += 64;
328  len -= 64;
329  }
330  }
331 
332  // Force a rekey after every request.
333  rekey();
334 }
335 
375 bool RNGClass::available(size_t len) const
376 {
377  if (len >= (RNG_MAX_CREDITS / 8))
378  return credits >= RNG_MAX_CREDITS;
379  else
380  return len <= (credits / 8);
381 }
382 
408 void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
409 {
410  // Increase the entropy credit.
411  if ((credit / 8) >= len)
412  credit = len * 8;
413  if ((RNG_MAX_CREDITS - credits) > credit)
414  credits += credit;
415  else
416  credits = RNG_MAX_CREDITS;
417 
418  // Process the supplied input data.
419  if (len > 0) {
420  // XOR the data with the ChaCha input block in 48 byte
421  // chunks and rekey the ChaCha cipher for each chunk to mix
422  // the data in. This should scatter any "true entropy" in
423  // the input across the entire block.
424  while (len > 0) {
425  size_t templen = len;
426  if (templen > 48)
427  templen = 48;
428  uint8_t *output = ((uint8_t *)block) + 16;
429  len -= templen;
430  while (templen > 0) {
431  *output++ ^= *data++;
432  --templen;
433  }
434  rekey();
435  }
436  } else {
437  // There was no input data, so just force a rekey so we
438  // get some mixing of the state even without new data.
439  rekey();
440  }
441 
442  // Save if this is the first time we have reached max entropy.
443  // This provides some protection if the system is powered off before
444  // the first auto-save timeout occurs.
445  if (firstSave && credits >= RNG_MAX_CREDITS) {
446  firstSave = 0;
447  save();
448  }
449 }
450 
478 {
479  // Generate random data from the current state and save
480  // that as the seed. Then force a rekey.
481  ++(block[12]);
482  ChaCha::hashCore(stream, block, RNG_ROUNDS);
483  eeprom_write_block(stream, (void *)(address + 1), 48);
484  eeprom_update_byte((uint8_t *)address, 'S');
485  rekey();
486  timer = millis();
487 }
488 
496 {
497  // Stir in the entropy from all registered noise sources.
498  for (uint8_t posn = 0; posn < count; ++posn)
499  noiseSources[posn]->stir();
500 
501  // Save the seed if the auto-save timer has expired.
502  if ((millis() - timer) >= timeout)
503  save();
504 }
505 
526 {
527  clean(block);
528  clean(stream);
529  for (int posn = 0; posn < SEED_SIZE; ++posn)
530  eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
531 }
532 
536 void RNGClass::rekey()
537 {
538  // Rekey the cipher for the next request by generating a new block.
539  // This is intended to make it difficult to wind the random number
540  // backwards if the state is captured later. The first 16 bytes of
541  // "block" remain set to "tagRNG".
542  ++(block[12]);
543  ChaCha::hashCore(stream, block, RNG_ROUNDS);
544  memcpy(block + 4, stream, 48);
545 
546  // Permute the high word of the counter using the system microsecond
547  // counter to introduce a little bit of non-stir randomness for each
548  // request. Note: If random data is requested on a predictable schedule
549  // then this may not help very much. It is still necessary to stir in
550  // high quality entropy data on a regular basis using stir().
551  block[13] ^= micros();
552 }
void save()
Saves the random seed to EEPROM.
Definition: RNG.cpp:477
void rand(uint8_t *data, size_t len)
Generates random bytes into a caller-supplied buffer.
Definition: RNG.cpp:298
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
virtual void added()
Called when the noise source is added to RNG with RNG.addNoiseSource().
Definition: NoiseSource.cpp:95
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:525
bool available(size_t len) const
Determine if there is sufficient entropy available for a specific request size.
Definition: RNG.cpp:375
void loop()
Run periodic housekeeping tasks on the random number generator.
Definition: RNG.cpp:495
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:408
void setAutoSaveTime(uint16_t minutes)
Sets the amount of time between automatic seed saves.
Definition: RNG.cpp:274