diff --git a/libraries/Crypto/BlockCipher.cpp b/libraries/Crypto/BlockCipher.cpp index f4ee57b5..e98eeb66 100644 --- a/libraries/Crypto/BlockCipher.cpp +++ b/libraries/Crypto/BlockCipher.cpp @@ -27,7 +27,7 @@ * \brief Abstract base class for block ciphers. * * Block ciphers always operate in electronic codebook (ECB) mode. - * Higher-level classes such as CFB and CTR wrap the block cipher to + * Higher-level classes such as CFB128 and CTR128 wrap the block cipher to * create more useful classes for encryption and decryption of bulk data. * * References: http://en.wikipedia.org/wiki/Block_cipher, diff --git a/libraries/Crypto/CTR.cpp b/libraries/Crypto/CTR.cpp new file mode 100644 index 00000000..fe4bf81f --- /dev/null +++ b/libraries/Crypto/CTR.cpp @@ -0,0 +1,220 @@ +/* + * 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 "CTR.h" +#include "Crypto.h" +#include + +/** + * \class CTRCommon CTR.h + * \brief Concrete base class to assist with implementing CTR mode for + * 128-bit block ciphers. + * + * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + * + * \sa CTR + */ + +/** + * \brief Constructs a new cipher in CTR mode. + * + * This constructor should be followed by a call to setBlockCipher(). + */ +CTRCommon::CTRCommon() + : blockCipher(0) + , posn(16) + , counterStart(0) +{ +} + +CTRCommon::~CTRCommon() +{ + // It is assumed that the subclass will clear sensitive + // information in the block cipher. + clean(counter); + clean(state); +} + +size_t CTRCommon::keySize() const +{ + return blockCipher->keySize(); +} + +size_t CTRCommon::ivSize() const +{ + return 16; +} + +/** + * \brief Sets the counter size for the IV. + * + * \param size The number of bytes on the end of the counter block + * that are relevant when incrementing, between 1 and 16. + * \return Returns false if the \a size value is not between 1 and 16. + * + * When the counter is incremented during encrypt(), only the last + * \a size bytes are considered relevant. This can be useful + * to improve performance when the higher level protocol specifies that + * only the least significant N bytes "count". The high level protocol + * should explicitly generate a new initial counter value and key long + * before the \a size bytes overflow and wrap around. + * + * By default, the counter size is 16 which is the same as the block size + * of the underlying block cipher. + * + * \sa setIV() + */ +bool CTRCommon::setCounterSize(size_t size) +{ + if (size < 1 || size > 16) + return false; + counterStart = 16 - size; + return true; +} + +bool CTRCommon::setKey(const uint8_t *key, size_t len) +{ + // Verify the cipher's block size, just in case. + if (blockCipher->blockSize() != 16) + return false; + + // Set the key on the underlying block cipher. + return blockCipher->setKey(key, len); +} + +/** + * \brief Sets the initial counter value to use for future encryption and + * decryption operations. + * + * \param iv The initial counter value which must contain exactly 16 bytes. + * \param len The length of the counter value, which mut be 16. + * \return Returns false if \a len is not exactly 16. + * + * The precise method to generate the initial counter is not defined by + * this class. Usually higher level protocols like SSL/TLS and SSH + * specify how to construct the initial counter value. This class merely + * increments the counter every time a new block of keystream data is needed. + * + * \sa encrypt(), setCounterSize() + */ +bool CTRCommon::setIV(const uint8_t *iv, size_t len) +{ + if (len != 16) + return false; + memcpy(counter, iv, len); + posn = 16; + return true; +} + +void CTRCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len) +{ + while (len > 0) { + if (posn >= 16) { + // Generate a new encrypted counter block. + blockCipher->encryptBlock(state, counter); + posn = 0; + + // Increment the counter, taking care not to reveal + // any timing information about the starting value. + // We iterate through the entire counter region even + // if we could stop earlier because a byte is non-zero. + uint16_t temp = 1; + uint8_t index = 16; + while (index > counterStart) { + --index; + temp += counter[index]; + counter[index] = (uint8_t)temp; + temp >>= 8; + } + } + uint8_t templen = 16 - posn; + if (templen > len) + templen = len; + len -= templen; + while (templen > 0) { + *output++ = *input++ ^ state[posn++]; + --templen; + } + } +} + +void CTRCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len) +{ + encrypt(output, input, len); +} + +void CTRCommon::clear() +{ + blockCipher->clear(); + clean(counter); + clean(state); + posn = 16; +} + +/** + * \fn void CTRCommon::setBlockCipher(BlockCipher *cipher) + * \brief Sets the block cipher to use for this CTR object. + * + * \param cipher The block cipher to use to implement CTR mode, + * which must have a block size of 16 bytes (128 bits). + * + * \note This class only works with block ciphers whose block size is + * 16 bytes (128 bits). If the \a cipher has a different block size, + * then setKey() will fail and return false. + */ + +/** + * \class CTR CTR.h + * \brief Implementation of the Counter (CTR) mode for 128-bit block ciphers. + * + * Counter mode converts a block cipher into a stream cipher. The specific + * block cipher is passed as the template parameter T and the key is + * specified via the setKey() function. + * + * Keystream blocks are generated by encrypting an increasing counter value + * and XOR'ing it with each byte of input. The encrypt() and decrypt() + * operations are identical. + * + * The template parameter T must be a concrete subclass of BlockCipher + * indicating the specific block cipher to use. For example, the following + * creates a CTR object using AES256 as the underlying cipher: + * + * \code + * CTR ctr; + * ctr.setKey(key, 32); + * ctr.setIV(iv, 16); + * ctr.setCounterSize(4); + * ctr.encrypt(output, input, len); + * \endcode + * + * In this example, the last 4 bytes of the IV are incremented to count + * blocks. The remaining bytes are left unchanged from block to block. + * + * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + * + * \sa CFB, Cipher + */ + +/** + * \fn CTR::CTR() + * \brief Constructs a new CTR object for the 128-bit block cipher T. + */ diff --git a/libraries/Crypto/CTR.h b/libraries/Crypto/CTR.h new file mode 100644 index 00000000..477166e4 --- /dev/null +++ b/libraries/Crypto/CTR.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_CTR_h +#define CRYPTO_CTR_h + +#include "Cipher.h" +#include "BlockCipher.h" + +class CTRCommon : public Cipher +{ +public: + virtual ~CTRCommon(); + + size_t keySize() const; + size_t ivSize() const; + + bool setCounterSize(size_t size); + + bool setKey(const uint8_t *key, size_t len); + bool setIV(const uint8_t *iv, size_t len); + + void encrypt(uint8_t *output, const uint8_t *input, size_t len); + void decrypt(uint8_t *output, const uint8_t *input, size_t len); + + void clear(); + +protected: + CTRCommon(); + void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; } + +private: + BlockCipher *blockCipher; + uint8_t counter[16]; + uint8_t state[16]; + uint8_t posn; + uint8_t counterStart; +}; + +template +class CTR : public CTRCommon +{ +public: + CTR() { setBlockCipher(&cipher); } + +private: + T cipher; +}; + +#endif diff --git a/libraries/Crypto/Cipher.cpp b/libraries/Crypto/Cipher.cpp index bbf57f36..a3842bcf 100644 --- a/libraries/Crypto/Cipher.cpp +++ b/libraries/Crypto/Cipher.cpp @@ -33,7 +33,7 @@ * * All of the stream ciphers such as Arcfour and ChaCha inherit * directly from this class, together with block cipher modes such as - * CTR128 and CFB128. + * CTR and CFB. */ /** diff --git a/libraries/Crypto/examples/TestAES/TestAES.ino b/libraries/Crypto/examples/TestAES/TestAES.ino index 8da08b6a..b79b044f 100644 --- a/libraries/Crypto/examples/TestAES/TestAES.ino +++ b/libraries/Crypto/examples/TestAES/TestAES.ino @@ -36,9 +36,9 @@ struct TestVector byte ciphertext[16]; }; -// Define the test vectors from the FIPS specification. +// Define the ECB test vectors from the FIPS specification. static TestVector const testVectorAES128 = { - .name = "AES-128", + .name = "AES-128-ECB", .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}, .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, @@ -47,7 +47,7 @@ static TestVector const testVectorAES128 = { 0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A} }; static TestVector const testVectorAES192 = { - .name = "AES-192", + .name = "AES-192-ECB", .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, @@ -57,7 +57,7 @@ static TestVector const testVectorAES192 = { 0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91} }; static TestVector const testVectorAES256 = { - .name = "AES-256", + .name = "AES-256-ECB", .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -139,7 +139,8 @@ void perfCipher(BlockCipher *cipher, const struct TestVector *test) Serial.println(); } -void setup() { +void setup() +{ Serial.begin(9600); Serial.println(); @@ -157,5 +158,6 @@ void setup() { perfCipher(&aes256, &testVectorAES256); } -void loop() { +void loop() +{ } diff --git a/libraries/Crypto/examples/TestCTR/TestCTR.ino b/libraries/Crypto/examples/TestCTR/TestCTR.ino new file mode 100644 index 00000000..a1646628 --- /dev/null +++ b/libraries/Crypto/examples/TestCTR/TestCTR.ino @@ -0,0 +1,231 @@ +/* + * 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. + */ + +/* +This example runs tests on the CTR implementation to verify correct behaviour. +*/ + +#include +#include +#include +#include + +#define MAX_PLAINTEXT_SIZE 36 +#define MAX_CIPHERTEXT_SIZE 36 + +struct TestVector +{ + const char *name; + byte key[16]; + byte plaintext[MAX_PLAINTEXT_SIZE]; + byte ciphertext[MAX_CIPHERTEXT_SIZE]; + byte iv[16]; + size_t size; +}; + +// Test vectors for AES-128 in CTR mode from RFC 3686. +static TestVector const testVectorAES128CTR1 = { + .name = "AES-128-CTR #1", + .key = {0xAE, 0x68, 0x52, 0xF8, 0x12, 0x10, 0x67, 0xCC, + 0x4B, 0xF7, 0xA5, 0x76, 0x55, 0x77, 0xF3, 0x9E}, + .plaintext = {0x53, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x62, + 0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x6D, 0x73, 0x67}, + .ciphertext = {0xE4, 0x09, 0x5D, 0x4F, 0xB7, 0xA7, 0xB3, 0x79, + 0x2D, 0x61, 0x75, 0xA3, 0x26, 0x13, 0x11, 0xB8}, + .iv = {0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + .size = 16 +}; +static TestVector const testVectorAES128CTR2 = { + .name = "AES-128-CTR #2", + .key = {0x7E, 0x24, 0x06, 0x78, 0x17, 0xFA, 0xE0, 0xD7, + 0x43, 0xD6, 0xCE, 0x1F, 0x32, 0x53, 0x91, 0x63}, + .plaintext = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F}, + .ciphertext = {0x51, 0x04, 0xA1, 0x06, 0x16, 0x8A, 0x72, 0xD9, + 0x79, 0x0D, 0x41, 0xEE, 0x8E, 0xDA, 0xD3, 0x88, + 0xEB, 0x2E, 0x1E, 0xFC, 0x46, 0xDA, 0x57, 0xC8, + 0xFC, 0xE6, 0x30, 0xDF, 0x91, 0x41, 0xBE, 0x28}, + .iv = {0x00, 0x6C, 0xB6, 0xDB, 0xC0, 0x54, 0x3B, 0x59, + 0xDA, 0x48, 0xD9, 0x0B, 0x00, 0x00, 0x00, 0x01}, + .size = 32 +}; +static TestVector const testVectorAES128CTR3 = { + .name = "AES-128-CTR #3", + .key = {0x76, 0x91, 0xBE, 0x03, 0x5E, 0x50, 0x20, 0xA8, + 0xAC, 0x6E, 0x61, 0x85, 0x29, 0xF9, 0xA0, 0xDC}, + .plaintext = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23}, + .ciphertext = {0xC1, 0xCF, 0x48, 0xA8, 0x9F, 0x2F, 0xFD, 0xD9, + 0xCF, 0x46, 0x52, 0xE9, 0xEF, 0xDB, 0x72, 0xD7, + 0x45, 0x40, 0xA4, 0x2B, 0xDE, 0x6D, 0x78, 0x36, + 0xD5, 0x9A, 0x5C, 0xEA, 0xAE, 0xF3, 0x10, 0x53, + 0x25, 0xB2, 0x07, 0x2F}, + .iv = {0x00, 0xE0, 0x01, 0x7B, 0x27, 0x77, 0x7F, 0x3F, + 0x4A, 0x17, 0x86, 0xF0, 0x00, 0x00, 0x00, 0x01}, + .size = 36 +}; + +CTR ctraes128; + +byte buffer[128]; + +bool testCipher_N(Cipher *cipher, const struct TestVector *test, size_t inc) +{ + byte output[MAX_CIPHERTEXT_SIZE]; + size_t posn, len; + + cipher->clear(); + if (!cipher->setKey(test->key, cipher->keySize())) { + Serial.print("setKey "); + return false; + } + if (!cipher->setIV(test->iv, cipher->ivSize())) { + Serial.print("setIV "); + return false; + } + + memset(output, 0xBA, sizeof(output)); + + for (posn = 0; posn < test->size; posn += inc) { + len = test->size - posn; + if (len > inc) + len = inc; + cipher->encrypt(output + posn, test->plaintext + posn, len); + } + + if (memcmp(output, test->ciphertext, test->size) != 0) { + Serial.print(output[0], HEX); + Serial.print("->"); + Serial.print(test->ciphertext[0], HEX); + return false; + } + + cipher->setKey(test->key, cipher->keySize()); + cipher->setIV(test->iv, cipher->ivSize()); + + for (posn = 0; posn < test->size; posn += inc) { + len = test->size - posn; + if (len > inc) + len = inc; + cipher->decrypt(output + posn, test->ciphertext + posn, len); + } + + if (memcmp(output, test->plaintext, test->size) != 0) + return false; + + return true; +} + +void testCipher(Cipher *cipher, const struct TestVector *test) +{ + bool ok; + + Serial.print(test->name); + Serial.print(" ... "); + + ok = testCipher_N(cipher, test, test->size); + ok &= testCipher_N(cipher, test, 1); + ok &= testCipher_N(cipher, test, 2); + ok &= testCipher_N(cipher, test, 5); + ok &= testCipher_N(cipher, test, 8); + ok &= testCipher_N(cipher, test, 13); + ok &= testCipher_N(cipher, test, 16); + + if (ok) + Serial.println("Passed"); + else + Serial.println("Failed"); +} + +void perfCipherEncrypt(const char *name, Cipher *cipher, const struct TestVector *test) +{ + unsigned long start; + unsigned long elapsed; + int count; + + Serial.print(name); + Serial.print(" ... "); + + cipher->setKey(test->key, cipher->keySize()); + cipher->setIV(test->iv, cipher->ivSize()); + start = micros(); + for (count = 0; count < 500; ++count) { + cipher->encrypt(buffer, buffer, sizeof(buffer)); + } + elapsed = micros() - start; + + Serial.print(elapsed / (sizeof(buffer) * 500.0)); + Serial.print("us per byte, "); + Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed); + Serial.println(" bytes per second"); +} + +void perfCipherDecrypt(const char *name, Cipher *cipher, const struct TestVector *test) +{ + unsigned long start; + unsigned long elapsed; + int count; + + Serial.print(name); + Serial.print(" ... "); + + cipher->setKey(test->key, cipher->keySize()); + cipher->setIV(test->iv, cipher->ivSize()); + start = micros(); + for (count = 0; count < 500; ++count) { + cipher->decrypt(buffer, buffer, sizeof(buffer)); + } + elapsed = micros() - start; + + Serial.print(elapsed / (sizeof(buffer) * 500.0)); + Serial.print("us per byte, "); + Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed); + Serial.println(" bytes per second"); +} + +void setup() +{ + Serial.begin(9600); + + Serial.println(); + + Serial.println("Test Vectors:"); + testCipher(&ctraes128, &testVectorAES128CTR1); + testCipher(&ctraes128, &testVectorAES128CTR2); + testCipher(&ctraes128, &testVectorAES128CTR3); + + Serial.println(); + + Serial.println("Performance Tests:"); + perfCipherEncrypt("AES-128-CTR Encrypt", &ctraes128, &testVectorAES128CTR1); + perfCipherDecrypt("AES-128-CTR Decrypt", &ctraes128, &testVectorAES128CTR1); +} + +void loop() +{ +}