diff --git a/doc/Doxyfile b/doc/Doxyfile index 2a60d107..0b73c089 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -753,6 +753,7 @@ IMAGE_PATH = ../libraries/BlinkLED/examples/Cylon \ ../libraries/LCD/examples/HelloWorld \ ../libraries/LCD/examples/Form \ ../libraries/RTC/examples/AlarmClock \ + ../libraries/Crypto \ ../libraries/DMD \ ../libraries/IR \ ../libraries/I2C diff --git a/doc/crypto.dox b/doc/crypto.dox index 5b823208..1e80ba50 100644 --- a/doc/crypto.dox +++ b/doc/crypto.dox @@ -30,6 +30,7 @@ \li Block cipher modes: CTR, CFB, CBC, OFB \li Stream ciphers: ChaCha \li Hash algorithms: SHA1, SHA256, BLAKE2s +\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource All cryptographic algorithms have been optimized for 8-bit Arduino platforms like the Uno. Memory usage is also reduced, particularly for SHA1 and SHA256 diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 651e6e40..d3f5a709 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -94,6 +94,7 @@ realtime clock and the LCD library to implement an alarm clock. \li Block cipher modes: CTR, CFB, CBC, OFB \li Stream ciphers: ChaCha \li Hash algorithms: SHA1, SHA256, BLAKE2s +\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource More information can be found on the \ref crypto "Cryptographic Library" page. diff --git a/libraries/Crypto/NoiseSource.cpp b/libraries/Crypto/NoiseSource.cpp new file mode 100644 index 00000000..e11b6b11 --- /dev/null +++ b/libraries/Crypto/NoiseSource.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "NoiseSource.h" +#include "RNG.h" + +/** + * \class NoiseSource NoiseSource.h + * \brief Abstract base class for random noise sources. + * + * \sa \link RNGClass RNG\endlink, TransistorNoiseSource + */ + +/** + * \brief Constructs a new random noise source. + */ +NoiseSource::NoiseSource() +{ +} + +/** + * \brief Destroys this random noise source. + */ +NoiseSource::~NoiseSource() +{ +} + +/** + * \fn bool NoiseSource::calibrating() const + * \brief Determine if the noise source is still calibrating itself. + * + * \return Returns true if calibration is in progress; false if the noise + * source is generating valid random data. + * + * Noise sources that require calibration start doing so at system startup + * and then switch over to random data generation once calibration is complete. + * Since no random data is being generated during calibration, the output + * from \link RNGClass::rand() RNG.rand()\endlink may be predictable. + * Use \link RNGClass::available() RNG.available()\endlink to determine + * when sufficient entropy is available to generate good random values. + * + * It is possible that the noise source never exits calibration. This can + * happen if the input voltage is insufficient to trigger noise or if the + * noise source is not connected. Noise sources may also periodically + * recalibrate themselves. + * + * \sa stir() + */ + +/** + * \fn void NoiseSource::stir() + * \brief Stirs entropy from this noise source into the global random + * number pool. + * + * This function should call output() to add the entropy from this noise + * source to the global random number pool. + * + * The noise source should batch up the entropy data, providing between + * 16 and 48 bytes of data each time. If the noise source does not have + * sufficient entropy data at the moment, it should return without stiring + * the current data in. + * + * \sa calibrating(), output() + */ + +/** + * \brief Called from subclasses to output noise to the global random + * number pool. + * + * \param data Points to the noise data. + * \param len Number of bytes of noise data. + * \param credit The number of bits of entropy to credit for the data. + * Note that this is bits, not bytes. + * + * The default implementation of this function calls + * \link RNGClass::stir() RNG.stir()\endlink to add the entropy from + * this noise source to the global random number pool. + * + * This function may be overridden by subclasses to capture the raw + * output from the noise source before it is mixed into the pool to + * allow the raw data to be analyzed for randomness. + */ +void NoiseSource::output(const uint8_t *data, size_t len, unsigned int credit) +{ + RNG.stir(data, len, credit); +} diff --git a/libraries/Crypto/NoiseSource.h b/libraries/Crypto/NoiseSource.h new file mode 100644 index 00000000..c16a4122 --- /dev/null +++ b/libraries/Crypto/NoiseSource.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef CRYPTO_NOISESOURCE_H +#define CRYPTO_NOISESOURCE_H + +#include +#include + +class NoiseSource +{ +public: + NoiseSource(); + virtual ~NoiseSource(); + + virtual bool calibrating() const = 0; + virtual void stir() = 0; + +protected: + virtual void output(const uint8_t *data, size_t len, unsigned int credit); +}; + +#endif diff --git a/libraries/Crypto/RNG.cpp b/libraries/Crypto/RNG.cpp new file mode 100644 index 00000000..fa23d9d9 --- /dev/null +++ b/libraries/Crypto/RNG.cpp @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "RNG.h" +#include "NoiseSource.h" +#include "ChaCha.h" +#include "Crypto.h" +#include "utility/ProgMemUtil.h" +#include +#include +#include + +/** + * \class RNGClass RNG.h + * \brief Pseudo random number generator suitable for cryptography. + * + * Random number generators must be seeded properly before they can + * be used or an adversary may be able to predict the random output. + * Seed data may be: + * + * \li Device-specific, for example serial numbers or MAC addresses. + * \li Application-specific, unique to the application. The tag that is + * passed to begin() is an example of an application-specific value. + * \li Noise-based, generated by a hardware random number generator + * that provides unpredictable values from a noise source. + * + * The following example demonstrates how to initialise the random + * number generator: + * + * \code + * #include + * #include + * #include + * #include + * #include + * + * // Noise source to seed the random number generator. + * TransistorNoiseSource noise(A1); + * + * // MAC address for Ethernet communication. + * byte mac_address[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; + * + * void setup() { + * // Initialize the Ethernet shield. + * Ethernet.begin(mac_address); + * + * // Initialize the random number generator with the application tag + * // "MyApp 1.0" and load the previous seed from EEPROM address 500. + * RNG.begin("MyApp 1.0", 500); + * + * // Stir in the Ethernet MAC address. + * RNG.stir(mac_address, sizeof(mac_address)); + * + * // ... + * } + * \endcode + * + * The application should regularly call stir() to mix in new data from + * the noise source and also regularly call loop(): + * + * \code + * void loop() { + * // ... + * + * // If the noise source has accumulated new entropy, then stir it in. + * RNG.stir(noise); + * + * // Perform regular housekeeping on the random number generator. + * RNG.loop(); + * + * // ... + * } + * \endcode + * + * The loop() function will automatically save the random number seed on a + * regular basis. By default the seed is saved every hour but this can be + * changed using setAutoSaveTime(). + * + * Keep in mind that saving too often may cause the EEPROM to wear out quicker. + * It is wise to limit saving to once an hour or once a day depending + * upon how long you intend to field the device before replacing it. + * For example, an EEPROM rated for 100k erase/write cycles will last about + * 69 days saving once a minute or 11 years saving once an hour. + * + * The application can still elect to call save() at any time if wants. + * For example, if the application can detect power loss or shutdown + * conditions programmatically, then it may make sense to force a save() + * of the seed upon shutdown. + * + * \sa NoiseSource + */ + +/** + * \brief Global random number generator instance. + * + * \sa RNGClass + */ +RNGClass RNG; + +/** + * \var RNGClass::SEED_SIZE + * \brief Size of a saved random number seed in EEPROM space. + */ + +// Number of ChaCha hash rounds to use for random number generation. +#define RNG_ROUNDS 20 + +// Force a rekey after this many blocks of random data. +#define RNG_REKEY_BLOCKS 16 + +// Maximum entropy credit that can be contained in the pool. +#define RNG_MAX_CREDITS 384 + +/** @cond */ + +// Tag for 256-bit ChaCha20 keys. This will always appear in the +// first 16 bytes of the block. The remaining 48 bytes are the seed. +static const char tagRNG[16] PROGMEM = { + 'e', 'x', 'p', 'a', 'n', 'd', ' ', '3', + '2', '-', 'b', 'y', 't', 'e', ' ', 'k' +}; + +// Initialization seed. This is the ChaCha20 output of hashing +// "expand 32-byte k" followed by 48 bytes set to the numbers 1 to 48. +// The ChaCha20 output block is then truncated to the first 48 bytes. +// +// This value is intended to start the RNG in a semi-chaotic state if +// we don't have a previously saved seed in EEPROM. +static const uint8_t initRNG[48] PROGMEM = { + 0xB0, 0x2A, 0xAE, 0x7D, 0xEE, 0xCB, 0xBB, 0xB1, + 0xFC, 0x03, 0x6F, 0xDD, 0xDC, 0x7D, 0x76, 0x67, + 0x0C, 0xE8, 0x1F, 0x0D, 0xA3, 0xA0, 0xAA, 0x1E, + 0xB0, 0xBD, 0x72, 0x6B, 0x2B, 0x4C, 0x8A, 0x7E, + 0x34, 0xFC, 0x37, 0x60, 0xF4, 0x1E, 0x22, 0xA0, + 0x0B, 0xFB, 0x18, 0x84, 0x60, 0xA5, 0x77, 0x72 +}; + +/** @endcond */ + +/** + * \brief Constructs a new random number generator instance. + * + * This constructor must be followed by a call to begin() to + * properly initialize the random number generator. + * + * \sa begin() + */ +RNGClass::RNGClass() + : address(0) + , credits(0) + , firstSave(1) + , timer(0) + , timeout(3600000UL) // 1 hour in milliseconds +{ +} + +/** + * \brief Destroys this random number generator instance. + */ +RNGClass::~RNGClass() +{ + clean(block); + clean(stream); +} + +/** + * \brief Initializes the random number generator. + * + * \param tag A string that is stirred into the random pool at startup; + * usually this should be a value that is unique to the application and + * version such as "MyApp 1.0" so that different applications do not + * generate the same sequence of values upon first boot. + * \param eepromAddress The EEPROM address to load the previously saved + * seed from and to save new seeds when save() is called. There must be + * at least SEED_SIZE (49) bytes of EEPROM space available at the address. + * + * This function should be followed by calls to stir() to mix in + * additional entropy data from noise sources to initialize the random + * number generator properly. + * + * \sa stir(), save() + */ +void RNGClass::begin(const char *tag, int eepromAddress) +{ + // Save the EEPROM address for use by save(). + address = eepromAddress; + + // Initialize the ChaCha20 input block from the saved seed. + memcpy_P(block, tagRNG, sizeof(tagRNG)); + memcpy_P(block + 4, initRNG, sizeof(initRNG)); + if (eeprom_read_byte((const uint8_t *)address) == 'S') { + // We have a saved seed: XOR it with the initialization block. + for (int posn = 0; posn < 12; ++posn) { + block[posn + 4] ^= + eeprom_read_dword((const uint32_t *)(address + posn * 4 + 1)); + } + } + + // No entropy credits for the saved seed. + credits = 0; + + // Trigger an automatic save once the entropy credits max out. + firstSave = 1; + + // Rekey the random number generator immediately. + rekey(); + + // Stir in the supplied tag data but don't credit any entropy to it. + if (tag) + stir((const uint8_t *)tag, strlen(tag)); + + // Re-save the seed to obliterate the previous value and to ensure + // that if the system is reset without a call to save() that we won't + // accidentally generate the same sequence of random data again. + save(); +} + +/** + * \brief Sets the amount of time between automatic seed saves. + * + * \param minutes The number of minutes between automatic seed saves. + * + * The default time between automatic seed saves is 1 hour. + * + * This function is intended to help with EEPROM wear by slowing down how + * often seed data is saved as noise is stirred into the random pool. + * The exact period to use depends upon how long you intend to field + * the device before replacing it. For example, an EEPROM rated for + * 100k erase/write cycles will last about 69 days saving once a minute + * or 11 years saving once an hour. + * + * \sa save(), stir() + */ +void RNGClass::setAutoSaveTime(uint16_t minutes) +{ + if (!minutes) + minutes = 1; // Just in case. + timeout = ((uint32_t)minutes) * 60000U; +} + +/** + * \brief Generates random bytes into a caller-supplied buffer. + * + * \param data Points to the buffer to fill with random bytes. + * \param len Number of bytes to generate. + * + * Calling this function will decrease the amount of entropy in the + * random number pool by \a len * 8 bits. If there isn't enough + * entropy, then this function will still return \a len bytes of + * random data generated from what entropy it does have. + * + * If the application requires a specific amount of entropy before + * generating important values, the available() function can be + * polled to determine when sufficient entropy is available. + * + * \sa available(), stir() + */ +void RNGClass::rand(uint8_t *data, size_t len) +{ + // Decrease the amount of entropy in the pool. + if (len > (credits / 8)) + credits = 0; + else + credits -= len * 8; + + // Generate the random data. + uint8_t count = 0; + while (len > 0) { + // Force a rekey if we have generated too many blocks in this request. + if (count >= RNG_REKEY_BLOCKS) { + rekey(); + count = 1; + } else { + ++count; + } + + // Increment the low counter word and generate a new keystream block. + ++(block[12]); + ChaCha::hashCore(stream, block, RNG_ROUNDS); + + // Copy the data to the return buffer. + if (len < 64) { + memcpy(data, stream, len); + break; + } else { + memcpy(data, stream, 64); + data += 64; + len -= 64; + } + } + + // Force a rekey after every request. + rekey(); +} + +/** + * \brief Determine if there is sufficient entropy available for a + * specific request size. + * + * \param len The number of bytes of random data that will be requested + * via a call to rand(). + * \return Returns true if there is at least \a len * 8 bits of entropy + * in the random number pool, or false if not. + * + * This function can be used by the application to wait for sufficient + * entropy to become available from the system's noise sources before + * generating important values. For example: + * + * \code + * bool haveKey = false; + * byte key[32]; + * + * void loop() { + * ... + * + * if (!haveKey && RNG.available(sizeof(key))) { + * RNG.rand(key, sizeof(key)); + * haveKey = true; + * } + * + * ... + * } + * \endcode + * + * If \a len is larger than the maximum number of entropy credits supported + * by the random number pool (384 bits, 48 bytes), then the maximum will be + * used instead. For example, asking if 512 bits (64 bytes) are available + * will return true if in reality only 384 bits are available. If this is a + * problem for the application's security requirements, then large requests + * for random data should be broken up into smaller chunks with the + * application waiting for the entropy pool to refill between chunks. + * + * \sa rand() + */ +bool RNGClass::available(size_t len) const +{ + if (len >= (RNG_MAX_CREDITS / 8)) + return credits >= RNG_MAX_CREDITS; + else + return len <= (credits / 8); +} + +/** + * \brief Stirs additional entropy data into the random pool. + * + * \param data Points to the additional data to be stirred in. + * \param len Number of bytes to be stirred in. + * \param credit The number of bits of entropy to credit for the + * data that is stirred in. Note that this is bits, not bytes. + * + * The maximum credit allowed is \a len * 8 bits, indicating that + * every bit in the input \a data is good and random. Practical noise + * sources are rarely that good, so \a credit will usually be smaller. + * For example, to credit 2 bits of entropy per byte, the function + * would be called as follows: + * + * \code + * RNG.stir(noise_data, noise_bytes, noise_bytes * 2); + * \endcode + * + * If \a credit is zero, then the \a data will be stirred in but no + * entropy credit is given. This is useful for static values like + * serial numbers and MAC addresses that are different between + * devices but highly predictable. + * + * \sa loop() + */ +void RNGClass::stir(const uint8_t *data, size_t len, unsigned int credit) +{ + // Increase the entropy credit. + if ((credit / 8) >= len) + credit = len * 8; + if ((RNG_MAX_CREDITS - credits) > credit) + credits += credit; + else + credits = RNG_MAX_CREDITS; + + // Process the supplied input data. + if (len > 0) { + // XOR the data with the ChaCha input block in 48 byte + // chunks and rekey the ChaCha cipher for each chunk to mix + // the data in. This should scatter any "true entropy" in + // the input across the entire block. + while (len > 0) { + size_t templen = len; + if (templen > 48) + templen = 48; + uint8_t *output = ((uint8_t *)block) + 16; + len -= templen; + while (templen > 0) { + *output++ ^= *data++; + --templen; + } + rekey(); + } + } else { + // There was no input data, so just force a rekey so we + // get some mixing of the state even without new data. + rekey(); + } + + // Save if this is the first time we have reached max entropy. + // This provides some protection if the system is powered off before + // the first auto-save timeout occurs. + if (firstSave && credits >= RNG_MAX_CREDITS) { + firstSave = 0; + save(); + } +} + +/** + * \brief Stirs in data from a noise source into the random pool. + * + * \param source The noise source to obtain entropy data from. + * + * \sa save(), NoiseSource::stir() + */ +void RNGClass::stir(NoiseSource &source) +{ + source.stir(); +} + +/** + * \brief Saves the random seed to EEPROM. + * + * During system startup, noise sources typically won't have accumulated + * much entropy. But startup is usually the time when the system most + * needs to generate random data for session keys, IV's, and the like. + * + * The purpose of this function is to pass some of the accumulated entropy + * from one session to the next after a loss of power. Thus, once the system + * has been running for a while it will get progressively better at generating + * random values and the accumulated entropy will not be completely lost. + * + * Normally it isn't necessary to call save() directly. The loop() function + * will automatically save the seed on a periodic basis (default of 1 hour). + * + * The seed that is saved is generated in such a way that it cannot be used + * to predict random values that were generated previously or subsequently + * in the current session. So a compromise of the EEPROM contents of a + * captured device should not result in compromise of random values + * that have already been generated. However, if power is lost and the + * system restarted, then there will be a short period of time where the + * random state will be predictable from the seed. For this reason it is + * very important to stir() in new noise data at startup. + * + * \sa loop(), stir() + */ +void RNGClass::save() +{ + // Generate random data from the current state and save + // that as the seed. Then force a rekey. + ++(block[12]); + ChaCha::hashCore(stream, block, RNG_ROUNDS); + eeprom_write_block(stream, (void *)(address + 1), 48); + eeprom_update_byte((uint8_t *)address, 'S'); + rekey(); + timer = millis(); +} + +/** + * \brief Run periodic housekeeping tasks on the random number generator. + * + * This function must be called on a regular basis from the application's + * main "loop()" function. + */ +void RNGClass::loop() +{ + // Save the seed if the auto-save timer has expired. + if ((millis() - timer) >= timeout) + save(); +} + +/** + * \brief Destroys the data in the random number pool and the saved seed + * in EEPROM. + * + * This function attempts to throw away any data that could theoretically be + * used to predict previous and future outputs of the random number generator + * if the device is captured, sold, or otherwise compromised. + * + * After this function is called, begin() must be called again to + * re-initialize the random number generator, followed by stir() to + * add in new entropy from system noise sources. + * + * \note The rand() and save() functions take some care to manage the + * random number pool in a way that makes prediction of past outputs from a + * captured state very difficult. Future outputs may be predictable if + * noise or other high-entropy data is not mixed in with stir() on a + * regular basis. + * + * \sa begin() + */ +void RNGClass::destroy() +{ + clean(block); + clean(stream); + for (int posn = 0; posn < SEED_SIZE; ++posn) + eeprom_write_byte((uint8_t *)(address + posn), 0xFF); +} + +/** + * \brief Rekeys the random number generator. + */ +void RNGClass::rekey() +{ + // Rekey the cipher for the next request by generating a new block. + // This is intended to make it difficult to wind the random number + // backwards if the state is captured later. The first 16 bytes of + // "block" remain set to "tagRNG". + ++(block[12]); + ChaCha::hashCore(stream, block, RNG_ROUNDS); + memcpy(block + 4, stream, 48); + + // Permute the high word of the counter using the system microsecond + // counter to introduce a little bit of non-stir randomness for each + // request. Note: If random data is requested on a predictable schedule + // then this may not help very much. It is still necessary to stir in + // high quality entropy data on a regular basis using stir(). + block[13] ^= micros(); +} diff --git a/libraries/Crypto/RNG.h b/libraries/Crypto/RNG.h new file mode 100644 index 00000000..2d77e656 --- /dev/null +++ b/libraries/Crypto/RNG.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef CRYPTO_RNG_h +#define CRYPTO_RNG_h + +#include +#include + +class NoiseSource; + +class RNGClass +{ +public: + RNGClass(); + ~RNGClass(); + + void begin(const char *tag, int eepromAddress); + + void setAutoSaveTime(uint16_t minutes); + + void rand(uint8_t *data, size_t len); + bool available(size_t len) const; + + void stir(const uint8_t *data, size_t len, unsigned int credit = 0); + void stir(NoiseSource &source); + + void save(); + + void loop(); + + void destroy(); + + static const int SEED_SIZE = 49; + +private: + uint32_t block[16]; + uint32_t stream[16]; + int address; + uint16_t credits : 15; + uint16_t firstSave : 1; + unsigned long timer; + unsigned long timeout; + + void rekey(); +}; + +extern RNGClass RNG; + +#endif diff --git a/libraries/Crypto/TransistorNoiseSource.cpp b/libraries/Crypto/TransistorNoiseSource.cpp new file mode 100644 index 00000000..9ce6c9cf --- /dev/null +++ b/libraries/Crypto/TransistorNoiseSource.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "TransistorNoiseSource.h" +#include "RNG.h" +#include "Crypto.h" +#include + +/** + * \class TransistorNoiseSource TransistorNoiseSource.h + * \brief Processes the signal from a transistor-based noise source. + * + * This class processes input from a transistor-based noise source, such as + * that described by Rob Seward. + * See that Web page for full details on how such noise sources work, + * how the output should be used, and caveats for the unwary. + * For convenience, Rob's circuit is reproduced below: + * + * \image html transistor_noise_source.png + * + * The following example shows how to initialize a transistor-based noise + * source and use it with \link RNGClass RNG\endlink. The noise is read + * from the A1 pin on the Arduino and stirred into the random number pool + * on a regular basis. For more information, see the documentation for + * \link RNGClass RNG\endlink. + * + * \code + * #include + * #include + * #include + * + * // Noise source to seed the random number generator. + * TransistorNoiseSource noise(A1); + * + * void setup() { + * // Initialize the random number generator with the application tag + * // "MyApp 1.0" and load the previous seed from EEPROM address 500. + * RNG.begin("MyApp 1.0", 500); + * + * // ... + * } + * + * void loop() { + * // ... + * + * // If the noise source has accumulated new entropy, then stir it in. + * RNG.stir(noise); + * + * // Perform regular housekeeping on the random number generator. + * RNG.loop(); + * + * // ... + * } + * \endcode + * + * \sa \link RNGClass RNG\endlink, NoiseSource + */ + +/* + +Theory of operation: + +From Rob Seward's original design we need to find the median of the input +signal. That is, the threshold at which half the signal is below the +threshold (a zero) and the other half is above the threshold (a one). +Rob used a histogram table to find the median which is memory-intensive. +We cannot afford to spend that much memory finding the median. + +In this implementation we divide the signal up into "buckets" of 1024 +samples. We pick a starting threshold and count the number of ones +within the bucket. If the number of ones is between 45% to 55% of the +total number of samples, then we use that threshold to convert the +bucket into output bits. Otherwise we adjust the threshold up or down, +discard the bucket, and try again. + +After a few buckets, the threshold naturally settles at the median without +needing a histogram. The rest of the bucket processing can be done online +with storage needed only for the debiased output bits. + +If the input voltage to the noise source is too low to generate noise, +then the delta between the minimum and maximum samples in the bucket will +be quite small. This is used to detect disconnection of the noise source. +No output is generated when the noise source is disconnected. + +With 1024 raw input samples we get roughly 256 output bits after +Von Neumann debiasing. As a further check, the output will be discarded +if less than 192 bits are generated. This can happen when the noise source +is connected or disconnected: only part of the bucket is valid. + +One of the properties of Rob's circuit design is that over time the median +changes due to environmental factors and component wear. Because we adjust +the threshold from bucket to bucket, it should naturally float up or down +to the new median level as the circuit's properties change. + +*/ + +// Number of ADC values that can be generated by analogRead(). +#define ADC_NUM 1024 + +// Number of samples to collect for a single noise "bucket". +#define SAMPLES_NUM 1024 + +// Calculate a percentage of the sample bucket size. +#define SAMPLES_PCT(num) ((int)(((long)SAMPLES_NUM) * (num) / 100L)) + +// Expected spread between the minimum and maximum ADC readings for +// the noise source to be considered as operating correctly. +#define NOISE_SPREAD (ADC_NUM / 8) + +// Calibration states. +#define NOISE_NOT_CALIBRATING 0 +#define NOISE_CALIBRATING 1 + +/** + * \brief Constructs a new transitor-based noise source handler. + * + * \param pin The analog input pin that the noise will appear on. + */ +TransistorNoiseSource::TransistorNoiseSource(uint8_t pin) + : threshold(ADC_NUM / 2) + , _pin(pin) + , calState(NOISE_CALIBRATING) +{ + // Configure the pin as an analog input with no pull-up. + pinMode(pin, INPUT); + digitalWrite(pin, LOW); + + // Start the bit collection routines. + restart(); +} + +TransistorNoiseSource::~TransistorNoiseSource() +{ + restart(); +} + +bool TransistorNoiseSource::calibrating() const +{ + return calState != NOISE_NOT_CALIBRATING; +} + +void TransistorNoiseSource::stir() +{ + // Keep track of the minimum and maximum while generating data + // so that we can detect when the input voltage falls too low + // for the circuit to generate noise. + int value = analogRead(_pin); + if (value < minValue) + minValue = value; + if (value > maxValue) + maxValue = value; + + // Collect two bits of input and remove bias using the Von Neumann method. + // If both bits are the same, then discard both. Otherwise choose one + // of the bits and output that one. We have to do this carefully so that + // instruction timing does not reveal the value of the bit that is chosen. + uint8_t bit = ((threshold - value) >> 15) & 1; // Subtract and extract sign. + if (count & 1) { + if (prevBit ^ bit) { + // The bits are different: add the new bit to the buffer. + if (posn < sizeof(buffer)) { + buffer[posn] = (buffer[posn] << 1) | bit; + if (++bitNum >= 8) { + ++posn; + bitNum = 0; + } + } + } + } else { + prevBit = bit; + } + + // Keep a count of the number of raw 1 bits. + ones += bit; + + // Bail out if we haven't collected enough samples for a full bucket yet. + if (++count < SAMPLES_NUM) + return; + + // If the maximum minus the minimum is too small, then there probably + // is no signal or the input voltage is insufficient to generate noise. + // Discard the entire bucket and return to calibration. + if ((maxValue - minValue) < NOISE_SPREAD) { + restart(); + calState = NOISE_CALIBRATING; + threshold = ADC_NUM / 2; // Reacquire threshold when the signal returns. + return; + } + + // If the number of 1's is between 45% and 55% of the total count, + // then we have a good bucket. The threshold is at an appropriate level. + if (ones >= SAMPLES_PCT(45) && ones <= SAMPLES_PCT(55)) { + if (posn >= (sizeof(buffer) * 3 / 4)) { + // The buffer is at least three-quarters full of debiased bits + // so pass them onto output(). There may be less bits if we + // lost or gained the signal half-way through the bucket. + // Credit 4 bits of entropy for every 8 bits of output. + output(buffer, posn, posn * 4); + } + restart(); + calState = NOISE_NOT_CALIBRATING; + return; + } + + // The threshold is not close enough to the mid-point of the signal. + // Adjust the threshold, discard the bucket, and try again. + if (ones < SAMPLES_PCT(25) || ones > SAMPLES_PCT(75)) { + // We are a long way away from the mid-point, so move the threshold + // by a large amount based on the delta to get closer quicker. + threshold -= (SAMPLES_PCT(50) - ones) / 8; + } else if (ones < SAMPLES_PCT(50)) { + // Not enough ones so move the threshold down a bit. + --threshold; + } else { + // Too many ones so move the threshold up a bit. + ++threshold; + } + if (threshold < 0) + threshold = 0; + else if (threshold >= ADC_NUM) + threshold = ADC_NUM - 1; + restart(); + calState = NOISE_CALIBRATING; +} + +/** + * \brief Restarts the bit collection process for the next bucket. + */ +void TransistorNoiseSource::restart() +{ + clean(buffer); + prevBit = 0; + posn = 0; + bitNum = 0; + minValue = ADC_NUM - 1; + maxValue = 0; + count = 0; + ones = 0; +} diff --git a/libraries/Crypto/TransistorNoiseSource.h b/libraries/Crypto/TransistorNoiseSource.h new file mode 100644 index 00000000..3f387102 --- /dev/null +++ b/libraries/Crypto/TransistorNoiseSource.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef CRYPTO_TRANSISTORNOISESOURCE_H +#define CRYPTO_TRANSISTORNOISESOURCE_H + +#include +#include "NoiseSource.h" + +class TransistorNoiseSource : public NoiseSource +{ +public: + explicit TransistorNoiseSource(uint8_t pin); + virtual ~TransistorNoiseSource(); + + bool calibrating() const; + + void stir(); + +private: + int threshold; + uint8_t _pin; + uint8_t prevBit; + uint8_t posn; + uint8_t bitNum; + uint8_t calState; + uint8_t buffer[32]; + int minValue; + int maxValue; + int count; + int ones; + + void restart(); +}; + +#endif diff --git a/libraries/Crypto/examples/TestChaCha/TestChaCha.ino b/libraries/Crypto/examples/TestChaCha/TestChaCha.ino index cc48bf9a..ed691bab 100644 --- a/libraries/Crypto/examples/TestChaCha/TestChaCha.ino +++ b/libraries/Crypto/examples/TestChaCha/TestChaCha.ino @@ -27,6 +27,7 @@ This example runs tests on the ChaCha implementation to verify correct behaviour #include #include #include +#include #define MAX_PLAINTEXT_SIZE 64 #define MAX_CIPHERTEXT_SIZE 64 @@ -50,7 +51,7 @@ struct TestVector // specification doesn't contain test vectors - these were generated // using the reference implementation from http://cr.yp.to/chacha.html. -static TestVector const testVectorChaCha20_128 = { +static TestVector const testVectorChaCha20_128 PROGMEM = { .name = "ChaCha20 128-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .keySize = 16, @@ -76,7 +77,7 @@ static TestVector const testVectorChaCha20_128 = { .size = 64 }; -static TestVector const testVectorChaCha20_256 = { +static TestVector const testVectorChaCha20_256 PROGMEM = { .name = "ChaCha20 256-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, @@ -104,7 +105,7 @@ static TestVector const testVectorChaCha20_256 = { .size = 64 }; -static TestVector const testVectorChaCha12_128 = { +static TestVector const testVectorChaCha12_128 PROGMEM = { .name = "ChaCha12 128-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .keySize = 16, @@ -130,7 +131,7 @@ static TestVector const testVectorChaCha12_128 = { .size = 64 }; -static TestVector const testVectorChaCha12_256 = { +static TestVector const testVectorChaCha12_256 PROGMEM = { .name = "ChaCha12 256-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, @@ -158,7 +159,7 @@ static TestVector const testVectorChaCha12_256 = { .size = 64 }; -static TestVector const testVectorChaCha8_128 = { +static TestVector const testVectorChaCha8_128 PROGMEM = { .name = "ChaCha8 128-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, .keySize = 16, @@ -184,7 +185,7 @@ static TestVector const testVectorChaCha8_128 = { .size = 64 }; -static TestVector const testVectorChaCha8_256 = { +static TestVector const testVectorChaCha8_256 PROGMEM = { .name = "ChaCha8 256-bit", .key = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, @@ -212,6 +213,8 @@ static TestVector const testVectorChaCha8_256 = { .size = 64 }; +TestVector testVector; + ChaCha chacha; byte buffer[128]; @@ -272,6 +275,9 @@ void testCipher(ChaCha *cipher, const struct TestVector *test) { bool ok; + memcpy_P(&testVector, test, sizeof(TestVector)); + test = &testVector; + Serial.print(test->name); Serial.print(" ... "); @@ -291,20 +297,15 @@ void testCipher(ChaCha *cipher, const struct TestVector *test) Serial.println("Failed"); } -// The data space of this sketch is too big if we try to test the -// performance of all of setKey(), encrypt(), and decrypt(). -// Since decryption is almost identical to encryption, only test -// that if the PERF_DECRYPT option is enabled, suppressing setKey(). -//#define PERF_DECRYPT 1 - -#if !defined(PERF_DECRYPT) - void perfCipherSetKey(ChaCha *cipher, const struct TestVector *test) { unsigned long start; unsigned long elapsed; int count; + memcpy_P(&testVector, test, sizeof(TestVector)); + test = &testVector; + Serial.print(test->name); Serial.print(" SetKey ... "); @@ -322,14 +323,15 @@ void perfCipherSetKey(ChaCha *cipher, const struct TestVector *test) Serial.println(" per second"); } -#endif - void perfCipherEncrypt(ChaCha *cipher, const struct TestVector *test) { unsigned long start; unsigned long elapsed; int count; + memcpy_P(&testVector, test, sizeof(TestVector)); + test = &testVector; + Serial.print(test->name); Serial.print(" Encrypt ... "); @@ -348,14 +350,15 @@ void perfCipherEncrypt(ChaCha *cipher, const struct TestVector *test) Serial.println(" bytes per second"); } -#if defined(PERF_DECRYPT) - void perfCipherDecrypt(ChaCha *cipher, const struct TestVector *test) { unsigned long start; unsigned long elapsed; int count; + memcpy_P(&testVector, test, sizeof(TestVector)); + test = &testVector; + Serial.print(test->name); Serial.print(" Decrypt ... "); @@ -374,17 +377,11 @@ void perfCipherDecrypt(ChaCha *cipher, const struct TestVector *test) Serial.println(" bytes per second"); } -#endif - void perfCipher(ChaCha *cipher, const struct TestVector *test) { -#if !defined(PERF_DECRYPT) perfCipherSetKey(cipher, test); perfCipherEncrypt(cipher, test); -#else - perfCipherEncrypt(cipher, test); perfCipherDecrypt(cipher, test); -#endif } void setup() diff --git a/libraries/Crypto/examples/TestNoise/TestNoise.ino b/libraries/Crypto/examples/TestNoise/TestNoise.ino new file mode 100644 index 00000000..2b28746a --- /dev/null +++ b/libraries/Crypto/examples/TestNoise/TestNoise.ino @@ -0,0 +1,44 @@ + +// This example dumps the raw data from a transistor noise source +// without any of the whitening normally performed by the RNG class. + +#include +#include + +char const hexchars[] = "0123456789ABCDEF"; + +class RawNoiseSource : public TransistorNoiseSource +{ +public: + RawNoiseSource(uint8_t pin) : TransistorNoiseSource(pin) {} + +protected: + void output(const uint8_t *data, size_t len, unsigned int credit) + { + for (size_t posn = 0; posn < len; ++posn) { + uint8_t value = data[posn]; + Serial.print(hexchars[(value >> 4) & 0x0F]); + Serial.print(hexchars[value & 0x0F]); + } + Serial.println(); + } +}; + +RawNoiseSource noise(A1); +bool calibrating = true; + +void setup() { + Serial.begin(9600); + Serial.println(); + Serial.println("calibrating"); +} + +void loop() { + noise.stir(); + bool nowCalibrating = noise.calibrating(); + if (nowCalibrating != calibrating) { + calibrating = nowCalibrating; + if (calibrating) + Serial.println("calibrating"); + } +} diff --git a/libraries/Crypto/examples/TestRNG/TestRNG.ino b/libraries/Crypto/examples/TestRNG/TestRNG.ino new file mode 100644 index 00000000..d0df122e --- /dev/null +++ b/libraries/Crypto/examples/TestRNG/TestRNG.ino @@ -0,0 +1,70 @@ + +// Example of initializing and using the random number generator. + +#include +#include +#include + +// Change "MyApp 1.0" to some other tag for your application +// so that different applications will generate different results +// even if the input noise or seed data is otherwise identical. +#define RNG_APP_TAG "MyApp 1.0" + +// EEPROM address to save the random number seed at. +#define RNG_EEPROM_ADDRESS 500 + +// Noise source to seed the random number generator. +TransistorNoiseSource noise(A1); + +bool calibrating = false; +byte data[32]; +unsigned long startTime; + +void setup() { + Serial.begin(9600); + Serial.println("start"); + + // Initialize the random number generator. + RNG.begin(RNG_APP_TAG, RNG_EEPROM_ADDRESS); + + startTime = millis(); +} + +void printHex(const byte *data, unsigned len) +{ + static char const hexchars[] = "0123456789ABCDEF"; + unsigned long time = millis() - startTime; + Serial.print(time / 1000); + Serial.print('.'); + Serial.print((time / 100) % 10); + Serial.print(": "); + while (len > 0) { + int b = *data++; + Serial.print(hexchars[(b >> 4) & 0x0F]); + Serial.print(hexchars[b & 0x0F]); + --len; + } + Serial.println(); +} + +void loop() { + // Track changes to the calibration state on the noise source. + bool newCalibrating = noise.calibrating(); + if (newCalibrating != calibrating) { + calibrating = newCalibrating; + if (calibrating) + Serial.println("calibrating"); + } + + // If the noise source has accumulated new entropy, then stir it in. + RNG.stir(noise); + + // Perform regular housekeeping on the random number generator. + RNG.loop(); + + // Generate output whenever 32 bytes of entropy have been accumulated. + if (RNG.available(sizeof(data))) { + RNG.rand(data, sizeof(data)); + printHex(data, sizeof(data)); + } +} diff --git a/libraries/Crypto/transistor_noise_source.fig b/libraries/Crypto/transistor_noise_source.fig new file mode 100644 index 00000000..f216e798 --- /dev/null +++ b/libraries/Crypto/transistor_noise_source.fig @@ -0,0 +1,203 @@ +#FIG 3.2 Produced by xfig version 3.2.5c +Landscape +Center +Metric +A4 +100.00 +Single +-2 +1200 2 +0 32 #404040 +0 33 #808080 +0 34 #c0c0c0 +0 35 #e0e0e0 +0 36 #808080 +0 37 #c0c0c0 +0 38 #e0e0e0 +0 39 #444444 +0 40 #8e8f8e +6 3465 4275 3690 4590 +5 1 0 1 0 -1 0 0 -1 0.000 1 0 0 0 3600.000 4600.000 3510 4480 3600 4450 3690 4480 +2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2 + 3600 4450 3600 4590 +2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2 + 3510 4410 3690 4410 +2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2 + 3600 4410 3600 4275 +2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2 + 3536 4304 3536 4374 +2 1 0 1 0 -1 0 0 -1 0.000 0 1 -1 0 0 2 + 3506 4339 3566 4339 +-6 +6 4725 4500 5220 4950 +1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 4995 4725 186 186 4995 4725 5040 4905 +2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4 + 5095 4870 5050 4780 5005 4825 5095 4870 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4950 4725 5175 4950 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4950 4725 5175 4500 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4950 4590 4950 4860 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4950 4725 4725 4725 +-6 +6 4680 3600 4770 4050 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4725 3955 4725 4050 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7 + 4725 3690 4685 3717 4765 3771 4685 3825 4765 3879 4685 3933 + 4725 3960 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4725 3600 4725 3695 +-6 +6 4230 4500 4725 4950 +1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 4455 4725 186 186 4455 4725 4410 4545 +2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4 + 4355 4580 4400 4670 4445 4625 4355 4580 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4500 4725 4275 4500 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4500 4725 4275 4950 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4500 4860 4500 4590 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 4500 4725 4725 4725 +-6 +6 5490 4185 5805 4365 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5625 4275 5490 4275 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5670 4275 5805 4275 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5625 4365 5625 4185 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5670 4365 5670 4185 +-6 +6 6075 4050 6570 4500 +1 3 0 1 0 -1 0 0 -1 0.000 1 0.0000 6345 4275 186 186 6345 4275 6390 4455 +2 1 0 1 0 -1 0 0 20 0.000 0 0 -1 0 0 4 + 6445 4420 6400 4330 6355 4375 6445 4420 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 6300 4275 6525 4500 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 6300 4275 6525 4050 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 6300 4140 6300 4410 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 6300 4275 6075 4275 +-6 +6 5895 3600 5985 4050 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5940 3955 5940 4050 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7 + 5940 3690 5900 3717 5980 3771 5900 3825 5980 3879 5900 3933 + 5940 3960 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 5940 3600 5940 3695 +-6 +6 3555 3330 3645 3420 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 3600 3375 30 30 3600 3375 3600 3405 +-6 +6 4680 3330 4770 3420 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 4725 3375 30 30 4725 3375 4725 3405 +-6 +6 5895 3330 5985 3420 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5940 3375 30 30 5940 3375 5940 3405 +-6 +6 5895 4230 5985 4320 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5940 4275 30 30 5940 4275 5940 4305 +-6 +6 5130 4230 5220 4320 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5175 4275 30 30 5175 4275 5175 4305 +-6 +6 4680 4230 4770 4320 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 4725 4275 30 30 4725 4275 4725 4305 +-6 +6 5130 5355 5220 5445 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 5175 5400 30 30 5175 5400 5175 5430 +-6 +6 3555 5355 3645 5445 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 3600 5400 30 30 3600 5400 3600 5430 +-6 +6 6480 5355 6570 5445 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 6525 5400 30 30 6525 5400 6525 5430 +-6 +6 7155 4635 7245 5085 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 7200 4990 7200 5085 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7 + 7200 4725 7160 4752 7240 4806 7160 4860 7240 4914 7160 4968 + 7200 4995 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 7200 4635 7200 4730 +-6 +6 7155 3510 7245 3960 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 7200 3865 7200 3960 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 7 + 7200 3600 7160 3627 7240 3681 7160 3735 7240 3789 7160 3843 + 7200 3870 +2 1 0 1 0 -1 0 0 -1 0.000 0 0 -1 0 0 2 + 7200 3510 7200 3605 +-6 +6 7155 4005 7245 4095 +1 3 0 1 0 -1 0 0 20 0.000 1 0.0000 7200 4050 30 30 7200 4050 7200 4080 +-6 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5175 4950 5175 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3600 4545 3600 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 3600 4275 3600 3375 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4725 4050 4725 4275 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4275 4950 4275 5085 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 + 4275 4500 4275 4275 5175 4275 5175 4500 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 4725 3600 4725 3375 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 6525 4500 6525 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5490 4275 5130 4275 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5760 4275 6120 4275 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5940 4050 5940 4275 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 5940 3645 5940 3375 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7200 5085 7200 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7200 3510 7200 3375 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 6525 4050 7650 4050 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 + 7200 3915 7200 4635 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 1 2 + 8 1 1.00 60.00 120.00 + 2925 5400 7200 5400 +2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 1 2 + 8 1 1.00 60.00 120.00 + 2925 3375 7200 3375 +4 0 0 50 -1 0 12 0.0000 4 135 420 3015 4455 10uF\001 +4 0 0 50 -1 0 12 0.0000 4 135 345 6705 3780 10K\001 +4 0 0 50 -1 0 12 0.0000 4 135 390 6705 4905 4.7K\001 +4 0 0 50 -1 0 12 0.0000 4 135 435 5400 3870 1.5M\001 +4 0 0 50 -1 0 12 0.0000 4 135 390 4185 3915 4.7K\001 +4 0 0 50 -1 0 12 0.0000 4 135 345 3060 4680 25V\001 +4 0 0 50 -1 0 12 0.0000 4 135 525 5400 4545 100nF\001 +4 0 0 50 -1 0 12 0.0000 4 165 240 5265 4770 Q2\001 +4 0 0 50 -1 0 12 0.0000 4 165 240 6615 4320 Q3\001 +4 0 0 50 -1 0 12 0.0000 4 135 270 3915 5175 NC\001 +4 0 0 50 -1 0 12 0.0000 4 165 240 3960 4770 Q1\001 +4 0 0 50 -1 0 12 0.0000 4 135 405 2385 5490 GND\001 +4 0 0 50 -1 0 12 0.0000 4 180 1185 7785 4005 To Analog Pin\001 +4 0 0 50 -1 0 12 0.0000 4 135 960 7875 4230 On Arduino\001 +4 0 0 50 -1 0 12 0.0000 4 180 3735 3330 5805 All transistors: BC548, 2N3904, or equivalent\001 +4 0 0 50 -1 0 12 0.0000 4 180 4440 3015 6075 Collector of Q1 must be left not connected to anything\001 +4 0 0 50 -1 0 12 0.0000 4 135 810 2070 3600 10 to 15V\001 +4 0 0 50 -1 0 12 0.0000 4 135 330 2295 3375 VIN\001 diff --git a/libraries/Crypto/transistor_noise_source.png b/libraries/Crypto/transistor_noise_source.png new file mode 100644 index 00000000..7356074a Binary files /dev/null and b/libraries/Crypto/transistor_noise_source.png differ