diff --git a/doc/crypto.dox b/doc/crypto.dox
index bd3d10eb..8292b576 100644
--- a/doc/crypto.dox
+++ b/doc/crypto.dox
@@ -27,9 +27,9 @@
\section crypto_algorithms Supported Algorithms
\li Block ciphers: AES128, AES192, AES256
-\li Block cipher modes: CTR, CFB, CBC, OFB
-li Stream ciphers: ChaCha
-\li Authenticated encryption with associated data (AEAD): ChaChaPoly
+\li Block cipher modes: CTR, CFB, CBC, OFB, GCM
+\li Stream ciphers: ChaCha
+\li Authenticated encryption with associated data (AEAD): ChaChaPoly, 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
@@ -62,14 +62,21 @@ All figures are for the Arduino Uno running at 16 MHz. Figures for the
Ardunino Mega 2560 running at 16 MHz are similar:
-Algorithm | Encryption / Hashing (per byte) | Decryption (per byte) | Key Setup | State Size (bytes) |
+Encryption Algorithm | Encryption (per byte) | Decryption (per byte) | Key Setup | State Size (bytes) |
AES128 (ECB mode) | 36.90us | 66.48us | 160.00us | 213 |
AES192 (ECB mode) | 44.20us | 80.35us | 166.54us | 245 |
AES256 (ECB mode) | 51.50us | 94.22us | 227.97us | 277 |
ChaCha (20 rounds) | 14.87us | 14.88us | 43.74us | 132 |
ChaCha (12 rounds) | 10.38us | 10.38us | 43.74us | 132 |
ChaCha (8 rounds) | 8.13us | 8.14us | 43.74us | 132 |
+ |
+AEAD Algorithm | Encryption (per byte) | Decryption (per byte) | Key Setup | State Size (bytes) |
ChaChaPoly | 41.23us | 41.23us | 902.55us | 255 |
+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 |
+ |
+Hash Algorithm | Hashing (per byte) | Finalization | Key Setup | State Size (bytes) |
SHA1 | 21.90us | | | 95 |
SHA256 | 43.85us | | | 107 |
SHA512 | 123.24us | | | 211 |
diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index 01664e1e..0f31b58c 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -92,9 +92,9 @@ realtime clock and the LCD library to implement an alarm clock.
\section main_Crypto Cryptographic Library
\li Block ciphers: AES128, AES192, AES256
-\li Block cipher modes: CTR, CFB, CBC, OFB
+\li Block cipher modes: CTR, CFB, CBC, OFB, GCM
\li Stream ciphers: ChaCha
-\li Authenticated encryption with associated data (AEAD): ChaChaPoly
+\li Authenticated encryption with associated data (AEAD): ChaChaPoly, 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
diff --git a/libraries/Crypto/GCM.cpp b/libraries/Crypto/GCM.cpp
new file mode 100644
index 00000000..a18f42d7
--- /dev/null
+++ b/libraries/Crypto/GCM.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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 "GCM.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include
+
+/**
+ * \class GCMCommon GCM.h
+ * \brief Concrete base class to assist with implementing GCM for
+ * 128-bit block ciphers.
+ *
+ * References: NIST SP 800-38D,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCM
+ */
+
+/**
+ * \brief Constructs a new cipher in GCM mode.
+ *
+ * This constructor must be followed by a call to setBlockCipher().
+ */
+GCMCommon::GCMCommon()
+ : blockCipher(0)
+{
+ state.authSize = 0;
+ state.dataSize = 0;
+ state.dataStarted = false;
+ state.posn = 16;
+}
+
+/**
+ * \brief Destroys this cipher object after clearing sensitive information.
+ */
+GCMCommon::~GCMCommon()
+{
+ clean(state);
+}
+
+size_t GCMCommon::keySize() const
+{
+ return blockCipher->keySize();
+}
+
+size_t GCMCommon::ivSize() const
+{
+ return 16;
+}
+
+size_t GCMCommon::tagSize() const
+{
+ // The GCM specification recommends an IV size of 96 bits.
+ return 12;
+}
+
+bool GCMCommon::setKey(const uint8_t *key, size_t len)
+{
+ // Set the encryption key for the block cipher.
+ if (!blockCipher->setKey(key, len))
+ return false;
+
+ // Construct the hashing key by encrypting a zero block.
+ memset(state.nonce, 0, 16);
+ blockCipher->encryptBlock(state.nonce, state.nonce);
+ ghash.reset(state.nonce);
+ return true;
+}
+
+bool GCMCommon::setIV(const uint8_t *iv, size_t len)
+{
+ // Note: We assume that setKey() has already been called to
+ // set the hashing key in the "ghash" object and that the
+ // hashing key itself is still stored in "state.nonce".
+
+ // Format the counter block from the IV.
+ if (len == 12) {
+ // IV's of exactly 96 bits are used directly as the counter block.
+ memcpy(state.counter, iv, 12);
+ state.counter[12] = 0;
+ state.counter[13] = 0;
+ state.counter[14] = 0;
+ state.counter[15] = 1;
+ } else {
+ // IV's of other sizes are hashed to produce the counter block.
+ ghash.update(iv, len);
+ ghash.pad();
+ uint64_t sizes[2] = {0, htobe64(((uint64_t)len) * 8)};
+ ghash.update(sizes, sizeof(sizes));
+ clean(sizes);
+ ghash.finalize(state.counter, 16);
+ ghash.reset(state.nonce);
+ }
+
+ // Reset the GCM object ready to process auth or payload data.
+ state.authSize = 0;
+ state.dataSize = 0;
+ state.dataStarted = false;
+ state.posn = 16;
+
+ // Replace the hash key in "nonce" with the encrypted counter.
+ // This value will be XOR'ed with the final authentication hash
+ // value in computeTag().
+ blockCipher->encryptBlock(state.nonce, state.counter);
+}
+
+/**
+ * \brief Increments the least significant 32 bits of the counter block.
+ *
+ * \param counter The counter block to increment.
+ */
+static inline void increment(uint8_t counter[16])
+{
+ uint16_t carry = 1;
+ carry += counter[15];
+ counter[15] = (uint8_t)carry;
+ carry = (carry >> 8) + counter[14];
+ counter[14] = (uint8_t)carry;
+ carry = (carry >> 8) + counter[13];
+ counter[13] = (uint8_t)carry;
+ carry = (carry >> 8) + counter[12];
+ counter[12] = (uint8_t)carry;
+}
+
+void GCMCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+ // Finalize the authenticated data if necessary.
+ if (!state.dataStarted) {
+ ghash.pad();
+ state.dataStarted = true;
+ }
+
+ // Encrypt the plaintext using the block cipher in counter mode.
+ uint8_t *out = output;
+ size_t size = len;
+ while (size > 0) {
+ // Create a new keystream block if necessary.
+ if (state.posn >= 16) {
+ increment(state.counter);
+ blockCipher->encryptBlock(state.stream, state.counter);
+ state.posn = 0;
+ }
+
+ // Encrypt as many bytes as we can using the keystream block.
+ uint8_t temp = 16 - state.posn;
+ if (temp > size)
+ temp = size;
+ uint8_t *stream = state.stream + state.posn;
+ state.posn += temp;
+ size -= temp;
+ while (temp > 0) {
+ *out++ = *input++ ^ *stream++;
+ --temp;
+ }
+ }
+
+ // Feed the ciphertext into the hash.
+ ghash.update(output, len);
+ state.dataSize += len;
+}
+
+void GCMCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len)
+{
+ // Finalize the authenticated data if necessary.
+ if (!state.dataStarted) {
+ ghash.pad();
+ state.dataStarted = true;
+ }
+
+ // Feed the ciphertext into the hash before we decrypt it.
+ ghash.update(input, len);
+ state.dataSize += len;
+
+ // Decrypt the plaintext using the block cipher in counter mode.
+ while (len > 0) {
+ // Create a new keystream block if necessary.
+ if (state.posn >= 16) {
+ increment(state.counter);
+ blockCipher->encryptBlock(state.stream, state.counter);
+ state.posn = 0;
+ }
+
+ // Decrypt as many bytes as we can using the keystream block.
+ uint8_t temp = 16 - state.posn;
+ if (temp > len)
+ temp = len;
+ uint8_t *stream = state.stream + state.posn;
+ state.posn += temp;
+ len -= temp;
+ while (temp > 0) {
+ *output++ = *input++ ^ *stream++;
+ --temp;
+ }
+ }
+}
+
+void GCMCommon::addAuthData(const void *data, size_t len)
+{
+ if (!state.dataStarted) {
+ ghash.update(data, len);
+ state.authSize += len;
+ }
+}
+
+void GCMCommon::computeTag(void *tag, size_t len)
+{
+ // Pad the hashed data and add the sizes.
+ ghash.pad();
+ uint64_t sizes[2] = {
+ htobe64(state.authSize * 8),
+ htobe64(state.dataSize * 8)
+ };
+ ghash.update(sizes, sizeof(sizes));
+ clean(sizes);
+
+ // Get the finalized hash, encrypt it with the nonce, and return the tag.
+ ghash.finalize(state.stream, 16);
+ for (uint8_t posn = 0; posn < 16; ++posn)
+ state.stream[posn] ^= state.nonce[posn];
+ if (len > 16)
+ len = 16;
+ memcpy(tag, state.stream, len);
+}
+
+bool GCMCommon::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 tag and check it.
+ computeTag(state.counter, 16);
+ return secure_compare(state.counter, tag, len);
+}
+
+void GCMCommon::clear()
+{
+ blockCipher->clear();
+ ghash.clear();
+ clean(state);
+ state.posn = 16;
+}
+
+/**
+ * \fn void GCMCommon::setBlockCipher(BlockCipher *cipher)
+ * \brief Sets the block cipher to use for this GCM object.
+ *
+ * \param cipher The block cipher to use to implement GCM mode.
+ * This object must have a block size of 128 bits (16 bytes).
+ */
+
+/**
+ * \class GCM GCM.h
+ * \brief Implementation of the Galois Counter Mode (GCM).
+ *
+ * GCM mode converts a block cipher into an authenticated cipher
+ * that uses the block cipher T to encrypt and GHASH to authenticate.
+ *
+ * The size of the key is determined by the underlying block cipher T.
+ * The IV is recommended to be 96 bits (12 bytes) in length, but other
+ * lengths are supported as well. The default tagSize() is 128 bits
+ * (16 bytes) but the GCM specification does allow other tag sizes:
+ * 32, 64, 96, 104, 112, 120, or 128 bits (4, 8, 12, 13, 14, 15, or 16 bytes).
+ *
+ * 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
+ * GCM object using AES256 as the underlying cipher and then uses it
+ * to encrypt and authenticate a \c plaintext block:
+ *
+ * \code
+ * GCM gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata, sizeof(adata));
+ * gcm.encrypt(ciphertext, plaintext, sizeof(plaintext));
+ * gcm.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
+ * GCM gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata, sizeof(adata));
+ * gcm.decrypt(ciphertext, plaintext, sizeof(plaintext));
+ * if (!gcm.checkTag(tag, sizeof(tag))) {
+ * // The data was invalid - do not use it.
+ * ...
+ * }
+ * \endcode
+ *
+ * The GCM class can also be used to implement GMAC message authentication
+ * by omitting the plaintext:
+ *
+ * \code
+ * GCM gcm;
+ * gcm.setKey(key, sizeof(key));
+ * gcm.setIV(iv, sizeof(iv));
+ * gcm.addAuthData(adata1, sizeof(adata1));
+ * gcm.addAuthData(adata2, sizeof(adata1));
+ * ...
+ * gcm.addAuthData(adataN, sizeof(adataN));
+ * gcm.computeTag(tag, sizeof(tag));
+ * \endcode
+ *
+ * References: NIST SP 800-38D,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCMCommon, GHASH
+ */
+
+/**
+ * \fn GCM::GCM()
+ * \brief Constructs a new GCM object for the block cipher T.
+ */
diff --git a/libraries/Crypto/GCM.h b/libraries/Crypto/GCM.h
new file mode 100644
index 00000000..6f204337
--- /dev/null
+++ b/libraries/Crypto/GCM.h
@@ -0,0 +1,80 @@
+/*
+ * 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_GCM_h
+#define CRYPTO_GCM_h
+
+#include "AuthenticatedCipher.h"
+#include "BlockCipher.h"
+#include "GHASH.h"
+
+class GCMCommon : public AuthenticatedCipher
+{
+public:
+ virtual ~GCMCommon();
+
+ 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:
+ GCMCommon();
+ void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; }
+
+private:
+ BlockCipher *blockCipher;
+ GHASH ghash;
+ struct {
+ uint8_t counter[16];
+ uint8_t stream[16];
+ uint8_t nonce[16];
+ uint64_t authSize;
+ uint64_t dataSize;
+ bool dataStarted;
+ uint8_t posn;
+ } state;
+};
+
+template
+class GCM : public GCMCommon
+{
+public:
+ GCM() { setBlockCipher(&cipher); }
+
+private:
+ T cipher;
+};
+
+#endif
diff --git a/libraries/Crypto/examples/TestGCM/TestGCM.ino b/libraries/Crypto/examples/TestGCM/TestGCM.ino
new file mode 100644
index 00000000..dd015615
--- /dev/null
+++ b/libraries/Crypto/examples/TestGCM/TestGCM.ino
@@ -0,0 +1,536 @@
+/*
+ * 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 GCM implementation to verify correct behaviour.
+*/
+
+#include
+#include
+#include
+#include
+#include
+
+#define MAX_PLAINTEXT_LEN 64
+
+struct TestVector
+{
+ const char *name;
+ uint8_t key[32];
+ uint8_t plaintext[MAX_PLAINTEXT_LEN];
+ uint8_t ciphertext[MAX_PLAINTEXT_LEN];
+ uint8_t authdata[20];
+ uint8_t iv[12];
+ uint8_t tag[16];
+ size_t authsize;
+ size_t datasize;
+ size_t tagsize;
+ size_t ivsize;
+};
+
+// Test vectors for AES in GCM mode from Appendix B of:
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+static TestVector const testVectorGCM1 PROGMEM = {
+ .name = "AES-128 GCM #1",
+ .key = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ .plaintext = {0x00},
+ .ciphertext = {0x00},
+ .authdata = {0x00},
+ .iv = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00},
+ .tag = {0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61,
+ 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a},
+ .authsize = 0,
+ .datasize = 0,
+ .tagsize = 16,
+ .ivsize = 12
+};
+static TestVector const testVectorGCM2 PROGMEM = {
+ .name = "AES-128 GCM #2",
+ .key = {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},
+ .ciphertext = {0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92,
+ 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78},
+ .authdata = {0x00},
+ .iv = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00},
+ .tag = {0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd,
+ 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf},
+ .authsize = 0,
+ .datasize = 16,
+ .tagsize = 16,
+ .ivsize = 12
+};
+static TestVector const testVectorGCM3 PROGMEM = {
+ .name = "AES-128 GCM #3",
+ .key = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+ .plaintext = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55},
+ .ciphertext = {0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+ 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+ 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+ 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+ 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+ 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+ 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+ 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85},
+ .authdata = {0x00},
+ .iv = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88},
+ .tag = {0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6,
+ 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4},
+ .authsize = 0,
+ .datasize = 64,
+ .tagsize = 16,
+ .ivsize = 12
+};
+static TestVector const testVectorGCM4 PROGMEM = {
+ .name = "AES-128 GCM #4",
+ .key = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+ .plaintext = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39},
+ .ciphertext = {0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24,
+ 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c,
+ 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0,
+ 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e,
+ 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c,
+ 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05,
+ 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97,
+ 0x3d, 0x58, 0xe0, 0x91},
+ .authdata = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2},
+ .iv = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88},
+ .tag = {0x5b, 0xc9, 0x4f, 0xbc, 0x32, 0x21, 0xa5, 0xdb,
+ 0x94, 0xfa, 0xe9, 0x5a, 0xe7, 0x12, 0x1a, 0x47},
+ .authsize = 20,
+ .datasize = 60,
+ .tagsize = 16,
+ .ivsize = 12
+};
+static TestVector const testVectorGCM5 PROGMEM = {
+ .name = "AES-128 GCM #5",
+ .key = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+ .plaintext = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39},
+ .ciphertext = {0x61, 0x35, 0x3b, 0x4c, 0x28, 0x06, 0x93, 0x4a,
+ 0x77, 0x7f, 0xf5, 0x1f, 0xa2, 0x2a, 0x47, 0x55,
+ 0x69, 0x9b, 0x2a, 0x71, 0x4f, 0xcd, 0xc6, 0xf8,
+ 0x37, 0x66, 0xe5, 0xf9, 0x7b, 0x6c, 0x74, 0x23,
+ 0x73, 0x80, 0x69, 0x00, 0xe4, 0x9f, 0x24, 0xb2,
+ 0x2b, 0x09, 0x75, 0x44, 0xd4, 0x89, 0x6b, 0x42,
+ 0x49, 0x89, 0xb5, 0xe1, 0xeb, 0xac, 0x0f, 0x07,
+ 0xc2, 0x3f, 0x45, 0x98},
+ .authdata = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2},
+ .iv = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad},
+ .tag = {0x36, 0x12, 0xd2, 0xe7, 0x9e, 0x3b, 0x07, 0x85,
+ 0x56, 0x1b, 0xe1, 0x4a, 0xac, 0xa2, 0xfc, 0xcb},
+ .authsize = 20,
+ .datasize = 60,
+ .tagsize = 16,
+ .ivsize = 8
+};
+static TestVector const testVectorGCM10 PROGMEM = {
+ .name = "AES-192 GCM #10",
+ .key = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c},
+ .plaintext = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39},
+ .ciphertext = {0x39, 0x80, 0xca, 0x0b, 0x3c, 0x00, 0xe8, 0x41,
+ 0xeb, 0x06, 0xfa, 0xc4, 0x87, 0x2a, 0x27, 0x57,
+ 0x85, 0x9e, 0x1c, 0xea, 0xa6, 0xef, 0xd9, 0x84,
+ 0x62, 0x85, 0x93, 0xb4, 0x0c, 0xa1, 0xe1, 0x9c,
+ 0x7d, 0x77, 0x3d, 0x00, 0xc1, 0x44, 0xc5, 0x25,
+ 0xac, 0x61, 0x9d, 0x18, 0xc8, 0x4a, 0x3f, 0x47,
+ 0x18, 0xe2, 0x44, 0x8b, 0x2f, 0xe3, 0x24, 0xd9,
+ 0xcc, 0xda, 0x27, 0x10},
+ .authdata = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2},
+ .iv = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88},
+ .tag = {0x25, 0x19, 0x49, 0x8e, 0x80, 0xf1, 0x47, 0x8f,
+ 0x37, 0xba, 0x55, 0xbd, 0x6d, 0x27, 0x61, 0x8c},
+ .authsize = 20,
+ .datasize = 60,
+ .tagsize = 16,
+ .ivsize = 12
+};
+static TestVector const testVectorGCM16 PROGMEM = {
+ .name = "AES-256 GCM #16",
+ .key = {0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08,
+ 0xfe, 0xff, 0xe9, 0x92, 0x86, 0x65, 0x73, 0x1c,
+ 0x6d, 0x6a, 0x8f, 0x94, 0x67, 0x30, 0x83, 0x08},
+ .plaintext = {0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
+ 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
+ 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
+ 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
+ 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
+ 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
+ 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
+ 0xba, 0x63, 0x7b, 0x39},
+ .ciphertext = {0x52, 0x2d, 0xc1, 0xf0, 0x99, 0x56, 0x7d, 0x07,
+ 0xf4, 0x7f, 0x37, 0xa3, 0x2a, 0x84, 0x42, 0x7d,
+ 0x64, 0x3a, 0x8c, 0xdc, 0xbf, 0xe5, 0xc0, 0xc9,
+ 0x75, 0x98, 0xa2, 0xbd, 0x25, 0x55, 0xd1, 0xaa,
+ 0x8c, 0xb0, 0x8e, 0x48, 0x59, 0x0d, 0xbb, 0x3d,
+ 0xa7, 0xb0, 0x8b, 0x10, 0x56, 0x82, 0x88, 0x38,
+ 0xc5, 0xf6, 0x1e, 0x63, 0x93, 0xba, 0x7a, 0x0a,
+ 0xbc, 0xc9, 0xf6, 0x62},
+ .authdata = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2},
+ .iv = {0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
+ 0xde, 0xca, 0xf8, 0x88},
+ .tag = {0x76, 0xfc, 0x6e, 0xce, 0x0f, 0x4e, 0x17, 0x68,
+ 0xcd, 0xdf, 0x88, 0x53, 0xbb, 0x2d, 0x55, 0x1b},
+ .authsize = 20,
+ .datasize = 60,
+ .tagsize = 16,
+ .ivsize = 12
+};
+
+TestVector testVector;
+
+GCM *gcmaes128 = 0;
+GCM *gcmaes192 = 0;
+GCM *gcmaes256 = 0;
+
+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, cipher->keySize())) {
+ Serial.print("setKey ");
+ return false;
+ }
+ if (!cipher->setIV(test->iv, test->ivsize)) {
+ Serial.print("setIV ");
+ return false;
+ }
+
+ memset(buffer, 0xBA, sizeof(buffer));
+
+ 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, cipher->keySize());
+ 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)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" SetKey ... ");
+
+ start = micros();
+ for (count = 0; count < 1000; ++count) {
+ cipher->setKey(test->key, cipher->keySize());
+ 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)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" Encrypt ... ");
+
+ cipher->setKey(test->key, cipher->keySize());
+ 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)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" Decrypt ... ");
+
+ cipher->setKey(test->key, cipher->keySize());
+ 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)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" AddAuthData ... ");
+
+ cipher->setKey(test->key, cipher->keySize());
+ 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)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ memcpy_P(&testVector, test, sizeof(TestVector));
+ test = &testVector;
+
+ Serial.print(test->name);
+ Serial.print(" ComputeTag ... ");
+
+ cipher->setKey(test->key, cipher->keySize());
+ 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)
+{
+ perfCipherSetKey(cipher, test);
+ perfCipherEncrypt(cipher, test);
+ perfCipherDecrypt(cipher, test);
+ perfCipherAddAuthData(cipher, test);
+ perfCipherComputeTag(cipher, test);
+}
+
+void setup()
+{
+ Serial.begin(9600);
+
+ Serial.println();
+
+ Serial.println("State Sizes:");
+ Serial.print("GCM ... ");
+ Serial.println(sizeof(*gcmaes128));
+ Serial.print("GCM ... ");
+ Serial.println(sizeof(*gcmaes192));
+ Serial.print("GCM ... ");
+ Serial.println(sizeof(*gcmaes256));
+ Serial.println();
+
+ Serial.println("Test Vectors:");
+ gcmaes128 = new GCM();
+ testCipher(gcmaes128, &testVectorGCM1);
+ testCipher(gcmaes128, &testVectorGCM2);
+ testCipher(gcmaes128, &testVectorGCM3);
+ testCipher(gcmaes128, &testVectorGCM4);
+ testCipher(gcmaes128, &testVectorGCM5);
+ delete gcmaes128;
+ gcmaes192 = new GCM();
+ testCipher(gcmaes192, &testVectorGCM10);
+ delete gcmaes192;
+ gcmaes256 = new GCM();
+ testCipher(gcmaes256, &testVectorGCM16);
+ delete gcmaes256;
+
+ Serial.println();
+
+ Serial.println("Performance Tests:");
+ gcmaes128 = new GCM();
+ perfCipher(gcmaes128, &testVectorGCM1);
+ delete gcmaes128;
+ gcmaes192 = new GCM();
+ perfCipher(gcmaes192, &testVectorGCM10);
+ delete gcmaes192;
+ gcmaes256 = new GCM();
+ perfCipher(gcmaes256, &testVectorGCM16);
+ delete gcmaes256;
+}
+
+void loop()
+{
+}