diff --git a/doc/crypto.dox b/doc/crypto.dox index 45564857..e9a3aa82 100644 --- a/doc/crypto.dox +++ b/doc/crypto.dox @@ -27,7 +27,7 @@ \section crypto_algorithms Supported Algorithms \li Block ciphers: AES128, AES192, AES256, Speck -\li Block cipher modes: CTR, CFB, CBC, OFB, EAX, GCM +\li Block cipher modes: CTR, CFB, CBC, OFB, EAX, GCM, XTS \li Stream ciphers: ChaCha \li Authenticated encryption with associated data (AEAD): ChaChaPoly, EAX, GCM \li Hash algorithms: SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes) diff --git a/doc/mainpage.dox b/doc/mainpage.dox index 052fd0dc..b6b2339a 100644 --- a/doc/mainpage.dox +++ b/doc/mainpage.dox @@ -92,7 +92,7 @@ realtime clock and the LCD library to implement an alarm clock. \section main_Crypto Cryptographic Library \li Block ciphers: AES128, AES192, AES256, Speck -\li Block cipher modes: CTR, CFB, CBC, OFB, EAX, GCM +\li Block cipher modes: CTR, CFB, CBC, OFB, EAX, GCM, XTS \li Stream ciphers: ChaCha \li Authenticated encryption with associated data (AEAD): ChaChaPoly, EAX, GCM \li Hash algorithms: SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes) diff --git a/libraries/Crypto/GF128.cpp b/libraries/Crypto/GF128.cpp index 3924392d..60c1b2ff 100644 --- a/libraries/Crypto/GF128.cpp +++ b/libraries/Crypto/GF128.cpp @@ -309,7 +309,7 @@ void GF128::mul(uint32_t Y[4], const uint32_t H[4]) * block, the modes multiply the nonce by 2 in the GF(2^128) field every * block. This function is provided to help with implementing such modes. * - * \sa dblEAX(), mul() + * \sa dblEAX(), dblXTS(), mul() */ void GF128::dbl(uint32_t V[4]) { @@ -401,7 +401,7 @@ void GF128::dbl(uint32_t V[4]) * References: https://en.wikipedia.org/wiki/EAX_mode, * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html * - * \sa dbl(), mul() + * \sa dbl(), dblXTS(), mul() */ void GF128::dblEAX(uint32_t V[4]) { @@ -478,3 +478,94 @@ void GF128::dblEAX(uint32_t V[4]) V[3] = htobe32(V3); #endif } + +/** + * \brief Doubles a value in the GF(2^128) field using XTS conventions. + * + * \param V The value to double, and the result. This array is + * assumed to be in littlen-endian order on entry and exit. + * + * This function differs from dbl() that it uses the conventions of XTS mode + * instead of those of NIST SP 800-38D (GCM). The two operations have + * equivalent security but the bits are ordered differently with the + * value shifted left instead of right. + * + * References: IEEE Std. 1619-2007, XTS-AES + * + * \sa dbl(), dblEAX(), mul() + */ +void GF128::dblXTS(uint32_t V[4]) +{ +#if defined(__AVR__) + __asm__ __volatile__ ( + "ld r16,Z\n" + "ldd r17,Z+1\n" + "ldd r18,Z+2\n" + "ldd r19,Z+3\n" + "lsl r16\n" + "rol r17\n" + "rol r18\n" + "rol r19\n" + "std Z+1,r17\n" + "std Z+2,r18\n" + "std Z+3,r19\n" + "ldd r17,Z+4\n" + "ldd r18,Z+5\n" + "ldd r19,Z+6\n" + "ldd r20,Z+7\n" + "rol r17\n" + "rol r18\n" + "rol r19\n" + "rol r20\n" + "std Z+4,r17\n" + "std Z+5,r18\n" + "std Z+6,r19\n" + "std Z+7,r20\n" + "ldd r17,Z+8\n" + "ldd r18,Z+9\n" + "ldd r19,Z+10\n" + "ldd r20,Z+11\n" + "rol r17\n" + "rol r18\n" + "rol r19\n" + "rol r20\n" + "std Z+8,r17\n" + "std Z+9,r18\n" + "std Z+10,r19\n" + "std Z+11,r20\n" + "ldd r17,Z+12\n" + "ldd r18,Z+13\n" + "ldd r19,Z+14\n" + "ldd r20,Z+15\n" + "rol r17\n" + "rol r18\n" + "rol r19\n" + "rol r20\n" + "std Z+12,r17\n" + "std Z+13,r18\n" + "std Z+14,r19\n" + "std Z+15,r20\n" + "mov r17,__zero_reg__\n" + "sbc r17,__zero_reg__\n" + "andi r17,0x87\n" + "eor r16,r17\n" + "st Z,r16\n" + : : "z"(V) + : "r16", "r17", "r18", "r19", "r20" + ); +#else + uint32_t V0 = le32toh(V[0]); + uint32_t V1 = le32toh(V[1]); + uint32_t V2 = le32toh(V[2]); + uint32_t V3 = le32toh(V[3]); + uint32_t mask = ((~(V3 >> 31)) + 1) & 0x00000087; + V3 = (V3 << 1) | (V2 >> 31); + V2 = (V2 << 1) | (V1 >> 31); + V1 = (V1 << 1) | (V0 >> 31); + V0 = (V0 << 1) ^ mask; + V[0] = htole32(V0); + V[1] = htole32(V1); + V[2] = htole32(V2); + V[3] = htole32(V3); +#endif +} diff --git a/libraries/Crypto/GF128.h b/libraries/Crypto/GF128.h index 715daa98..96d63df2 100644 --- a/libraries/Crypto/GF128.h +++ b/libraries/Crypto/GF128.h @@ -36,6 +36,7 @@ public: static void mul(uint32_t Y[4], const uint32_t H[4]); static void dbl(uint32_t V[4]); static void dblEAX(uint32_t V[4]); + static void dblXTS(uint32_t V[4]); }; #endif diff --git a/libraries/Crypto/XTS.cpp b/libraries/Crypto/XTS.cpp new file mode 100644 index 00000000..87c40440 --- /dev/null +++ b/libraries/Crypto/XTS.cpp @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2016 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 "XTS.h" +#include "Crypto.h" +#include "GF128.h" +#include + +/** + * \class XTSCommon XTS.h + * \brief Concrete base class to assist with implementing XTS mode for + * 128-bit block ciphers. + * + * References: IEEE Std. 1619-2007, NIST SP 800-38E, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX. + * + * \sa XTS, XTSSingleKey + */ + +/** + * \brief Constructs an XTS object with a default sector size of 512 bytes. + */ +XTSCommon::XTSCommon() + : sectSize(512) +{ +} + +/** + * \brief Clears all sensitive information and destroys this object. + */ +XTSCommon::~XTSCommon() +{ + clean(twk); +} + +/** + * \brief Gets the size of the key for XTS mode. + * + * The key size for XTS mode is twice the size of the underlying + * block cipher key size. + * + * \sa setKey(), tweakSize() + */ +size_t XTSCommon::keySize() const +{ + return blockCipher1->keySize() * 2; +} + +/** + * \brief Gets the maximum supported size for the tweak. + * + * This function returns 16, which indicates that any tweak up to 16 bytes + * in size can be specified via setTweak(). + */ +size_t XTSCommon::tweakSize() const +{ + return 16; +} + +/** + * \fn size_t XTSCommon::sectorSize() const + * \brief Gets the size of sectors encrypted or decrypted by this class. + * + * The default value is 512 bytes. + * + * \sa setSectorSize() + */ + +/** + * \brief Sets the size of sectors encrypted or decrypted by this class. + * + * \param size The sector size in bytes, which must be greater than or + * equal to 16. + * + * \return Returns false if \a size is less than 16. + * + * \sa sectorSize(), encryptSector() + */ +bool XTSCommon::setSectorSize(size_t size) +{ + if (size < 16) + return false; + sectSize = size; + return true; +} + +/** + * \brief Sets the key to use for XTS mode. + * + * \param key Points to the key. + * \param len The size of the key in bytes which must be twice the + * size of the underlying block cipher's key size. + * + * \return Returns true if the key was set or false if \a len was incorrect. + * + * This function should be followed by a call to setTweak() to specify + * the sector-specific tweak. + * + * \sa keySize(), setTweak(), encryptSector() + */ +bool XTSCommon::setKey(const uint8_t *key, size_t len) +{ + if (!blockCipher1->setKey(key, len / 2)) + return false; + return blockCipher2->setKey(key + len / 2, len - (len / 2)); +} + +/** + * \brief Sets the tweak value for the current sector to encrypt or decrypt. + * + * \param tweak Points to the tweak. + * \param len The length of the tweak which must be less than or equal to 16. + * + * \return Returns true if the tweak was set or false if \a len was incorrect. + * + * If \a len is less than 16, then the \a tweak will be zero-padded to + * 16 bytes. + * + * The \a tweak is encrypted with the second half of the XTS key to generate + * the actual tweak value for the sector. + * + * \sa tweakSize(), setKey(), encryptSector() + */ +bool XTSCommon::setTweak(const uint8_t *tweak, size_t len) +{ + if (len > 16) + return false; + memcpy(twk, tweak, len); + memset(((uint8_t *)twk) + len, 0, 16 - len); + blockCipher2->encryptBlock((uint8_t *)twk, (uint8_t *)twk); + return true; +} + +#define xorTweak(output, input, tweak) \ + do { \ + for (uint8_t i = 0; i < 16; ++i) \ + (output)[i] = (input)[i] ^ ((const uint8_t *)(tweak))[i]; \ + } while (0) + +/** + * \brief Encrypts an entire sector of data. + * + * \param output The output buffer to write the ciphertext to, which can + * be the same as \a input. + * \param input The input buffer to read the plaintext from. + * + * The \a input and \a output buffers must be at least sectorSize() + * bytes in length. + * + * \sa decryptSector(), setKey(), setTweak() + */ +void XTSCommon::encryptSector(uint8_t *output, const uint8_t *input) +{ + size_t sectLast = sectSize & ~15; + size_t posn = 0; + uint32_t t[4]; + memcpy(t, twk, sizeof(t)); + while (posn < sectLast) { + // Process all complete 16-byte blocks. + xorTweak(output, input, t); + blockCipher1->encryptBlock(output, output); + xorTweak(output, output, t); + GF128::dblXTS(t); + input += 16; + output += 16; + posn += 16; + } + if (posn < sectSize) { + // Perform ciphertext stealing on the final partial block. + uint8_t leftOver = sectSize - posn; + output -= 16; + while (leftOver > 0) { + // Swap the left-over bytes in the last two blocks. + --leftOver; + uint8_t temp = input[leftOver]; + output[leftOver + 16] = output[leftOver]; + output[leftOver] = temp; + } + xorTweak(output, output, t); + blockCipher1->encryptBlock(output, output); + xorTweak(output, output, t); + } +} + +/** + * \brief Decrypts an entire sector of data. + * + * \param output The output buffer to write the plaintext to, which can + * be the same as \a input. + * \param input The input buffer to read the ciphertext from. + * + * The \a input and \a output buffers must be at least sectorSize() + * bytes in length. + * + * \sa encryptSector(), setKey(), setTweak() + */ +void XTSCommon::decryptSector(uint8_t *output, const uint8_t *input) +{ + size_t sectLast = sectSize & ~15; + size_t posn = 0; + uint32_t t[4]; + memcpy(t, twk, sizeof(t)); + if (sectLast != sectSize) + sectLast -= 16; + while (posn < sectLast) { + // Process all complete 16-byte blocks. + xorTweak(output, input, t); + blockCipher1->decryptBlock(output, output); + xorTweak(output, output, t); + GF128::dblXTS(t); + input += 16; + output += 16; + posn += 16; + } + if (posn < sectSize) { + // Perform ciphertext stealing on the final two blocks. + uint8_t leftOver = sectSize - 16 - posn; + uint32_t u[4]; + + // Decrypt the second-last block of ciphertext to recover + // the last partial block of plaintext. We need to use + // dblXTS(t) as the tweak for this block. Save the current + // tweak in "u" for use later. + memcpy(u, t, sizeof(t)); + GF128::dblXTS(t); + xorTweak(output, input, t); + blockCipher1->decryptBlock(output, output); + xorTweak(output, output, t); + + // Swap the left-over bytes in the last two blocks. + while (leftOver > 0) { + --leftOver; + uint8_t temp = input[leftOver + 16]; + output[leftOver + 16] = output[leftOver]; + output[leftOver] = temp; + } + + // Decrypt the second-last block using the second-last tweak. + xorTweak(output, output, u); + blockCipher1->decryptBlock(output, output); + xorTweak(output, output, u); + } +} + +/** + * \brief Clears all security-sensitive state from this XTS object. + */ +void XTSCommon::clear() +{ + clean(twk); + blockCipher1->clear(); + blockCipher2->clear(); +} + +/** + * \fn void XTSCommon::setBlockCiphers(BlockCipher *cipher1, BlockCipher *cipher2) + * \brief Sets the two block ciphers to use for XTS mode. + * + * \param cipher1 Points to the first block cipher object, which must be + * capable of both encryption and decryption. + * \param cipher2 Points to the second block cipher object, which must be + * capable of both encryption but does not need to be capable of decryption. + * + * Both block ciphers must have a 128-bit block size. + */ + +/** + * \class XTSSingleKeyCommon XTS.h + * \brief Concrete base class to assist with implementing single-key XTS + * mode for 128-bit block ciphers. + * + * References: IEEE Std. 1619-2007, NIST SP 800-38E, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX. + * + * \sa XTSSingleKey, XTSCommon + */ + +/** + * \fn XTSSingleKeyCommon::XTSSingleKeyCommon() + * \brief Constructs an XTS object with a default sector size of 512 bytes. + */ + +/** + * \brief Clears all sensitive information and destroys this object. + */ +XTSSingleKeyCommon::~XTSSingleKeyCommon() +{ +} + +/** + * \brief Gets the size of the key for single-pkey XTS mode. + * + * The key size for single-key XTS mode is the same as the key size + * for the underlying block cipher. + * + * \sa setKey(), tweakSize() + */ +size_t XTSSingleKeyCommon::keySize() const +{ + return blockCipher1->keySize(); +} + +/** + * \brief Sets the key to use for single-keyh XTS mode. + * + * \param key Points to the key. + * \param len The size of the key in bytes which must be same as the + * size of the underlying block cipher. + * + * \return Returns true if the key was set or false if \a len was incorrect. + * + * This function should be followed by a call to setTweak() to specify + * the sector-specific tweak. + * + * \sa keySize(), setTweak(), encryptSector() + */ +bool XTSSingleKeyCommon::setKey(const uint8_t *key, size_t len) +{ + return blockCipher1->setKey(key, len); +} + +/** + * \class XTS XTS.h + * \brief Implementation of the XTS mode for 128-bit block ciphers. + * + * XTS mode implements the XEX tweakable block cipher mode with ciphertext + * stealing for data that isn't a multiple of the 128-bit block size. + * + * XTS was designed for use in disk encryption where a large number of + * equal-sized "sectors" need to be encrypted in a way that information + * from one sector cannot be used to decrypt the other sectors. The mode + * combines the key with a sector-specific "tweak" which is usually + * based on the sector number. + * + * Some Arduino systems have SD cards, but typically embedded systems + * do not have disk drives. However, XTS can still be useful on + * Arduino systems with lots of EEPROM or flash memory. If the application + * needs to store critical security parameters like private keys then + * XTS can be used to encrypt non-volatile memory to protect the parameters. + * + * The following example encrypts a sector using XTS mode: + * + * \code + * XTS xts; + * xts.setSectorSize(520); + * xts.setKey(key, 64); // Twice the AES256 key size. + * xts.setTweak(sectorNumber, sizeof(sectorNumber)); + * xts.encryptSector(output, input); + * \endcode + * + * XTS keys are twice the size of the underlying block cipher + * (AES256 in the above example). The XTS key is divided into two halves. + * The first half is used to encrypt the plaintext and the second half + * is used to encrypt the sector-specific tweak. The same key can be + * used for both, in which case XTS is equivalent to the original + * XEX design upon which XTS was based. The companion XTSSingleKey class + * can be used for single-key scenarios. + * + * The template parameter must be a concrete subclass of BlockCipher + * indicating the specific block cipher to use. The example above uses + * AES256 as the underlying cipher. + * + * It is also possible to specify two different block ciphers, as long as + * they have the same key size. Because the second half of the key is only + * used to encrypt tweaks and never decrypt, a reduced block cipher + * implementation like SpeckTiny that only supports encryption can be + * used for the second block cipher: + * + * \code + * XTS xts; + * \endcode + * + * This might save some memory that would otherwise be needed for the + * decryption key schedule of the second block cipher. XTSSingleKey provides + * another method to save memory. + * + * References: IEEE Std. 1619-2007, NIST SP 800-38E, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX. + * + * \sa XTSSingleKey, XTSCommon + */ + +/** + * \fn XTS::XTS() + * \brief Constructs an object for encrypting sectors in XTS mode. + * + * This constructor should be followed by a call to setSectorSize(). + * The default sector size is 512 bytes. + */ + +/** + * \fn XTS::~XTS() + * \brief Clears all sensitive information and destroys this object. + */ + +/** + * \class XTSSingleKey XTS.h + * \brief Implementation of the single-key XTS mode for 128-bit block ciphers. + * + * XTS mode normally uses two keys to encrypt plaintext and the + * sector-specific tweak values. This class uses the same key for + * both purposes, which can help save memory. + * + * References: IEEE Std. 1619-2007, NIST SP 800-38E, a href="http://web.cs.ucdavis.edu/~rogaway/papers/offsets.pdf">XEX. + * + * \sa XTS, XTSSingleKeyCommon + */ + +/** + * \fn XTSSingleKey::XTSSingleKey() + * \brief Constructs an object for encrypting sectors in XTS mode + * with a single key instead of two split keys. + * + * This constructor should be followed by a call to setSectorSize(). + * The default sector size is 512 bytes. + */ + +/** + * \fn XTSSingleKey::~XTSSingleKey() + * \brief Clears all sensitive information and destroys this object. + */ diff --git a/libraries/Crypto/XTS.h b/libraries/Crypto/XTS.h new file mode 100644 index 00000000..8b745ad6 --- /dev/null +++ b/libraries/Crypto/XTS.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 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_XTS_h +#define CRYPTO_XTS_h + +#include "BlockCipher.h" + +class XTSSingleKeyCommon; + +class XTSCommon +{ +public: + virtual ~XTSCommon(); + + virtual size_t keySize() const; + size_t tweakSize() const; + + size_t sectorSize() const { return sectSize; } + bool setSectorSize(size_t size); + + virtual bool setKey(const uint8_t *key, size_t len); + bool setTweak(const uint8_t *tweak, size_t len); + + void encryptSector(uint8_t *output, const uint8_t *input); + void decryptSector(uint8_t *output, const uint8_t *input); + + void clear(); + +protected: + XTSCommon(); + void setBlockCiphers(BlockCipher *cipher1, BlockCipher *cipher2) + { + blockCipher1 = cipher1; + blockCipher2 = cipher2; + } + +private: + BlockCipher *blockCipher1; + BlockCipher *blockCipher2; + uint32_t twk[4]; + size_t sectSize; + + friend class XTSSingleKeyCommon; +}; + +class XTSSingleKeyCommon : public XTSCommon +{ +public: + virtual ~XTSSingleKeyCommon(); + + size_t keySize() const; + bool setKey(const uint8_t *key, size_t len); + +protected: + XTSSingleKeyCommon() : XTSCommon() {} +}; + +template +class XTS : public XTSCommon +{ +public: + XTS() { setBlockCiphers(&cipher1, &cipher2); } + ~XTS() {} + +private: + T1 cipher1; + T2 cipher2; +}; + +template +class XTSSingleKey : public XTSSingleKeyCommon +{ +public: + XTSSingleKey() { setBlockCiphers(&cipher, &cipher); } + ~XTSSingleKey() {} + +private: + T cipher; +}; + +#endif diff --git a/libraries/Crypto/examples/TestXTS/TestXTS.ino b/libraries/Crypto/examples/TestXTS/TestXTS.ino new file mode 100644 index 00000000..7fd64e69 --- /dev/null +++ b/libraries/Crypto/examples/TestXTS/TestXTS.ino @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2016 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 XTS implementation to verify correct behaviour. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_SECTOR_SIZE 64 + +struct TestVector +{ + const char *name; + byte key1[16]; + byte key2[16]; + byte plaintext[MAX_SECTOR_SIZE]; + byte ciphertext[MAX_SECTOR_SIZE]; + byte tweak[16]; + size_t sectorSize; +}; + +// Selected test vectors for XTS-AES-128 from: +// http://libeccio.di.unisa.it/Crypto14/Lab/p1619.pdf +static TestVector const testVectorXTSAES128_1 PROGMEM = { + .name = "XTS-AES-128 #1", + .key1 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .key2 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .plaintext = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + .ciphertext = {0x91, 0x7c, 0xf6, 0x9e, 0xbd, 0x68, 0xb2, 0xec, + 0x9b, 0x9f, 0xe9, 0xa3, 0xea, 0xdd, 0xa6, 0x92, + 0xcd, 0x43, 0xd2, 0xf5, 0x95, 0x98, 0xed, 0x85, + 0x8c, 0x02, 0xc2, 0x65, 0x2f, 0xbf, 0x92, 0x2e}, + .tweak = {0x00}, + .sectorSize = 32 +}; +static TestVector const testVectorXTSAES128_2 PROGMEM = { + .name = "XTS-AES-128 #2", + .key1 = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, + .key2 = {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, + .plaintext = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, + .ciphertext = {0xc4, 0x54, 0x18, 0x5e, 0x6a, 0x16, 0x93, 0x6e, + 0x39, 0x33, 0x40, 0x38, 0xac, 0xef, 0x83, 0x8b, + 0xfb, 0x18, 0x6f, 0xff, 0x74, 0x80, 0xad, 0xc4, + 0x28, 0x93, 0x82, 0xec, 0xd6, 0xd3, 0x94, 0xf0}, + .tweak = {0x33, 0x33, 0x33, 0x33, 0x33}, + .sectorSize = 32 +}; +static TestVector const testVectorXTSAES128_3 PROGMEM = { + .name = "XTS-AES-128 #3", + .key1 = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0}, + .key2 = {0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, + .plaintext = {0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, + .ciphertext = {0xaf, 0x85, 0x33, 0x6b, 0x59, 0x7a, 0xfc, 0x1a, + 0x90, 0x0b, 0x2e, 0xb2, 0x1e, 0xc9, 0x49, 0xd2, + 0x92, 0xdf, 0x4c, 0x04, 0x7e, 0x0b, 0x21, 0x53, + 0x21, 0x86, 0xa5, 0x97, 0x1a, 0x22, 0x7a, 0x89}, + .tweak = {0x33, 0x33, 0x33, 0x33, 0x33}, + .sectorSize = 32 +}; +static TestVector const testVectorXTSAES128_4 PROGMEM = { + // 512 byte test vector from the spec truncated to the first 64 bytes. + .name = "XTS-AES-128 #4", + .key1 = {0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45, + 0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26}, + .key2 = {0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93, + 0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95}, + .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, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f}, + .ciphertext = {0x27, 0xa7, 0x47, 0x9b, 0xef, 0xa1, 0xd4, 0x76, + 0x48, 0x9f, 0x30, 0x8c, 0xd4, 0xcf, 0xa6, 0xe2, + 0xa9, 0x6e, 0x4b, 0xbe, 0x32, 0x08, 0xff, 0x25, + 0x28, 0x7d, 0xd3, 0x81, 0x96, 0x16, 0xe8, 0x9c, + 0xc7, 0x8c, 0xf7, 0xf5, 0xe5, 0x43, 0x44, 0x5f, + 0x83, 0x33, 0xd8, 0xfa, 0x7f, 0x56, 0x00, 0x00, + 0x05, 0x27, 0x9f, 0xa5, 0xd8, 0xb5, 0xe4, 0xad, + 0x40, 0xe7, 0x36, 0xdd, 0xb4, 0xd3, 0x54, 0x12}, + .tweak = {0x00}, + .sectorSize = 64 +}; +static TestVector const testVectorXTSAES128_15 PROGMEM = { + .name = "XTS-AES-128 #15", + .key1 = {0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, + 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf2, 0xf1, 0xf0}, + .key2 = {0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, + 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0}, + .plaintext = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10}, + .ciphertext = {0x6c, 0x16, 0x25, 0xdb, 0x46, 0x71, 0x52, 0x2d, + 0x3d, 0x75, 0x99, 0x60, 0x1d, 0xe7, 0xca, 0x09, + 0xed}, + .tweak = {0x9a, 0x78, 0x56, 0x34, 0x12}, + .sectorSize = 17 +}; +// This test vector is from: +// https://github.com/heisencoder/XTS-AES/blob/master/testvals/xts.4 +// We use this one because the main specification doesn't have an odd +// block size greater than 32 bytes but less than 64 bytes. +static TestVector const testVectorXTSAES128_16 PROGMEM = { + .name = "XTS-AES-128 #16", + .key1 = {0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90, 0x45, + 0x23, 0x53, 0x60, 0x28, 0x74, 0x71, 0x35, 0x26}, + .key2 = {0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x97, 0x93, + 0x23, 0x84, 0x62, 0x64, 0x33, 0x83, 0x27, 0x95}, + .plaintext = {0x50, 0x00, 0xec, 0xa5, 0xa1, 0xf6, 0xa4, 0x93, + 0x78, 0x03, 0x0d, 0x9e, 0xe8, 0x05, 0xac, 0xef, + 0x46, 0x0f, 0x31, 0x4e, 0xe0, 0x4b, 0xb5, 0x14, + 0x03, 0x4e, 0xb2, 0x7f, 0xb8, 0xdf, 0x2b, 0xc8, + 0x12, 0xae, 0x5b, 0xdf, 0x8c}, + .ciphertext = {0xe5, 0x9e, 0x6f, 0x23, 0x3b, 0xe0, 0xe0, 0x83, + 0x04, 0x83, 0xc6, 0xbd, 0x4e, 0x82, 0xf4, 0xc3, + 0x95, 0x43, 0x55, 0x8a, 0x25, 0xe3, 0xdb, 0x60, + 0xa5, 0x53, 0xa5, 0x94, 0x81, 0x45, 0xa6, 0xff, + 0xb5, 0xe6, 0xbe, 0x1d, 0xb5}, + .tweak = {0x33, 0x22, 0x11, 0x00}, + .sectorSize = 37 +}; + +XTS *xtsaes128; +TestVector testVector; + +byte buffer[MAX_SECTOR_SIZE]; + +#if defined(__AVR__) + +void _printProgMem(const char *str) +{ + for (;;) { + uint8_t ch = pgm_read_byte((const uint8_t *)str); + if (!ch) + break; + Serial.write(ch); + ++str; + } +} + +#define printProgMem(str) \ + do { \ + static char const temp_str[] PROGMEM = str; \ + _printProgMem(temp_str); \ + } while (0) + +#define printlnProgMem(str) \ + do { \ + static char const temp_str[] PROGMEM = str; \ + _printProgMem(temp_str); \ + Serial.println(); \ + } while (0) + +#else + +#define printProgMem(str) \ + Serial.print(str) + +#define printlnProgMem(str) \ + Serial.println(str) + +#endif + +void testXTS(XTSCommon *cipher, const struct TestVector *test) +{ + memcpy_P(&testVector, test, sizeof(testVector)); + + Serial.print(testVector.name); + printProgMem(" Encrypt ... "); + + cipher->setSectorSize(testVector.sectorSize); + cipher->setKey(testVector.key1, 32); + cipher->setTweak(testVector.tweak, sizeof(testVector.tweak)); + cipher->encryptSector(buffer, testVector.plaintext); + + if (!memcmp(buffer, testVector.ciphertext, testVector.sectorSize)) + printlnProgMem("Passed"); + else + printlnProgMem("Failed"); + + Serial.print(testVector.name); + printProgMem(" Decrypt ... "); + + cipher->decryptSector(buffer, testVector.ciphertext); + + if (!memcmp(buffer, testVector.plaintext, testVector.sectorSize)) + printlnProgMem("Passed"); + else + printlnProgMem("Failed"); + + Serial.print(testVector.name); + printProgMem(" Encrypt In-Place ... "); + + memcpy(buffer, testVector.plaintext, testVector.sectorSize); + cipher->encryptSector(buffer, buffer); + + if (!memcmp(buffer, testVector.ciphertext, testVector.sectorSize)) + printlnProgMem("Passed"); + else + printlnProgMem("Failed"); + + Serial.print(testVector.name); + printProgMem(" Decrypt In-Place ... "); + + memcpy(buffer, testVector.ciphertext, testVector.sectorSize); + cipher->decryptSector(buffer, buffer); + + if (!memcmp(buffer, testVector.plaintext, testVector.sectorSize)) + printlnProgMem("Passed"); + else + printlnProgMem("Failed"); +} + +void perfEncrypt(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32) +{ + unsigned long start; + unsigned long elapsed; + int count; + + memcpy_P(&testVector, test, sizeof(testVector)); + + Serial.print(name); + printProgMem(" ... "); + + cipher->setSectorSize(sizeof(buffer)); + cipher->setKey(testVector.key1, keySize); + cipher->setTweak(testVector.tweak, sizeof(testVector.tweak)); + memset(buffer, 0xAA, sizeof(buffer)); + start = micros(); + for (count = 0; count < 500; ++count) { + cipher->encryptSector(buffer, buffer); + } + elapsed = micros() - start; + + Serial.print(elapsed / (sizeof(buffer) * 500.0)); + printProgMem("us per byte, "); + Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed); + printlnProgMem(" bytes per second"); +} + +void perfDecrypt(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32) +{ + unsigned long start; + unsigned long elapsed; + int count; + + memcpy_P(&testVector, test, sizeof(testVector)); + + Serial.print(name); + printProgMem(" ... "); + + cipher->setSectorSize(sizeof(buffer)); + cipher->setKey(testVector.key1, keySize); + cipher->setTweak(testVector.tweak, sizeof(testVector.tweak)); + start = micros(); + for (count = 0; count < 500; ++count) { + cipher->decryptSector(buffer, buffer); + } + elapsed = micros() - start; + + Serial.print(elapsed / (sizeof(buffer) * 500.0)); + printProgMem("us per byte, "); + Serial.print((sizeof(buffer) * 500.0 * 1000000.0) / elapsed); + printlnProgMem(" bytes per second"); +} + +void perfSetKey(const char *name, XTSCommon *cipher, const struct TestVector *test, size_t keySize = 32) +{ + unsigned long start; + unsigned long elapsed; + int count; + + memcpy_P(&testVector, test, sizeof(testVector)); + + Serial.print(name); + printProgMem(" ... "); + + start = micros(); + for (count = 0; count < 2000; ++count) { + cipher->setKey(testVector.key1, keySize); + } + elapsed = micros() - start; + + Serial.print(elapsed / 2000.0); + printProgMem("us per operation, "); + Serial.print((2000.0 * 1000000.0) / elapsed); + printlnProgMem(" operations per second"); +} + +void perfSetTweak(const char *name, XTSCommon *cipher, const struct TestVector *test) +{ + unsigned long start; + unsigned long elapsed; + int count; + + memcpy_P(&testVector, test, sizeof(testVector)); + + Serial.print(name); + printProgMem(" ... "); + + start = micros(); + for (count = 0; count < 2000; ++count) { + cipher->setTweak(testVector.tweak, sizeof(testVector.tweak)); + } + elapsed = micros() - start; + + Serial.print(elapsed / 2000.0); + printProgMem("us per operation, "); + Serial.print((2000.0 * 1000000.0) / elapsed); + printlnProgMem(" operations per second"); +} + +void setup() +{ + Serial.begin(9600); + + Serial.println(); + + xtsaes128 = new XTS(); + printlnProgMem("State Sizes:"); + printProgMem("XTS ... "); + Serial.println(sizeof(*xtsaes128)); + printProgMem("XTS ... "); + Serial.println(sizeof(XTS)); + printProgMem("XTS ... "); + Serial.println(sizeof(XTS)); + printProgMem("XTS ... "); + Serial.println(sizeof(XTS)); + printProgMem("XTS ... "); + Serial.println(sizeof(XTS)); + + printProgMem("XTSSingleKey ... "); + Serial.println(sizeof(XTSSingleKey)); + printProgMem("XTSSingleKey ... "); + Serial.println(sizeof(XTSSingleKey)); + printProgMem("XTSSingleKey ... "); + Serial.println(sizeof(XTSSingleKey)); + printProgMem("XTSSingleKey ... "); + Serial.println(sizeof(XTSSingleKey)); + + Serial.println(); + + printlnProgMem("Test Vectors:"); + testXTS(xtsaes128, &testVectorXTSAES128_1); + testXTS(xtsaes128, &testVectorXTSAES128_2); + testXTS(xtsaes128, &testVectorXTSAES128_3); + testXTS(xtsaes128, &testVectorXTSAES128_4); + testXTS(xtsaes128, &testVectorXTSAES128_15); + testXTS(xtsaes128, &testVectorXTSAES128_16); + + Serial.println(); + + printlnProgMem("Performance Tests:"); + Serial.println(); + + printlnProgMem("XTS-AES-128:"); + perfEncrypt("Encrypt", xtsaes128, &testVectorXTSAES128_4); + perfDecrypt("Decrypt", xtsaes128, &testVectorXTSAES128_4); + perfSetKey("Set Key", xtsaes128, &testVectorXTSAES128_4); + perfSetTweak("Set Tweak", xtsaes128, &testVectorXTSAES128_4); + delete xtsaes128; + Serial.println(); + + printlnProgMem("XTS-AES-128 Single Key:"); + XTSSingleKey *singleaes128 = new XTSSingleKey(); + perfEncrypt("Encrypt", singleaes128, &testVectorXTSAES128_4, 16); + perfDecrypt("Decrypt", singleaes128, &testVectorXTSAES128_4, 16); + perfSetKey("Set Key", singleaes128, &testVectorXTSAES128_4, 16); + perfSetTweak("Set Tweak", singleaes128, &testVectorXTSAES128_4); + delete singleaes128; + Serial.println(); + + printlnProgMem("XTS-AES-256 Single Key:"); + XTSSingleKey *xtsaes256 = new XTSSingleKey(); + perfEncrypt("Encrypt", xtsaes256, &testVectorXTSAES128_4, 32); + perfDecrypt("Decrypt", xtsaes256, &testVectorXTSAES128_4, 32); + perfSetKey("Set Key", xtsaes256, &testVectorXTSAES128_4, 32); + perfSetTweak("Set Tweak", xtsaes256, &testVectorXTSAES128_4); + delete xtsaes256; + Serial.println(); + + printlnProgMem("XTS-SpeckSmall-256:"); + XTS *xtsspeck = new XTS(); + perfEncrypt("Encrypt", xtsspeck, &testVectorXTSAES128_4, 64); + perfDecrypt("Decrypt", xtsspeck, &testVectorXTSAES128_4, 64); + perfSetKey("Set Key", xtsspeck, &testVectorXTSAES128_4, 64); + perfSetTweak("Set Tweak", xtsspeck, &testVectorXTSAES128_4); + delete xtsspeck; + Serial.println(); + + printlnProgMem("XTS-SpeckSmall-256 Single Key:"); + XTSSingleKey *singlespeck = new XTSSingleKey(); + perfEncrypt("Encrypt", singlespeck, &testVectorXTSAES128_4, 32); + perfDecrypt("Decrypt", singlespeck, &testVectorXTSAES128_4, 32); + perfSetKey("Set Key", singlespeck, &testVectorXTSAES128_4, 32); + perfSetTweak("Set Tweak", singlespeck, &testVectorXTSAES128_4); + delete singlespeck; + Serial.println(); + + printlnProgMem("XTS-Speck-256:"); + XTS *xtsspeck2 = new XTS(); + perfEncrypt("Encrypt", xtsspeck2, &testVectorXTSAES128_4, 64); + perfDecrypt("Decrypt", xtsspeck2, &testVectorXTSAES128_4, 64); + perfSetKey("Set Key", xtsspeck2, &testVectorXTSAES128_4, 64); + perfSetTweak("Set Tweak", xtsspeck2, &testVectorXTSAES128_4); + delete xtsspeck2; + Serial.println(); + + printlnProgMem("XTS-Speck-256 Single Key:"); + XTSSingleKey *singlespeck2 = new XTSSingleKey(); + perfEncrypt("Encrypt", singlespeck2, &testVectorXTSAES128_4, 32); + perfDecrypt("Decrypt", singlespeck2, &testVectorXTSAES128_4, 32); + perfSetKey("Set Key", singlespeck2, &testVectorXTSAES128_4, 32); + perfSetTweak("Set Tweak", singlespeck2, &testVectorXTSAES128_4); + delete singlespeck2; + Serial.println(); +} + +void loop() +{ +}