diff --git a/doc/crypto.dox b/doc/crypto.dox
index ebb06caa..bd3d10eb 100644
--- a/doc/crypto.dox
+++ b/doc/crypto.dox
@@ -28,10 +28,10 @@
\li Block ciphers: AES128, AES192, AES256
\li Block cipher modes: CTR, CFB, CBC, OFB
-\li Stream ciphers: ChaCha
+li Stream ciphers: ChaCha
\li Authenticated encryption with associated data (AEAD): ChaChaPoly
\li Hash algorithms: SHA1, SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes)
-\li Message authenticators: Poly1305
+\li Message authenticators: Poly1305, GHASH
\li Public key algorithms: Curve25519
\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource, RingOscillatorNoiseSource
@@ -78,6 +78,7 @@ Ardunino Mega 2560 running at 16 MHz are similar:
BLAKE2s | 18.54us | | | 171 |
BLAKE2b | 50.58us | | | 339 |
Poly1305 | 26.29us | | | 87 |
+GHASH | 148.14us | | | 33 |
Where a cipher supports more than one key size (such as ChaCha), the values
diff --git a/doc/mainpage.dox b/doc/mainpage.dox
index 48eb9de6..01664e1e 100644
--- a/doc/mainpage.dox
+++ b/doc/mainpage.dox
@@ -96,7 +96,7 @@ realtime clock and the LCD library to implement an alarm clock.
\li Stream ciphers: ChaCha
\li Authenticated encryption with associated data (AEAD): ChaChaPoly
\li Hash algorithms: SHA1, SHA256, SHA512, SHA3_256, SHA3_512, BLAKE2s, BLAKE2b (regular and HMAC modes)
-\li Message authenticators: Poly1305
+\li Message authenticators: Poly1305, GHASH
\li Public key algorithms: Curve25519
\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource, RingOscillatorNoiseSource
diff --git a/libraries/Crypto/GHASH.cpp b/libraries/Crypto/GHASH.cpp
new file mode 100644
index 00000000..f8fd6797
--- /dev/null
+++ b/libraries/Crypto/GHASH.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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 "GHASH.h"
+#include "Crypto.h"
+#include "utility/EndianUtil.h"
+#include
+
+/**
+ * \class GHASH GHASH.h
+ * \brief Implementation of the GHASH message authenticator.
+ *
+ * GHASH is the message authentication part of Galois Counter Mode (GCM).
+ *
+ * \note GHASH is not the same as GMAC. GHASH implements the low level
+ * hashing primitive that is used by both GCM and GMAC. GMAC can be
+ * simulated using GCM and an empty plaintext/ciphertext.
+ *
+ * References: NIST SP 800-38D,
+ * http://en.wikipedia.org/wiki/Galois/Counter_Mode
+ *
+ * \sa GCM
+ */
+
+/**
+ * \brief Constructs a new GHASH message authenticator.
+ */
+GHASH::GHASH()
+{
+ state.posn = 0;
+}
+
+/**
+ * \brief Destroys this GHASH message authenticator.
+ */
+GHASH::~GHASH()
+{
+ clean(state);
+}
+
+/**
+ * \brief Resets the GHASH message authenticator for a new session.
+ *
+ * \param key Points to the 16 byte authentication key.
+ *
+ * \sa update(), finalize()
+ */
+void GHASH::reset(const void *key)
+{
+ // Copy the key into H and convert from big endian to host order.
+ memcpy(state.H, key, 16);
+#if defined(CRYPTO_LITTLE_ENDIAN)
+ state.H[0] = be32toh(state.H[0]);
+ state.H[1] = be32toh(state.H[1]);
+ state.H[2] = be32toh(state.H[2]);
+ state.H[3] = be32toh(state.H[3]);
+#endif
+
+ // Reset the hash.
+ memset(state.Y, 0, sizeof(state.Y));
+ state.posn = 0;
+}
+
+/**
+ * \brief Updates the message authenticator with more data.
+ *
+ * \param data Data to be hashed.
+ * \param len Number of bytes of data to be hashed.
+ *
+ * If finalize() has already been called, then the behavior of update() will
+ * be undefined. Call reset() first to start a new authentication process.
+ *
+ * \sa pad(), reset(), finalize()
+ */
+void GHASH::update(const void *data, size_t len)
+{
+ // XOR the input with state.Y in 128-bit chunks and process them.
+ const uint8_t *d = (const uint8_t *)data;
+ while (len > 0) {
+ uint8_t size = 16 - state.posn;
+ if (size > len)
+ size = len;
+ uint8_t *y = ((uint8_t *)state.Y) + state.posn;
+ for (uint8_t i = 0; i < size; ++i)
+ y[i] ^= d[i];
+ state.posn += size;
+ len -= size;
+ d += size;
+ if (state.posn == 16) {
+ processChunk();
+ state.posn = 0;
+ }
+ }
+}
+
+/**
+ * \brief Finalizes the authentication process and returns the token.
+ *
+ * \param token The buffer to return the token value in.
+ * \param len The length of the \a token buffer between 0 and 16.
+ *
+ * If \a len is less than 16, then the token value will be truncated to
+ * the first \a len bytes. If \a len is greater than 16, then the remaining
+ * bytes will left unchanged.
+ *
+ * If finalize() is called again, then the returned \a token value is
+ * undefined. Call reset() first to start a new authentication process.
+ *
+ * \sa reset(), update()
+ */
+void GHASH::finalize(void *token, size_t len)
+{
+ // Pad with zeroes to a multiple of 16 bytes.
+ pad();
+
+ // The token is the current value of Y.
+ if (len > 16)
+ len = 16;
+ memcpy(token, state.Y, len);
+}
+
+/**
+ * \brief Pads the input stream with zero bytes to a multiple of 16.
+ *
+ * \sa update()
+ */
+void GHASH::pad()
+{
+ if (state.posn != 0) {
+ // Padding involves XOR'ing the rest of state.Y with zeroes,
+ // which does nothing. Immediately process the next chunk.
+ processChunk();
+ state.posn = 0;
+ }
+}
+
+/**
+ * \brief Clears the authenticator's state, removing all sensitive data.
+ */
+void GHASH::clear()
+{
+ clean(state);
+}
+
+void GHASH::processChunk()
+{
+ uint32_t Z0 = 0; // Z = 0
+ uint32_t Z1 = 0;
+ uint32_t Z2 = 0;
+ uint32_t Z3 = 0;
+ uint32_t V0 = state.H[0]; // V = H
+ uint32_t V1 = state.H[1];
+ uint32_t V2 = state.H[2];
+ uint32_t V3 = state.H[3];
+
+ // Multiply Z by V for the set bits in Y, starting at the top.
+ // This is a very simple bit by bit version that may not be very
+ // fast but it should be resistant to cache timing attacks.
+ for (uint8_t posn = 0; posn < 16; ++posn) {
+ uint8_t value = ((const uint8_t *)state.Y)[posn];
+ for (uint8_t bit = 0; bit < 8; ++bit, value <<= 1) {
+ // Extract the high bit of "value" and turn it into a mask.
+ uint32_t mask = (~((uint32_t)(value >> 7))) + 1;
+
+ // XOR V with Z if the bit is 1.
+ Z0 ^= (V0 & mask);
+ Z1 ^= (V1 & mask);
+ Z2 ^= (V2 & mask);
+ Z3 ^= (V3 & mask);
+
+ // Rotate V right by 1 bit.
+ mask = ((~(V3 & 0x01)) + 1) & 0xE1000000;
+ V3 = (V3 >> 1) | (V2 << 31);
+ V2 = (V2 >> 1) | (V1 << 31);
+ V1 = (V1 >> 1) | (V0 << 31);
+ V0 = (V0 >> 1) ^ mask;
+ }
+ }
+
+ // We have finished the block so copy Z into Y and byte-swap.
+ state.Y[0] = htobe32(Z0);
+ state.Y[1] = htobe32(Z1);
+ state.Y[2] = htobe32(Z2);
+ state.Y[3] = htobe32(Z3);
+}
diff --git a/libraries/Crypto/GHASH.h b/libraries/Crypto/GHASH.h
new file mode 100644
index 00000000..07a84bfa
--- /dev/null
+++ b/libraries/Crypto/GHASH.h
@@ -0,0 +1,53 @@
+/*
+ * 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_GHASH_h
+#define CRYPTO_GHASH_h
+
+#include
+#include
+
+class GHASH
+{
+public:
+ GHASH();
+ ~GHASH();
+
+ void reset(const void *key);
+ void update(const void *data, size_t len);
+ void finalize(void *token, size_t len);
+
+ void pad();
+
+ void clear();
+
+private:
+ struct {
+ uint32_t H[4];
+ uint32_t Y[4];
+ uint8_t posn;
+ } state;
+
+ void processChunk();
+};
+
+#endif
diff --git a/libraries/Crypto/examples/TestGHASH/TestGHASH.ino b/libraries/Crypto/examples/TestGHASH/TestGHASH.ino
new file mode 100644
index 00000000..66b69454
--- /dev/null
+++ b/libraries/Crypto/examples/TestGHASH/TestGHASH.ino
@@ -0,0 +1,201 @@
+/*
+ * 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 GHASH implementation to verify correct behaviour.
+*/
+
+#include
+#include
+#include
+
+// Test vectors from Appendix B of:
+// http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+
+struct TestVector
+{
+ const char *name;
+ uint8_t key[16];
+ uint8_t data[112];
+ size_t dataLen;
+ uint8_t hash[16];
+};
+
+static TestVector const testVectorGHASH_1 = {
+ .name = "GHASH #1",
+ .key = {0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b,
+ 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e},
+ .data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ .dataLen = 16,
+ .hash = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+static TestVector const testVectorGHASH_2 = {
+ .name = "GHASH #2",
+ .key = {0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b,
+ 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e},
+ .data = {0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92,
+ 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80},
+ .dataLen = 32,
+ .hash = {0xf3, 0x8c, 0xbb, 0x1a, 0xd6, 0x92, 0x23, 0xdc,
+ 0xc3, 0x45, 0x7a, 0xe5, 0xb6, 0xb0, 0xf8, 0x85}
+};
+static TestVector const testVectorGHASH_3 = {
+ .name = "GHASH #3",
+ .key = {0xb8, 0x3b, 0x53, 0x37, 0x08, 0xbf, 0x53, 0x5d,
+ 0x0a, 0xa6, 0xe5, 0x29, 0x80, 0xd5, 0x3b, 0x78},
+ .data = {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,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00},
+ .dataLen = 80,
+ .hash = {0x7f, 0x1b, 0x32, 0xb8, 0x1b, 0x82, 0x0d, 0x02,
+ 0x61, 0x4f, 0x88, 0x95, 0xac, 0x1d, 0x4e, 0xac}
+};
+static TestVector const testVectorGHASH_4 = {
+ .name = "GHASH #4",
+ .key = {0xb8, 0x3b, 0x53, 0x37, 0x08, 0xbf, 0x53, 0x5d,
+ 0x0a, 0xa6, 0xe5, 0x29, 0x80, 0xd5, 0x3b, 0x78},
+ .data = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef,
+ 0xab, 0xad, 0xda, 0xd2, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 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, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0},
+ .dataLen = 112,
+ .hash = {0x69, 0x8e, 0x57, 0xf7, 0x0e, 0x6e, 0xcc, 0x7f,
+ 0xd9, 0x46, 0x3b, 0x72, 0x60, 0xa9, 0xae, 0x5f}
+};
+
+GHASH ghash;
+
+byte buffer[128];
+
+bool testGHASH_N(GHASH *hash, const struct TestVector *test, size_t inc)
+{
+ size_t size = test->dataLen;
+ size_t posn, len;
+
+ hash->reset(test->key);
+
+ for (posn = 0; posn < size; posn += inc) {
+ len = size - posn;
+ if (len > inc)
+ len = inc;
+ hash->update(test->data + posn, len);
+ }
+
+ hash->finalize(buffer, 16);
+
+ return !memcmp(buffer, test->hash, 16);
+}
+
+void testGHASH(GHASH *hash, const struct TestVector *test)
+{
+ bool ok;
+
+ Serial.print(test->name);
+ Serial.print(" ... ");
+
+ ok = testGHASH_N(hash, test, test->dataLen);
+ ok &= testGHASH_N(hash, test, 1);
+ ok &= testGHASH_N(hash, test, 2);
+ ok &= testGHASH_N(hash, test, 5);
+ ok &= testGHASH_N(hash, test, 8);
+ ok &= testGHASH_N(hash, test, 13);
+ ok &= testGHASH_N(hash, test, 16);
+ ok &= testGHASH_N(hash, test, 24);
+ ok &= testGHASH_N(hash, test, 63);
+ ok &= testGHASH_N(hash, test, 64);
+
+ if (ok)
+ Serial.println("Passed");
+ else
+ Serial.println("Failed");
+}
+
+void perfGHASH(GHASH *hash)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ Serial.print("Hashing ... ");
+
+ for (size_t posn = 0; posn < sizeof(buffer); ++posn)
+ buffer[posn] = (uint8_t)posn;
+
+ hash->reset(testVectorGHASH_1.key);
+ start = micros();
+ for (count = 0; count < 200; ++count) {
+ hash->update(buffer, sizeof(buffer));
+ }
+ elapsed = micros() - start;
+
+ Serial.print(elapsed / (sizeof(buffer) * 200.0));
+ Serial.print("us per byte, ");
+ Serial.print((sizeof(buffer) * 200.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+}
+
+void setup()
+{
+ Serial.begin(9600);
+
+ Serial.println();
+
+ Serial.print("State Size ... ");
+ Serial.println(sizeof(GHASH));
+ Serial.println();
+
+ Serial.println("Test Vectors:");
+ testGHASH(&ghash, &testVectorGHASH_1);
+ testGHASH(&ghash, &testVectorGHASH_2);
+ testGHASH(&ghash, &testVectorGHASH_3);
+ testGHASH(&ghash, &testVectorGHASH_4);
+
+ Serial.println();
+
+ Serial.println("Performance Tests:");
+ perfGHASH(&ghash);
+}
+
+void loop()
+{
+}