diff --git a/doc/crypto.dox b/doc/crypto.dox
index bc48e935..b445d728 100644
--- a/doc/crypto.dox
+++ b/doc/crypto.dox
@@ -29,7 +29,7 @@
\li Block ciphers: AES128, AES192, AES256, Speck
\li Block cipher modes: CTR, CFB, CBC, OFB, GCM
\li Stream ciphers: ChaCha
-\li Authenticated encryption with associated data (AEAD): ChaChaPoly, GCM
+\li Authenticated encryption with associated data (AEAD): ChaChaPoly, EAX, GCM
\li Hash algorithms: SHA1, SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes)
\li Message authenticators: Poly1305, GHASH
\li Public key algorithms: Curve25519, Ed25519
@@ -89,6 +89,9 @@ Ardunino Mega 2560 running at 16 MHz are similar:
GCM<AES128> | 186.47us | 186.42us | 1388.43us | 316 |
GCM<AES192> | 194.17us | 193.72us | 1628.67us | 348 |
GCM<AES256> | 201.47us | 201.02us | 1923.78us | 380 |
+EAX<AES128> | 78.37us | 78.37us | 1445.15us | 300 |
+EAX<Speck> (128-bit key) | 26.01us | 26.01us | 735.46us | 362 |
+EAX<SpeckLowMemory> (128-bit key) | 75.08us | 75.07us | 1243.66us | 122 |
|
Hash Algorithm | Hashing (per byte) | Finalization | | State Size (bytes) |
SHA1 | 21.90us | 1423.28us | | 95 |
@@ -143,6 +146,9 @@ All figures are for the Arduino Due running at 84 MHz:
GCM<AES128> | 11.01us | 10.92us | 247.90us | 344 |
GCM<AES192> | 12.40us | 12.31us | 294.07us | 376 |
GCM<AES256> | 13.73us | 13.64us | 347.40us | 408 |
+EAX<AES128> | 14.17us | 14.17us | 266.56us | 312 |
+EAX<Speck> (128-bit key) | 2.65us | 2.65us | 79.38us | 384 |
+EAX<SpeckLowMemory> (128-bit key) | 6.40us | 6.39us | 108.25us | 122 |
|
Hash Algorithm | Hashing (per byte) | Finalization | | State Size (bytes) |
SHA1 | 0.94us | 62.55us | | 112 |
diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index cfc150c3..fc00b07b 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -94,7 +94,7 @@ realtime clock and the LCD library to implement an alarm clock.
\li Block ciphers: AES128, AES192, AES256, Speck
\li Block cipher modes: CTR, CFB, CBC, OFB, GCM
\li Stream ciphers: ChaCha
-\li Authenticated encryption with associated data (AEAD): ChaChaPoly, GCM
+\li Authenticated encryption with associated data (AEAD): ChaChaPoly, EAX, GCM
\li Hash algorithms: SHA1, SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes)
\li Message authenticators: Poly1305, GHASH
\li Public key algorithms: Curve25519, Ed25519
diff --git a/libraries/Crypto/EAX.cpp b/libraries/Crypto/EAX.cpp
new file mode 100644
index 00000000..42db3b37
--- /dev/null
+++ b/libraries/Crypto/EAX.cpp
@@ -0,0 +1,397 @@
+/*
+ * 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 "EAX.h"
+#include "Crypto.h"
+#include
+
+/**
+ * \class EAXCommon EAX.h
+ * \brief Concrete base class to assist with implementing EAX for
+ * 128-bit block ciphers.
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa EAX
+ */
+
+/**
+ * \brief Constructs a new cipher in EAX mode.
+ *
+ * This constructor must be followed by a call to setBlockCipher().
+ */
+EAXCommon::EAXCommon()
+ : blockCipher(0)
+{
+ state.encPosn = 0;
+ state.authPosn = 0;
+ state.authMode = 0;
+}
+
+EAXCommon::~EAXCommon()
+{
+ clean(state);
+}
+
+size_t EAXCommon::keySize() const
+{
+ return blockCipher->keySize();
+}
+
+size_t EAXCommon::ivSize() const
+{
+ // Can use any size but 16 is recommended.
+ return 16;
+}
+
+size_t EAXCommon::tagSize() const
+{
+ // Tags can be up to 16 bytes in length.
+ return 16;
+}
+
+bool EAXCommon::setKey(const uint8_t *key, size_t len)
+{
+ return blockCipher->setKey(key, len);
+}
+
+bool EAXCommon::setIV(const uint8_t *iv, size_t len)
+{
+ // Must have at least 1 byte for the IV.
+ if (!len)
+ return false;
+
+ // Hash the IV to create the initial nonce for CTR mode. Also creates B.
+ omacInitFirst(state.counter);
+ omacUpdate(state.counter, iv, len);
+ omacFinal(state.counter);
+
+ // The tag is initially the nonce value. Will be XOR'ed with
+ // the hash of the authenticated and encrypted data later.
+ memcpy(state.tag, state.counter, 16);
+
+ // Start the hashing context for the authenticated data.
+ omacInit(state.hash, 1);
+ state.encPosn = 16;
+ state.authMode = 1;
+
+ // The EAX context is ready to go.
+ return true;
+}
+
+void EAXCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+ if (state.authMode)
+ closeAuthData();
+ encryptCTR(output, input, len);
+ omacUpdate(state.hash, output, len);
+}
+
+void EAXCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+ if (state.authMode)
+ closeAuthData();
+ omacUpdate(state.hash, input, len);
+ encryptCTR(output, input, len);
+}
+
+void EAXCommon::addAuthData(const void *data, size_t len)
+{
+ if (state.authMode)
+ omacUpdate(state.hash, (const uint8_t *)data, len);
+}
+
+void EAXCommon::computeTag(void *tag, size_t len)
+{
+ closeTag();
+ if (len > 16)
+ len = 16;
+ memcpy(tag, state.tag, len);
+}
+
+bool EAXCommon::checkTag(const void *tag, size_t len)
+{
+ // Can never match if the expected tag length is too long.
+ if (len > 16)
+ return false;
+
+ // Compute the final tag and check it.
+ closeTag();
+ return secure_compare(state.tag, tag, len);
+}
+
+void EAXCommon::clear()
+{
+ clean(state);
+}
+
+// Doubles a 128-bit value in the GF(2^128) field.
+static void gfDouble(uint8_t value[16])
+{
+ uint16_t temp = 0;
+ for (uint8_t index = 16; index > 0; ) {
+ --index;
+ temp |= (((uint16_t)(value[index])) << 1);
+ value[index] = (uint8_t)temp;
+ temp >>= 8;
+ }
+ value[15] ^= (uint8_t)((-temp) & 0x87);
+}
+
+/**
+ * \brief Initialises the first OMAC hashing context and creates the B value.
+ *
+ * \param omac The OMAC hashing context.
+ */
+void EAXCommon::omacInitFirst(uint8_t omac[16])
+{
+ // Start the OMAC context for the nonce. We assume that the
+ // data that follows will be at least 1 byte in length so that
+ // we can encrypt the zeroes now to derive the B value.
+ memset(omac, 0, 16);
+ blockCipher->encryptBlock(omac, omac);
+ state.authPosn = 0;
+
+ // Generate the B value from the encrypted block of zeroes.
+ // We will need this later when finalising the OMAC hashes.
+ memcpy(state.b, omac, 16);
+ gfDouble(state.b);
+}
+
+/**
+ * \brief Initialises an OMAC hashing context.
+ *
+ * \param omac The OMAC hashing context.
+ * \param t The tag value indicating which OMAC calculation we are doing.
+ */
+void EAXCommon::omacInit(uint8_t omac[16], uint8_t t)
+{
+ memset(omac, 0, 15);
+ omac[15] = t;
+ state.authPosn = 16;
+}
+
+/**
+ * \brief Updates an OMAC hashing context with more data.
+ *
+ * \param omac The OMAC hashing context.
+ * \param data Points to the data to be hashed.
+ * \parm len The number of bytes to be hashed.
+ */
+void EAXCommon::omacUpdate(uint8_t omac[16], const uint8_t *data, size_t len)
+{
+ while (len > 0) {
+ // Encrypt the current block if it is already full.
+ if (state.authPosn == 16) {
+ blockCipher->encryptBlock(omac, omac);
+ state.authPosn = 0;
+ }
+
+ // XOR the incoming data with the current block.
+ uint8_t size = 16 - state.authPosn;
+ if (size > len)
+ size = (uint8_t)len;
+ for (uint8_t index = 0; index < size; ++index)
+ omac[(state.authPosn)++] ^= data[index];
+
+ // Move onto the next block.
+ len -= size;
+ data += size;
+ }
+}
+
+/**
+ * \brief Finalises an OMAC hashing context.
+ *
+ * \param omac The OMAC hashing context on entry, the final OMAC value on exit.
+ */
+void EAXCommon::omacFinal(uint8_t omac[16])
+{
+ // Apply padding if necessary.
+ if (state.authPosn != 16) {
+ // Need padding: XOR with P = 2 * B.
+ uint8_t p[16];
+ memcpy(p, state.b, 16);
+ gfDouble(p);
+ omac[state.authPosn] ^= 0x80;
+ for (uint8_t index = 0; index < 16; ++index)
+ omac[index] ^= p[index];
+ clean(p);
+ } else {
+ // No padding necessary: XOR with B.
+ for (uint8_t index = 0; index < 16; ++index)
+ omac[index] ^= state.b[index];
+ }
+
+ // Encrypt the hash to get the final OMAC value.
+ blockCipher->encryptBlock(omac, omac);
+}
+
+/**
+ * \brief Closes the authenticated data portion of the session and
+ * starts encryption or decryption.
+ */
+void EAXCommon::closeAuthData()
+{
+ // Finalise the OMAC hash and XOR it with the final tag.
+ omacFinal(state.hash);
+ for (uint8_t index = 0; index < 16; ++index)
+ state.tag[index] ^= state.hash[index];
+ state.authMode = 0;
+
+ // Initialise the hashing context for the ciphertext data.
+ omacInit(state.hash, 2);
+}
+
+/**
+ * \brief Encrypts or decrypts a region using the block cipher in CTR mode.
+ *
+ * \param output The output buffer to write to, which may be the same
+ * buffer as \a input. The \a output buffer must have at least as many
+ * bytes as the \a input buffer.
+ * \param input The input buffer to read from.
+ * \param len The number of bytes to process.
+ */
+void EAXCommon::encryptCTR(uint8_t *output, const uint8_t *input, size_t len)
+{
+ while (len > 0) {
+ // Do we need to start a new block?
+ if (state.encPosn == 16) {
+ // Encrypt the counter to create the next keystream block.
+ blockCipher->encryptBlock(state.stream, state.counter);
+ state.encPosn = 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 > 0) {
+ --index;
+ temp += state.counter[index];
+ state.counter[index] = (uint8_t)temp;
+ temp >>= 8;
+ }
+ }
+
+ // Encrypt/decrypt the current input block.
+ uint8_t size = 16 - state.encPosn;
+ if (size > len)
+ size = (uint8_t)len;
+ for (uint8_t index = 0; index < size; ++index)
+ output[index] = input[index] ^ state.stream[(state.encPosn)++];
+
+ // Move onto the next block.
+ len -= size;
+ input += size;
+ output += size;
+ }
+}
+
+void EAXCommon::closeTag()
+{
+ // If we were only authenticating, then close off auth mode.
+ if (state.authMode)
+ closeAuthData();
+
+ // Finalise the hash over the ciphertext and XOR with the final tag.
+ omacFinal(state.hash);
+ for (uint8_t index = 0; index < 16; ++index)
+ state.tag[index] ^= state.hash[index];
+}
+
+/**
+ * \fn void EAXCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this EAX object.
+ *
+ * \param cipher The block cipher to use to implement EAX mode.
+ * This object must have a block size of 128 bits (16 bytes).
+ */
+
+/**
+ * \class EAX EAX.h
+ * \brief Implementation of the EAX authenticated cipher.
+ *
+ * EAX mode converts a block cipher into an authenticated cipher
+ * that uses the block cipher T to encrypt and authenticate.
+ *
+ * The size of the key is determined by the underlying block cipher T.
+ * The IV is recommended to be 128 bits (16 bytes) in length, but other
+ * lengths are supported as well. The default tagSize() is 128 bits
+ * (16 bytes) but the EAX specification does allow smaller tag sizes.
+ *
+ * The template parameter T must be a concrete subclass of BlockCipher
+ * indicating the specific block cipher to use. The block cipher must
+ * have a block size of 128 bits. For example, the following creates a
+ * EAX object using AES256 as the underlying cipher and then uses it
+ * to encrypt and authenticate a \c plaintext block:
+ *
+ * \code
+ * EAX eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata, sizeof(adata));
+ * eax.encrypt(ciphertext, plaintext, sizeof(plaintext));
+ * eax.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * The decryption process is almost identical to convert a \c ciphertext and
+ * \a tag back into plaintext and then check the tag:
+ *
+ * \code
+ * EAX eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata, sizeof(adata));
+ * eax.decrypt(ciphertext, plaintext, sizeof(plaintext));
+ * if (!eax.checkTag(tag, sizeof(tag))) {
+ * // The data was invalid - do not use it.
+ * ...
+ * }
+ * \endcode
+ *
+ * The EAX class can also be used to implement message authentication
+ * by omitting the plaintext:
+ *
+ * \code
+ * EAX eax;
+ * eax.setKey(key, sizeof(key));
+ * eax.setIV(iv, sizeof(iv));
+ * eax.addAuthData(adata1, sizeof(adata1));
+ * eax.addAuthData(adata2, sizeof(adata1));
+ * ...
+ * eax.addAuthData(adataN, sizeof(adataN));
+ * eax.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * References: https://en.wikipedia.org/wiki/EAX_mode,
+ * http://web.cs.ucdavis.edu/~rogaway/papers/eax.html
+ *
+ * \sa EAXCommon, GCM
+ */
+
+/**
+ * \fn EAX::EAX()
+ * \brief Constructs a new EAX object for the block cipher T.
+ */
diff --git a/libraries/Crypto/EAX.h b/libraries/Crypto/EAX.h
new file mode 100644
index 00000000..1499b744
--- /dev/null
+++ b/libraries/Crypto/EAX.h
@@ -0,0 +1,88 @@
+/*
+ * 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_EAX_h
+#define CRYPTO_EAX_h
+
+#include "AuthenticatedCipher.h"
+#include "BlockCipher.h"
+
+class EAXCommon : public AuthenticatedCipher
+{
+public:
+ virtual ~EAXCommon();
+
+ size_t keySize() const;
+ size_t ivSize() const;
+ size_t tagSize() const;
+
+ 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 addAuthData(const void *data, size_t len);
+
+ void computeTag(void *tag, size_t len);
+ bool checkTag(const void *tag, size_t len);
+
+ void clear();
+
+protected:
+ EAXCommon();
+ void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
+
+private:
+ BlockCipher *blockCipher;
+ struct {
+ uint8_t counter[16];
+ uint8_t stream[16];
+ uint8_t tag[16];
+ uint8_t hash[16];
+ uint8_t b[16];
+ uint8_t encPosn;
+ uint8_t authPosn;
+ uint8_t authMode;
+ } state;
+
+ void omacInitFirst(uint8_t omac[16]);
+ void omacInit(uint8_t omac[16], uint8_t t);
+ void omacUpdate(uint8_t omac[16], const uint8_t *data, size_t len);
+ void omacFinal(uint8_t omac[16]);
+
+ void closeAuthData();
+ void encryptCTR(uint8_t *output, const uint8_t *input, size_t len);
+ void closeTag();
+};
+
+template
+class EAX : public EAXCommon
+{
+public:
+ EAX() { setBlockCipher(&cipher); }
+
+private:
+ T cipher;
+};
+
+#endif
diff --git a/libraries/Crypto/examples/TestEAX/TestEAX.ino b/libraries/Crypto/examples/TestEAX/TestEAX.ino
new file mode 100644
index 00000000..89e6f5f4
--- /dev/null
+++ b/libraries/Crypto/examples/TestEAX/TestEAX.ino
@@ -0,0 +1,534 @@
+/*
+ * 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 EAX implementation to verify correct behaviour.
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define MAX_PLAINTEXT_LEN 64
+
+struct TestVector
+{
+ const char *name;
+ uint8_t key[16];
+ uint8_t plaintext[MAX_PLAINTEXT_LEN];
+ uint8_t ciphertext[MAX_PLAINTEXT_LEN];
+ uint8_t authdata[20];
+ uint8_t iv[16];
+ uint8_t tag[16];
+ size_t authsize;
+ size_t datasize;
+ size_t tagsize;
+ size_t ivsize;
+};
+
+// Test vectors for AES in EAX mode from Appendix G of:
+// http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
+static TestVector const testVectorEAX1 PROGMEM = {
+ .name = "EAX #1",
+ .key = {0x23, 0x39, 0x52, 0xDE, 0xE4, 0xD5, 0xED, 0x5F,
+ 0x9B, 0x9C, 0x6D, 0x6F, 0xF8, 0x0F, 0xF4, 0x78},
+ .plaintext = {0x00},
+ .ciphertext = {0x00},
+ .authdata = {0x6B, 0xFB, 0x91, 0x4F, 0xD0, 0x7E, 0xAE, 0x6B},
+ .iv = {0x62, 0xEC, 0x67, 0xF9, 0xC3, 0xA4, 0xA4, 0x07,
+ 0xFC, 0xB2, 0xA8, 0xC4, 0x90, 0x31, 0xA8, 0xB3},
+ .tag = {0xE0, 0x37, 0x83, 0x0E, 0x83, 0x89, 0xF2, 0x7B,
+ 0x02, 0x5A, 0x2D, 0x65, 0x27, 0xE7, 0x9D, 0x01},
+ .authsize = 8,
+ .datasize = 0,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX2 PROGMEM = {
+ .name = "EAX #2",
+ .key = {0x91, 0x94, 0x5D, 0x3F, 0x4D, 0xCB, 0xEE, 0x0B,
+ 0xF4, 0x5E, 0xF5, 0x22, 0x55, 0xF0, 0x95, 0xA4},
+ .plaintext = {0xF7, 0xFB},
+ .ciphertext = {0x19, 0xDD},
+ .authdata = {0xFA, 0x3B, 0xFD, 0x48, 0x06, 0xEB, 0x53, 0xFA},
+ .iv = {0xBE, 0xCA, 0xF0, 0x43, 0xB0, 0xA2, 0x3D, 0x84,
+ 0x31, 0x94, 0xBA, 0x97, 0x2C, 0x66, 0xDE, 0xBD},
+ .tag = {0x5C, 0x4C, 0x93, 0x31, 0x04, 0x9D, 0x0B, 0xDA,
+ 0xB0, 0x27, 0x74, 0x08, 0xF6, 0x79, 0x67, 0xE5},
+ .authsize = 8,
+ .datasize = 2,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX3 PROGMEM = {
+ .name = "EAX #3",
+ .key = {0x01, 0xF7, 0x4A, 0xD6, 0x40, 0x77, 0xF2, 0xE7,
+ 0x04, 0xC0, 0xF6, 0x0A, 0xDA, 0x3D, 0xD5, 0x23},
+ .plaintext = {0x1A, 0x47, 0xCB, 0x49, 0x33},
+ .ciphertext = {0xD8, 0x51, 0xD5, 0xBA, 0xE0},
+ .authdata = {0x23, 0x4A, 0x34, 0x63, 0xC1, 0x26, 0x4A, 0xC6},
+ .iv = {0x70, 0xC3, 0xDB, 0x4F, 0x0D, 0x26, 0x36, 0x84,
+ 0x00, 0xA1, 0x0E, 0xD0, 0x5D, 0x2B, 0xFF, 0x5E},
+ .tag = {0x3A, 0x59, 0xF2, 0x38, 0xA2, 0x3E, 0x39, 0x19,
+ 0x9D, 0xC9, 0x26, 0x66, 0x26, 0xC4, 0x0F, 0x80},
+ .authsize = 8,
+ .datasize = 5,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX4 PROGMEM = {
+ .name = "EAX #4",
+ .key = {0xD0, 0x7C, 0xF6, 0xCB, 0xB7, 0xF3, 0x13, 0xBD,
+ 0xDE, 0x66, 0xB7, 0x27, 0xAF, 0xD3, 0xC5, 0xE8},
+ .plaintext = {0x48, 0x1C, 0x9E, 0x39, 0xB1},
+ .ciphertext = {0x63, 0x2A, 0x9D, 0x13, 0x1A},
+ .authdata = {0x33, 0xCC, 0xE2, 0xEA, 0xBF, 0xF5, 0xA7, 0x9D},
+ .iv = {0x84, 0x08, 0xDF, 0xFF, 0x3C, 0x1A, 0x2B, 0x12,
+ 0x92, 0xDC, 0x19, 0x9E, 0x46, 0xB7, 0xD6, 0x17},
+ .tag = {0xD4, 0xC1, 0x68, 0xA4, 0x22, 0x5D, 0x8E, 0x1F,
+ 0xF7, 0x55, 0x93, 0x99, 0x74, 0xA7, 0xBE, 0xDE},
+ .authsize = 8,
+ .datasize = 5,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX5 PROGMEM = {
+ .name = "EAX #5",
+ .key = {0x35, 0xB6, 0xD0, 0x58, 0x00, 0x05, 0xBB, 0xC1,
+ 0x2B, 0x05, 0x87, 0x12, 0x45, 0x57, 0xD2, 0xC2},
+ .plaintext = {0x40, 0xD0, 0xC0, 0x7D, 0xA5, 0xE4},
+ .ciphertext = {0x07, 0x1D, 0xFE, 0x16, 0xC6, 0x75},
+ .authdata = {0xAE, 0xB9, 0x6E, 0xAE, 0xBE, 0x29, 0x70, 0xE9},
+ .iv = {0xFD, 0xB6, 0xB0, 0x66, 0x76, 0xEE, 0xDC, 0x5C,
+ 0x61, 0xD7, 0x42, 0x76, 0xE1, 0xF8, 0xE8, 0x16},
+ .tag = {0xCB, 0x06, 0x77, 0xE5, 0x36, 0xF7, 0x3A, 0xFE,
+ 0x6A, 0x14, 0xB7, 0x4E, 0xE4, 0x98, 0x44, 0xDD},
+ .authsize = 8,
+ .datasize = 6,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX6 PROGMEM = {
+ .name = "EAX #6",
+ .key = {0xBD, 0x8E, 0x6E, 0x11, 0x47, 0x5E, 0x60, 0xB2,
+ 0x68, 0x78, 0x4C, 0x38, 0xC6, 0x2F, 0xEB, 0x22},
+ .plaintext = {0x4D, 0xE3, 0xB3, 0x5C, 0x3F, 0xC0, 0x39, 0x24,
+ 0x5B, 0xD1, 0xFB, 0x7D},
+ .ciphertext = {0x83, 0x5B, 0xB4, 0xF1, 0x5D, 0x74, 0x3E, 0x35,
+ 0x0E, 0x72, 0x84, 0x14},
+ .authdata = {0xD4, 0x48, 0x2D, 0x1C, 0xA7, 0x8D, 0xCE, 0x0F},
+ .iv = {0x6E, 0xAC, 0x5C, 0x93, 0x07, 0x2D, 0x8E, 0x85,
+ 0x13, 0xF7, 0x50, 0x93, 0x5E, 0x46, 0xDA, 0x1B},
+ .tag = {0xAB, 0xB8, 0x64, 0x4F, 0xD6, 0xCC, 0xB8, 0x69,
+ 0x47, 0xC5, 0xE1, 0x05, 0x90, 0x21, 0x0A, 0x4F},
+ .authsize = 8,
+ .datasize = 12,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX7 PROGMEM = {
+ .name = "EAX #7",
+ .key = {0x7C, 0x77, 0xD6, 0xE8, 0x13, 0xBE, 0xD5, 0xAC,
+ 0x98, 0xBA, 0xA4, 0x17, 0x47, 0x7A, 0x2E, 0x7D},
+ .plaintext = {0x8B, 0x0A, 0x79, 0x30, 0x6C, 0x9C, 0xE7, 0xED,
+ 0x99, 0xDA, 0xE4, 0xF8, 0x7F, 0x8D, 0xD6, 0x16,
+ 0x36},
+ .ciphertext = {0x02, 0x08, 0x3E, 0x39, 0x79, 0xDA, 0x01, 0x48,
+ 0x12, 0xF5, 0x9F, 0x11, 0xD5, 0x26, 0x30, 0xDA,
+ 0x30},
+ .authdata = {0x65, 0xD2, 0x01, 0x79, 0x90, 0xD6, 0x25, 0x28},
+ .iv = {0x1A, 0x8C, 0x98, 0xDC, 0xD7, 0x3D, 0x38, 0x39,
+ 0x3B, 0x2B, 0xF1, 0x56, 0x9D, 0xEE, 0xFC, 0x19},
+ .tag = {0x13, 0x73, 0x27, 0xD1, 0x06, 0x49, 0xB0, 0xAA,
+ 0x6E, 0x1C, 0x18, 0x1D, 0xB6, 0x17, 0xD7, 0xF2},
+ .authsize = 8,
+ .datasize = 17,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX8 PROGMEM = {
+ .name = "EAX #8",
+ .key = {0x5F, 0xFF, 0x20, 0xCA, 0xFA, 0xB1, 0x19, 0xCA,
+ 0x2F, 0xC7, 0x35, 0x49, 0xE2, 0x0F, 0x5B, 0x0D},
+ .plaintext = {0x1B, 0xDA, 0x12, 0x2B, 0xCE, 0x8A, 0x8D, 0xBA,
+ 0xF1, 0x87, 0x7D, 0x96, 0x2B, 0x85, 0x92, 0xDD,
+ 0x2D, 0x56},
+ .ciphertext = {0x2E, 0xC4, 0x7B, 0x2C, 0x49, 0x54, 0xA4, 0x89,
+ 0xAF, 0xC7, 0xBA, 0x48, 0x97, 0xED, 0xCD, 0xAE,
+ 0x8C, 0xC3},
+ .authdata = {0x54, 0xB9, 0xF0, 0x4E, 0x6A, 0x09, 0x18, 0x9A},
+ .iv = {0xDD, 0xE5, 0x9B, 0x97, 0xD7, 0x22, 0x15, 0x6D,
+ 0x4D, 0x9A, 0xFF, 0x2B, 0xC7, 0x55, 0x98, 0x26},
+ .tag = {0x3B, 0x60, 0x45, 0x05, 0x99, 0xBD, 0x02, 0xC9,
+ 0x63, 0x82, 0x90, 0x2A, 0xEF, 0x7F, 0x83, 0x2A},
+ .authsize = 8,
+ .datasize = 18,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX9 PROGMEM = {
+ .name = "EAX #9",
+ .key = {0xA4, 0xA4, 0x78, 0x2B, 0xCF, 0xFD, 0x3E, 0xC5,
+ 0xE7, 0xEF, 0x6D, 0x8C, 0x34, 0xA5, 0x61, 0x23},
+ .plaintext = {0x6C, 0xF3, 0x67, 0x20, 0x87, 0x2B, 0x85, 0x13,
+ 0xF6, 0xEA, 0xB1, 0xA8, 0xA4, 0x44, 0x38, 0xD5,
+ 0xEF, 0x11},
+ .ciphertext = {0x0D, 0xE1, 0x8F, 0xD0, 0xFD, 0xD9, 0x1E, 0x7A,
+ 0xF1, 0x9F, 0x1D, 0x8E, 0xE8, 0x73, 0x39, 0x38,
+ 0xB1, 0xE8},
+ .authdata = {0x89, 0x9A, 0x17, 0x58, 0x97, 0x56, 0x1D, 0x7E},
+ .iv = {0xB7, 0x81, 0xFC, 0xF2, 0xF7, 0x5F, 0xA5, 0xA8,
+ 0xDE, 0x97, 0xA9, 0xCA, 0x48, 0xE5, 0x22, 0xEC},
+ .tag = {0xE7, 0xF6, 0xD2, 0x23, 0x16, 0x18, 0x10, 0x2F,
+ 0xDB, 0x7F, 0xE5, 0x5F, 0xF1, 0x99, 0x17, 0x00},
+ .authsize = 8,
+ .datasize = 18,
+ .tagsize = 16,
+ .ivsize = 16
+};
+static TestVector const testVectorEAX10 PROGMEM = {
+ .name = "EAX #10",
+ .key = {0x83, 0x95, 0xFC, 0xF1, 0xE9, 0x5B, 0xEB, 0xD6,
+ 0x97, 0xBD, 0x01, 0x0B, 0xC7, 0x66, 0xAA, 0xC3},
+ .plaintext = {0xCA, 0x40, 0xD7, 0x44, 0x6E, 0x54, 0x5F, 0xFA,
+ 0xED, 0x3B, 0xD1, 0x2A, 0x74, 0x0A, 0x65, 0x9F,
+ 0xFB, 0xBB, 0x3C, 0xEA, 0xB7},
+ .ciphertext = {0xCB, 0x89, 0x20, 0xF8, 0x7A, 0x6C, 0x75, 0xCF,
+ 0xF3, 0x96, 0x27, 0xB5, 0x6E, 0x3E, 0xD1, 0x97,
+ 0xC5, 0x52, 0xD2, 0x95, 0xA7},
+ .authdata = {0x12, 0x67, 0x35, 0xFC, 0xC3, 0x20, 0xD2, 0x5A},
+ .iv = {0x22, 0xE7, 0xAD, 0xD9, 0x3C, 0xFC, 0x63, 0x93,
+ 0xC5, 0x7E, 0xC0, 0xB3, 0xC1, 0x7D, 0x6B, 0x44},
+ .tag = {0xCF, 0xC4, 0x6A, 0xFC, 0x25, 0x3B, 0x46, 0x52,
+ 0xB1, 0xAF, 0x37, 0x95, 0xB1, 0x24, 0xAB, 0x6E},
+ .authsize = 8,
+ .datasize = 21,
+ .tagsize = 16,
+ .ivsize = 16
+};
+
+TestVector testVector;
+
+EAX *eax;
+EAX *eaxSpeck;
+EAX *eaxSpeckLowMemory;
+
+byte buffer[128];
+
+bool testCipher_N(AuthenticatedCipher *cipher, const struct TestVector *test, size_t inc)
+{
+ size_t posn, len;
+ uint8_t tag[16];
+
+ cipher->clear();
+ if (!cipher->setKey(test->key, 16)) {
+ Serial.print("setKey ");
+ return false;
+ }
+ if (!cipher->setIV(test->iv, test->ivsize)) {
+ Serial.print("setIV ");
+ return false;
+ }
+
+ memset(buffer, 0xBA, sizeof(buffer));
+
+ if (!inc)
+ inc = 1;
+
+ for (posn = 0; posn < test->authsize; posn += inc) {
+ len = test->authsize - posn;
+ if (len > inc)
+ len = inc;
+ cipher->addAuthData(test->authdata + posn, len);
+ }
+
+ for (posn = 0; posn < test->datasize; posn += inc) {
+ len = test->datasize - posn;
+ if (len > inc)
+ len = inc;
+ cipher->encrypt(buffer + posn, test->plaintext + posn, len);
+ }
+
+ if (memcmp(buffer, test->ciphertext, test->datasize) != 0) {
+ Serial.print(buffer[0], HEX);
+ Serial.print("->");
+ Serial.print(test->ciphertext[0], HEX);
+ return false;
+ }
+
+ cipher->computeTag(tag, sizeof(tag));
+ if (memcmp(tag, test->tag, sizeof(tag)) != 0) {
+ Serial.print("computed wrong tag ... ");
+ return false;
+ }
+
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+
+ for (posn = 0; posn < test->authsize; posn += inc) {
+ len = test->authsize - posn;
+ if (len > inc)
+ len = inc;
+ cipher->addAuthData(test->authdata + posn, len);
+ }
+
+ for (posn = 0; posn < test->datasize; posn += inc) {
+ len = test->datasize - posn;
+ if (len > inc)
+ len = inc;
+ cipher->decrypt(buffer + posn, test->ciphertext + posn, len);
+ }
+
+ if (memcmp(buffer, test->plaintext, test->datasize) != 0)
+ return false;
+
+ if (!cipher->checkTag(tag, sizeof(tag))) {
+ Serial.print("tag did not check ... ");
+ return false;
+ }
+
+ return true;
+}
+
+void testCipher(AuthenticatedCipher *cipher, const struct TestVector *test)
+{
+ bool ok;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" ... ");
+
+ ok = testCipher_N(cipher, test, test->datasize);
+ 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 perfCipherSetKey(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(cipherName);
+ Serial.print(' ');
+ Serial.print(test->name);
+ Serial.print(" SetKey ... ");
+
+ start = micros();
+ for (count = 0; count < 1000; ++count) {
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / 1000.0);
+ Serial.print("us per operation, ");
+ Serial.print((1000.0 * 1000000.0) / elapsed);
+ Serial.println(" per second");
+}
+
+void perfCipherEncrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(cipherName);
+ Serial.print(' ');
+ Serial.print(test->name);
+ Serial.print(" Encrypt ... ");
+
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+ start = micros();
+ for (count = 0; count < 500; ++count) {
+ cipher->encrypt(buffer, buffer, 128);
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / (128.0 * 500.0));
+ Serial.print("us per byte, ");
+ Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+}
+
+void perfCipherDecrypt(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(cipherName);
+ Serial.print(' ');
+ Serial.print(test->name);
+ Serial.print(" Decrypt ... ");
+
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+ start = micros();
+ for (count = 0; count < 500; ++count) {
+ cipher->decrypt(buffer, buffer, 128);
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / (128.0 * 500.0));
+ Serial.print("us per byte, ");
+ Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+}
+
+void perfCipherAddAuthData(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(cipherName);
+ Serial.print(' ');
+ Serial.print(test->name);
+ Serial.print(" AddAuthData ... ");
+
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+ start = micros();
+ memset(buffer, 0xBA, 128);
+ for (count = 0; count < 500; ++count) {
+ cipher->addAuthData(buffer, 128);
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / (128.0 * 500.0));
+ Serial.print("us per byte, ");
+ Serial.print((128.0 * 500.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+}
+
+void perfCipherComputeTag(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(cipherName);
+ Serial.print(' ');
+ Serial.print(test->name);
+ Serial.print(" ComputeTag ... ");
+
+ cipher->setKey(test->key, 16);
+ cipher->setIV(test->iv, test->ivsize);
+ start = micros();
+ for (count = 0; count < 1000; ++count) {
+ cipher->computeTag(buffer, 16);
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / 1000.0);
+ Serial.print("us per operation, ");
+ Serial.print((1000.0 * 1000000.0) / elapsed);
+ Serial.println(" per second");
+}
+
+void perfCipher(AuthenticatedCipher *cipher, const struct TestVector *test, const char *cipherName)
+{
+ perfCipherSetKey(cipher, test, cipherName);
+ perfCipherEncrypt(cipher, test, cipherName);
+ perfCipherDecrypt(cipher, test, cipherName);
+ perfCipherAddAuthData(cipher, test, cipherName);
+ perfCipherComputeTag(cipher, test, cipherName);
+}
+
+void setup()
+{
+ Serial.begin(9600);
+
+ Serial.println();
+
+ Serial.println("State Sizes:");
+ Serial.print("EAX ... ");
+ Serial.println(sizeof(*eax));
+ Serial.print("EAX ... ");
+ Serial.println(sizeof(*eaxSpeck));
+ Serial.print("EAX ... ");
+ Serial.println(sizeof(*eaxSpeckLowMemory));
+ Serial.println();
+
+ Serial.println("Test Vectors:");
+ eax = new EAX();
+ testCipher(eax, &testVectorEAX1);
+ testCipher(eax, &testVectorEAX2);
+ testCipher(eax, &testVectorEAX3);
+ testCipher(eax, &testVectorEAX4);
+ testCipher(eax, &testVectorEAX5);
+ testCipher(eax, &testVectorEAX6);
+ testCipher(eax, &testVectorEAX7);
+ testCipher(eax, &testVectorEAX8);
+ testCipher(eax, &testVectorEAX9);
+ testCipher(eax, &testVectorEAX10);
+
+ Serial.println();
+
+ Serial.println("Performance Tests:");
+ perfCipher(eax, &testVectorEAX1, "AES-128");
+ Serial.println();
+ delete eax;
+ eaxSpeck = new EAX();
+ perfCipher(eaxSpeck, &testVectorEAX1, "Speck");
+ Serial.println();
+ delete eaxSpeck;
+ eaxSpeckLowMemory = new EAX();
+ perfCipher(eaxSpeckLowMemory, &testVectorEAX1, "SpeckLowMemory");
+ delete eaxSpeckLowMemory;
+}
+
+void loop()
+{
+}