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 {
174 }
175 
180 {
181  clean(block);
182  clean(stream);
183 }
184 
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 
253 void RNGClass::setAutoSaveTime(uint16_t minutes)
254 {
255  if (!minutes)
256  minutes = 1; // Just in case.
257  timeout = ((uint32_t)minutes) * 60000U;
258 }
259 
277 void RNGClass::rand(uint8_t *data, size_t len)
278 {
279  // Decrease the amount of entropy in the pool.
280  if (len > (credits / 8))
281  credits = 0;
282  else
283  credits -= len * 8;
284 
285  // Generate the random data.
286  uint8_t count = 0;
287  while (len > 0) {
288  // Force a rekey if we have generated too many blocks in this request.
289  if (count >= RNG_REKEY_BLOCKS) {
290  rekey();
291  count = 1;
292  } else {
293  ++count;
294  }
295 
296  // Increment the low counter word and generate a new keystream block.
297  ++(block[12]);
298  ChaCha::hashCore(stream, block, RNG_ROUNDS);
299 
300  // Copy the data to the return buffer.
301  if (len < 64) {
302  memcpy(data, stream, len);
303  break;
304  } else {
305  memcpy(data, stream, 64);
306  data += 64;
307  len -= 64;
308  }
309  }
310 
311  // Force a rekey after every request.
312  rekey();
313 }
314 
354 bool RNGClass::available(size_t len) const
355 {
356  if (len >= (RNG_MAX_CREDITS / 8))
357  return credits >= RNG_MAX_CREDITS;
358  else
359  return len <= (credits / 8);
360 }
361 
387 void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit)
388 {
389  // Increase the entropy credit.
390  if ((credit / 8) >= len)
391  credit = len * 8;
392  if ((RNG_MAX_CREDITS - credits) > credit)
393  credits += credit;
394  else
395  credits = RNG_MAX_CREDITS;
396 
397  // Process the supplied input data.
398  if (len > 0) {
399  // XOR the data with the ChaCha input block in 48 byte
400  // chunks and rekey the ChaCha cipher for each chunk to mix
401  // the data in. This should scatter any "true entropy" in
402  // the input across the entire block.
403  while (len > 0) {
404  size_t templen = len;
405  if (templen > 48)
406  templen = 48;
407  uint8_t *output = ((uint8_t *)block) + 16;
408  len -= templen;
409  while (templen > 0) {
410  *output++ ^= *data++;
411  --templen;
412  }
413  rekey();
414  }
415  } else {
416  // There was no input data, so just force a rekey so we
417  // get some mixing of the state even without new data.
418  rekey();
419  }
420 
421  // Save if this is the first time we have reached max entropy.
422  // This provides some protection if the system is powered off before
423  // the first auto-save timeout occurs.
424  if (firstSave && credits >= RNG_MAX_CREDITS) {
425  firstSave = 0;
426  save();
427  }
428 }
429 
438 {
439  source.stir();
440 }
441 
469 {
470  // Generate random data from the current state and save
471  // that as the seed. Then force a rekey.
472  ++(block[12]);
473  ChaCha::hashCore(stream, block, RNG_ROUNDS);
474  eeprom_write_block(stream, (void *)(address + 1), 48);
475  eeprom_update_byte((uint8_t *)address, 'S');
476  rekey();
477  timer = millis();
478 }
479 
487 {
488  // Save the seed if the auto-save timer has expired.
489  if ((millis() - timer) >= timeout)
490  save();
491 }
492 
514 {
515  clean(block);
516  clean(stream);
517  for (int posn = 0; posn < SEED_SIZE; ++posn)
518  eeprom_write_byte((uint8_t *)(address + posn), 0xFF);
519 }
520 
524 void RNGClass::rekey()
525 {
526  // Rekey the cipher for the next request by generating a new block.
527  // This is intended to make it difficult to wind the random number
528  // backwards if the state is captured later. The first 16 bytes of
529  // "block" remain set to "tagRNG".
530  ++(block[12]);
531  ChaCha::hashCore(stream, block, RNG_ROUNDS);
532  memcpy(block + 4, stream, 48);
533 
534  // Permute the high word of the counter using the system microsecond
535  // counter to introduce a little bit of non-stir randomness for each
536  // request. Note: If random data is requested on a predictable schedule
537  // then this may not help very much. It is still necessary to stir in
538  // high quality entropy data on a regular basis using stir().
539  block[13] ^= micros();
540 }
void save()
Saves the random seed to EEPROM.
Definition: RNG.cpp:468
void rand(uint8_t *data, size_t len)
Generates random bytes into a caller-supplied buffer.
Definition: RNG.cpp:277
void begin(const char *tag, int eepromAddress)
Initializes the random number generator.
Definition: RNG.cpp:202
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:179
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:513
bool available(size_t len) const
Determine if there is sufficient entropy available for a specific request size.
Definition: RNG.cpp:354
void loop()
Run periodic housekeeping tasks on the random number generator.
Definition: RNG.cpp:486
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: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:387
void setAutoSaveTime(uint16_t minutes)
Sets the amount of time between automatic seed saves.
Definition: RNG.cpp:253