diff --git a/doc/crypto.dox b/doc/crypto.dox
index c6997c1a..0f8f2d53 100644
--- a/doc/crypto.dox
+++ b/doc/crypto.dox
@@ -37,6 +37,18 @@
\li Post-quantum algorithms: NewHope
\li Random number generation: \link RNGClass RNG\endlink, TransistorNoiseSource, RingOscillatorNoiseSource
+Reduced memory versions of some algorithms (encryption is slower, but the
+RAM required for the key schedule is less):
+
+\li AESTiny128, AESSmall128, AESTiny256, AESSmall256
+\li SpeckTiny, SpeckSmall
+
+The "tiny" versions only support encryption which makes them suitable for
+the CTR, CFB, OFB, EAX, and GCM block cipher modes but not CBC. The "small"
+versions use a little more memory but support both encryptionm and decryption.
+
+\section crypto_optimizations Optimizations
+
All cryptographic algorithms have been optimized for 8-bit Arduino platforms
like the Uno. Memory usage is also reduced, particularly for SHA256
and SHA512 which save 192 and 512 bytes respectively over traditional
@@ -76,9 +88,13 @@ Ardunino Mega 2560 running at 16 MHz are similar:
Encryption Algorithm | Encryption (per byte) | Decryption (per byte) | Key Setup | State Size (bytes) |
-AES128 (ECB mode) | 33.28us | 63.18us | 160.00us | 181 |
-AES192 (ECB mode) | 39.94us | 76.48us | 166.54us | 213 |
-AES256 (ECB mode) | 46.61us | 89.78us | 227.97us | 245 |
+AES128 (ECB mode) | 33.28us | 63.18us | 158.68us | 181 |
+AES192 (ECB mode) | 39.94us | 76.48us | 165.34us | 213 |
+AES256 (ECB mode) | 46.61us | 89.78us | 217.79us | 245 |
+AESTiny128 (ECB mode) | 40.37us | | 10.16us | 18 |
+AESTiny256 (ECB mode) | 56.84us | | 17.20us | 34 |
+AESSmall128 (ECB mode) | 40.37us | 71.36us | 134.22us | 34 |
+AESSmall256 (ECB mode) | 56.84us | 100.55us | 177.73us | 66 |
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 |
@@ -159,9 +175,13 @@ All figures are for the Arduino Due running at 84 MHz:
Encryption Algorithm | Encryption (per byte) | Decryption (per byte) | Key Setup | State Size (bytes) |
-AES128 (ECB mode) | 5.71us | 10.41us | 34.73us | 188 |
-AES192 (ECB mode) | 6.87us | 12.57us | 36.51us | 220 |
-AES256 (ECB mode) | 8.04us | 14.72 | 49.96us | 252 |
+AES128 (ECB mode) | 6.58us | 11.40us | 38.15us | 188 |
+AES192 (ECB mode) | 7.94us | 13.83us | 39.79us | 220 |
+AES256 (ECB mode) | 9.30us | 16.25us | 49.68us | 252 |
+AESTiny128 (ECB mode) | 7.23us | | 1.25us | 20 |
+AESTiny256 (ECB mode) | 10.62us | | 1.43us | 36 |
+AESSmall128 (ECB mode) | 7.23us | 12.33us | 23.44us | 36 |
+AESSmall256 (ECB mode) | 10.62us | 16.92us | 31.88us | 68 |
ChaCha (20 rounds) | 0.87us | 0.88us | 4.96us | 136 |
ChaCha (12 rounds) | 0.70us | 0.71us | 4.96us | 136 |
ChaCha (8 rounds) | 0.62us | 0.62us | 4.96us | 136 |
diff --git a/host/Crypto/Makefile b/host/Crypto/Makefile
index 23d7ada2..55953ce8 100644
--- a/host/Crypto/Makefile
+++ b/host/Crypto/Makefile
@@ -67,6 +67,8 @@ SOURCES = \
SKETCHES = \
TestAES/TestAES.ino \
+ TestAESTiny/TestAESTiny.ino \
+ TestAESSmall/TestAESSmall.ino \
TestBigNumberUtil/TestBigNumberUtil.ino \
TestBLAKE2b/TestBLAKE2b.ino \
TestBLAKE2s/TestBLAKE2s.ino \
diff --git a/libraries/Crypto/AES.h b/libraries/Crypto/AES.h
index 9db65897..22b8ae4f 100644
--- a/libraries/Crypto/AES.h
+++ b/libraries/Crypto/AES.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ * Copyright (C) 2015,2018 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"),
@@ -25,6 +25,11 @@
#include "BlockCipher.h"
+class AESTiny128;
+class AESTiny256;
+class AESSmall128;
+class AESSmall256;
+
class AESCommon : public BlockCipher
{
public:
@@ -40,13 +45,22 @@ public:
protected:
AESCommon();
- /** @cond */
+ /** @cond aes_internal */
uint8_t rounds;
uint8_t *schedule;
- void keyScheduleCore(uint8_t *output, const uint8_t *input, uint8_t iteration);
- void applySbox(uint8_t *output, const uint8_t *input);
+ static void subBytesAndShiftRows(uint8_t *output, const uint8_t *input);
+ static void inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input);
+ static void mixColumn(uint8_t *output, uint8_t *input);
+ static void inverseMixColumn(uint8_t *output, const uint8_t *input);
+ static void keyScheduleCore(uint8_t *output, const uint8_t *input, uint8_t iteration);
+ static void applySbox(uint8_t *output, const uint8_t *input);
/** @endcond */
+
+ friend class AESTiny128;
+ friend class AESTiny256;
+ friend class AESSmall128;
+ friend class AESSmall256;
};
class AES128 : public AESCommon
@@ -91,4 +105,76 @@ private:
uint8_t sched[240];
};
+class AESTiny256 : public BlockCipher
+{
+public:
+ AESTiny256();
+ virtual ~AESTiny256();
+
+ size_t blockSize() const;
+ size_t keySize() const;
+
+ bool setKey(const uint8_t *key, size_t len);
+
+ void encryptBlock(uint8_t *output, const uint8_t *input);
+ void decryptBlock(uint8_t *output, const uint8_t *input);
+
+ void clear();
+
+private:
+ uint8_t schedule[32];
+};
+
+class AESSmall256 : public AESTiny256
+{
+public:
+ AESSmall256();
+ virtual ~AESSmall256();
+
+ bool setKey(const uint8_t *key, size_t len);
+
+ void decryptBlock(uint8_t *output, const uint8_t *input);
+
+ void clear();
+
+private:
+ uint8_t reverse[32];
+};
+
+class AESTiny128 : public BlockCipher
+{
+public:
+ AESTiny128();
+ virtual ~AESTiny128();
+
+ size_t blockSize() const;
+ size_t keySize() const;
+
+ bool setKey(const uint8_t *key, size_t len);
+
+ void encryptBlock(uint8_t *output, const uint8_t *input);
+ void decryptBlock(uint8_t *output, const uint8_t *input);
+
+ void clear();
+
+private:
+ uint8_t schedule[16];
+};
+
+class AESSmall128 : public AESTiny128
+{
+public:
+ AESSmall128();
+ virtual ~AESSmall128();
+
+ bool setKey(const uint8_t *key, size_t len);
+
+ void decryptBlock(uint8_t *output, const uint8_t *input);
+
+ void clear();
+
+private:
+ uint8_t reverse[16];
+};
+
#endif
diff --git a/libraries/Crypto/AES128.cpp b/libraries/Crypto/AES128.cpp
index b758059f..aaf23396 100644
--- a/libraries/Crypto/AES128.cpp
+++ b/libraries/Crypto/AES128.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ * Copyright (C) 2015,2018 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"),
@@ -28,7 +28,7 @@
* \class AES128 AES.h
* \brief AES block cipher with 128-bit keys.
*
- * \sa AES192, AES256
+ * \sa AES192, AES256, AESTiny128, AESSmall128
*/
/**
@@ -96,3 +96,257 @@ bool AES128::setKey(const uint8_t *key, size_t len)
return true;
}
+
+/**
+ * \class AESTiny128 AES.h
+ * \brief AES block cipher with 128-bit keys and tiny memory usage.
+ *
+ * This class differs from the AES128 class in the following ways:
+ *
+ * \li RAM requirements are vastly reduced. The key is stored directly
+ * and then expanded to the full key schedule round by round. The setKey()
+ * method is very fast because of this.
+ * \li Performance of encryptBlock() is slower than for AES128 due to
+ * expanding the key on the fly rather than ahead of time.
+ * \li The decryptBlock() function is not supported, which means that CBC
+ * mode cannot be used but the CTR, CFB, OFB, EAX, and GCM modes can be used.
+ *
+ * This class is useful when RAM is at a premium, CBC mode is not required,
+ * and reduced encryption performance is not a hindrance to the application.
+ *
+ * The companion AESSmall128 class supports decryptBlock() at the cost of
+ * some additional memory and slower setKey() times.
+ *
+ * \sa AESSmall128, AES128
+ */
+
+/** @cond */
+
+// Helper macros.
+#define KCORE(n) \
+ do { \
+ AESCommon::keyScheduleCore(temp, schedule + 12, (n)); \
+ schedule[0] ^= temp[0]; \
+ schedule[1] ^= temp[1]; \
+ schedule[2] ^= temp[2]; \
+ schedule[3] ^= temp[3]; \
+ } while (0)
+#define KXOR(a, b) \
+ do { \
+ schedule[(a) * 4] ^= schedule[(b) * 4]; \
+ schedule[(a) * 4 + 1] ^= schedule[(b) * 4 + 1]; \
+ schedule[(a) * 4 + 2] ^= schedule[(b) * 4 + 2]; \
+ schedule[(a) * 4 + 3] ^= schedule[(b) * 4 + 3]; \
+ } while (0)
+
+/** @endcond */
+
+/**
+ * \brief Constructs an AES 128-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESTiny128::AESTiny128()
+{
+}
+
+AESTiny128::~AESTiny128()
+{
+ clean(schedule);
+}
+
+/**
+ * \brief Size of an AES block in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny128::blockSize() const
+{
+ return 16;
+}
+
+/**
+ * \brief Size of a 128-bit AES key in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny128::keySize() const
+{
+ return 16;
+}
+
+bool AESTiny128::setKey(const uint8_t *key, size_t len)
+{
+ if (len == 16) {
+ // Make a copy of the key - it will be expanded in encryptBlock().
+ memcpy(schedule, key, 16);
+ return true;
+ }
+ return false;
+}
+
+void AESTiny128::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+ uint8_t schedule[16];
+ uint8_t posn;
+ uint8_t round;
+ uint8_t state1[16];
+ uint8_t state2[16];
+ uint8_t temp[4];
+
+ // Start with the key in the schedule buffer.
+ memcpy(schedule, this->schedule, 16);
+
+ // Copy the input into the state and XOR with the key schedule.
+ for (posn = 0; posn < 16; ++posn)
+ state1[posn] = input[posn] ^ schedule[posn];
+
+ // Perform the first 9 rounds of the cipher.
+ for (round = 1; round <= 9; ++round) {
+ // Expand the next 16 bytes of the key schedule.
+ KCORE(round);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+
+ // Encrypt using the key schedule.
+ AESCommon::subBytesAndShiftRows(state2, state1);
+ AESCommon::mixColumn(state1, state2);
+ AESCommon::mixColumn(state1 + 4, state2 + 4);
+ AESCommon::mixColumn(state1 + 8, state2 + 8);
+ AESCommon::mixColumn(state1 + 12, state2 + 12);
+ for (posn = 0; posn < 16; ++posn)
+ state1[posn] ^= schedule[posn];
+ }
+
+ // Expand the final 16 bytes of the key schedule.
+ KCORE(10);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+
+ // Perform the final round.
+ AESCommon::subBytesAndShiftRows(state2, state1);
+ for (posn = 0; posn < 16; ++posn)
+ output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESTiny128::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+ // Decryption is not supported by AESTiny128.
+}
+
+void AESTiny128::clear()
+{
+ clean(schedule);
+}
+
+/**
+ * \class AESSmall128 AES.h
+ * \brief AES block cipher with 128-bit keys and reduced memory usage.
+ *
+ * This class differs from the AES128 class in that the RAM requirements are
+ * vastly reduced. The key schedule is expanded round by round instead of
+ * being generated and stored by setKey(). The performance of encryption
+ * and decryption is slightly less because of this.
+ *
+ * This class is useful when RAM is at a premium and reduced encryption
+ * performance is not a hindrance to the application.
+ *
+ * The companion AESTiny128 class uses even less RAM but only supports the
+ * encryptBlock() operation. Block cipher modes like CTR, EAX, and GCM
+ * do not need the decryptBlock() operation, so AESTiny128 may be a better
+ * option than AESSmall128 for many applications.
+ *
+ * \sa AESTiny128, AES128
+ */
+
+/**
+ * \brief Constructs an AES 128-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESSmall128::AESSmall128()
+{
+}
+
+AESSmall128::~AESSmall128()
+{
+ clean(reverse);
+}
+
+bool AESSmall128::setKey(const uint8_t *key, size_t len)
+{
+ uint8_t *schedule;
+ uint8_t round;
+ uint8_t temp[4];
+
+ // Set the encryption key first.
+ if (!AESTiny128::setKey(key, len))
+ return false;
+
+ // Expand the key schedule up to the last round which gives
+ // us the round keys to use for the final two rounds. We can
+ // then work backwards from there in decryptBlock().
+ schedule = reverse;
+ memcpy(schedule, key, 16);
+ for (round = 1; round <= 10; ++round) {
+ KCORE(round);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+ }
+
+ // Key is ready to go.
+ return true;
+}
+
+void AESSmall128::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+ uint8_t schedule[16];
+ uint8_t round;
+ uint8_t posn;
+ uint8_t state1[16];
+ uint8_t state2[16];
+ uint8_t temp[4];
+
+ // Start with the end of the decryption schedule.
+ memcpy(schedule, reverse, 16);
+
+ // Copy the input into the state and reverse the final round.
+ for (posn = 0; posn < 16; ++posn)
+ state1[posn] = input[posn] ^ schedule[posn];
+ AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+ KXOR(3, 2);
+ KXOR(2, 1);
+ KXOR(1, 0);
+ KCORE(10);
+
+ // Perform the next 9 rounds of the decryption process.
+ for (round = 9; round >= 1; --round) {
+ // Decrypt using the key schedule.
+ for (posn = 0; posn < 16; ++posn)
+ state2[posn] ^= schedule[posn];
+ AESCommon::inverseMixColumn(state1, state2);
+ AESCommon::inverseMixColumn(state1 + 4, state2 + 4);
+ AESCommon::inverseMixColumn(state1 + 8, state2 + 8);
+ AESCommon::inverseMixColumn(state1 + 12, state2 + 12);
+ AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+
+ // Expand the next 16 bytes of the key schedule in reverse.
+ KXOR(3, 2);
+ KXOR(2, 1);
+ KXOR(1, 0);
+ KCORE(round);
+ }
+
+ // Reverse the initial round and create the output words.
+ for (posn = 0; posn < 16; ++posn)
+ output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESSmall128::clear()
+{
+ clean(reverse);
+ AESTiny128::clear();
+}
diff --git a/libraries/Crypto/AES256.cpp b/libraries/Crypto/AES256.cpp
index bca97ace..a7975ae3 100644
--- a/libraries/Crypto/AES256.cpp
+++ b/libraries/Crypto/AES256.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Southern Storm Software, Pty Ltd.
+ * Copyright (C) 2015,2018 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"),
@@ -28,7 +28,7 @@
* \class AES256 AES.h
* \brief AES block cipher with 256-bit keys.
*
- * \sa AES128, AES192
+ * \sa AES128, AES192, AESTiny256, AESSmall256
*/
/**
@@ -103,3 +103,295 @@ bool AES256::setKey(const uint8_t *key, size_t len)
return true;
}
+
+/**
+ * \class AESTiny256 AES.h
+ * \brief AES block cipher with 256-bit keys and tiny memory usage.
+ *
+ * This class differs from the AES256 class in the following ways:
+ *
+ * \li RAM requirements are vastly reduced. The key is stored directly
+ * and then expanded to the full key schedule round by round. The setKey()
+ * method is very fast because of this.
+ * \li Performance of encryptBlock() is slower than for AES256 due to
+ * expanding the key on the fly rather than ahead of time.
+ * \li The decryptBlock() function is not supported, which means that CBC
+ * mode cannot be used but the CTR, CFB, OFB, EAX, and GCM modes can be used.
+ *
+ * This class is useful when RAM is at a premium, CBC mode is not required,
+ * and reduced encryption performance is not a hindrance to the application.
+ *
+ * The companion AESSmall256 class supports decryptBlock() at the cost of
+ * some additional memory and slower setKey() times.
+ *
+ * \sa AESSmall256, AES256
+ */
+
+/** @cond */
+
+// Helper macros.
+#define LEFT 0
+#define RIGHT 16
+#define ENCRYPT(phase) \
+ do { \
+ AESCommon::subBytesAndShiftRows(state2, state1); \
+ AESCommon::mixColumn(state1, state2); \
+ AESCommon::mixColumn(state1 + 4, state2 + 4); \
+ AESCommon::mixColumn(state1 + 8, state2 + 8); \
+ AESCommon::mixColumn(state1 + 12, state2 + 12); \
+ for (posn = 0; posn < 16; ++posn) \
+ state1[posn] ^= schedule[posn + (phase)]; \
+ } while (0)
+#define DECRYPT(phase) \
+ do { \
+ for (posn = 0; posn < 16; ++posn) \
+ state2[posn] ^= schedule[posn + (phase)]; \
+ AESCommon::inverseMixColumn(state1, state2); \
+ AESCommon::inverseMixColumn(state1 + 4, state2 + 4); \
+ AESCommon::inverseMixColumn(state1 + 8, state2 + 8); \
+ AESCommon::inverseMixColumn(state1 + 12, state2 + 12); \
+ AESCommon::inverseShiftRowsAndSubBytes(state2, state1); \
+ } while (0)
+#define KCORE(n) \
+ do { \
+ AESCommon::keyScheduleCore(temp, schedule + 28, (n)); \
+ schedule[0] ^= temp[0]; \
+ schedule[1] ^= temp[1]; \
+ schedule[2] ^= temp[2]; \
+ schedule[3] ^= temp[3]; \
+ } while (0)
+#define KXOR(a, b) \
+ do { \
+ schedule[(a) * 4] ^= schedule[(b) * 4]; \
+ schedule[(a) * 4 + 1] ^= schedule[(b) * 4 + 1]; \
+ schedule[(a) * 4 + 2] ^= schedule[(b) * 4 + 2]; \
+ schedule[(a) * 4 + 3] ^= schedule[(b) * 4 + 3]; \
+ } while (0)
+#define KSBOX() \
+ do { \
+ AESCommon::applySbox(temp, schedule + 12); \
+ schedule[16] ^= temp[0]; \
+ schedule[17] ^= temp[1]; \
+ schedule[18] ^= temp[2]; \
+ schedule[19] ^= temp[3]; \
+ } while (0)
+
+/** @endcond */
+
+/**
+ * \brief Constructs an AES 256-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESTiny256::AESTiny256()
+{
+}
+
+AESTiny256::~AESTiny256()
+{
+ clean(schedule);
+}
+
+/**
+ * \brief Size of an AES block in bytes.
+ * \return Always returns 16.
+ */
+size_t AESTiny256::blockSize() const
+{
+ return 16;
+}
+
+/**
+ * \brief Size of a 256-bit AES key in bytes.
+ * \return Always returns 32.
+ */
+size_t AESTiny256::keySize() const
+{
+ return 32;
+}
+
+bool AESTiny256::setKey(const uint8_t *key, size_t len)
+{
+ if (len == 32) {
+ // Make a copy of the key - it will be expanded in encryptBlock().
+ memcpy(schedule, key, 32);
+ return true;
+ }
+ return false;
+}
+
+void AESTiny256::encryptBlock(uint8_t *output, const uint8_t *input)
+{
+ uint8_t schedule[32];
+ uint8_t posn;
+ uint8_t round;
+ uint8_t state1[16];
+ uint8_t state2[16];
+ uint8_t temp[4];
+
+ // Start with the key in the schedule buffer.
+ memcpy(schedule, this->schedule, 32);
+
+ // Copy the input into the state and perform the first round.
+ for (posn = 0; posn < 16; ++posn)
+ state1[posn] = input[posn] ^ schedule[posn];
+ ENCRYPT(RIGHT);
+
+ // Perform the next 12 rounds of the cipher two at a time.
+ for (round = 1; round <= 6; ++round) {
+ // Expand the next 32 bytes of the key schedule.
+ KCORE(round);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+ KSBOX();
+ KXOR(5, 4);
+ KXOR(6, 5);
+ KXOR(7, 6);
+
+ // Encrypt using the left and right halves of the key schedule.
+ ENCRYPT(LEFT);
+ ENCRYPT(RIGHT);
+ }
+
+ // Expand the final 16 bytes of the key schedule.
+ KCORE(7);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+
+ // Perform the final round.
+ AESCommon::subBytesAndShiftRows(state2, state1);
+ for (posn = 0; posn < 16; ++posn)
+ output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESTiny256::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+ // Decryption is not supported by AESTiny256.
+}
+
+void AESTiny256::clear()
+{
+ clean(schedule);
+}
+
+/**
+ * \class AESSmall256 AES.h
+ * \brief AES block cipher with 256-bit keys and reduced memory usage.
+ *
+ * This class differs from the AES256 class in that the RAM requirements are
+ * vastly reduced. The key schedule is expanded round by round instead of
+ * being generated and stored by setKey(). The performance of encryption
+ * and decryption is slightly less because of this.
+ *
+ * This class is useful when RAM is at a premium and reduced encryption
+ * performance is not a hindrance to the application.
+ *
+ * The companion AESTiny256 class uses even less RAM but only supports the
+ * encryptBlock() operation. Block cipher modes like CTR, EAX, and GCM
+ * do not need the decryptBlock() operation, so AESTiny256 may be a better
+ * option than AESSmall256 for many applications.
+ *
+ * \sa AESTiny256, AES256
+ */
+
+/**
+ * \brief Constructs an AES 256-bit block cipher with no initial key.
+ *
+ * This constructor must be followed by a call to setKey() before the
+ * block cipher can be used for encryption or decryption.
+ */
+AESSmall256::AESSmall256()
+{
+}
+
+AESSmall256::~AESSmall256()
+{
+ clean(reverse);
+}
+
+bool AESSmall256::setKey(const uint8_t *key, size_t len)
+{
+ uint8_t *schedule;
+ uint8_t round;
+ uint8_t temp[4];
+
+ // Set the encryption key first.
+ if (!AESTiny256::setKey(key, len))
+ return false;
+
+ // Expand the key schedule up to the last round which gives
+ // us the round keys to use for the final two rounds. We can
+ // then work backwards from there in decryptBlock().
+ schedule = reverse;
+ memcpy(schedule, key, 32);
+ for (round = 1; round <= 6; ++round) {
+ KCORE(round);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+ KSBOX();
+ KXOR(5, 4);
+ KXOR(6, 5);
+ KXOR(7, 6);
+ }
+ KCORE(7);
+ KXOR(1, 0);
+ KXOR(2, 1);
+ KXOR(3, 2);
+
+ // Key is ready to go.
+ return true;
+}
+
+void AESSmall256::decryptBlock(uint8_t *output, const uint8_t *input)
+{
+ uint8_t schedule[32];
+ uint8_t round;
+ uint8_t posn;
+ uint8_t state1[16];
+ uint8_t state2[16];
+ uint8_t temp[4];
+
+ // Start with the end of the decryption schedule.
+ memcpy(schedule, reverse, 32);
+
+ // Copy the input into the state and reverse the final round.
+ for (posn = 0; posn < 16; ++posn)
+ state1[posn] = input[posn] ^ schedule[posn];
+ AESCommon::inverseShiftRowsAndSubBytes(state2, state1);
+ KXOR(3, 2);
+ KXOR(2, 1);
+ KXOR(1, 0);
+ KCORE(7);
+
+ // Perform the next 12 rounds of the decryption process two at a time.
+ for (round = 6; round >= 1; --round) {
+ // Decrypt using the right and left halves of the key schedule.
+ DECRYPT(RIGHT);
+ DECRYPT(LEFT);
+
+ // Expand the next 32 bytes of the key schedule in reverse.
+ KXOR(7, 6);
+ KXOR(6, 5);
+ KXOR(5, 4);
+ KSBOX();
+ KXOR(3, 2);
+ KXOR(2, 1);
+ KXOR(1, 0);
+ KCORE(round);
+ }
+
+ // Reverse the initial round and create the output words.
+ DECRYPT(RIGHT);
+ for (posn = 0; posn < 16; ++posn)
+ output[posn] = state2[posn] ^ schedule[posn];
+}
+
+void AESSmall256::clear()
+{
+ clean(reverse);
+ AESTiny256::clear();
+}
diff --git a/libraries/Crypto/AESCommon.cpp b/libraries/Crypto/AESCommon.cpp
index 98fe0511..3f7af201 100644
--- a/libraries/Crypto/AESCommon.cpp
+++ b/libraries/Crypto/AESCommon.cpp
@@ -43,7 +43,7 @@
* \sa ChaCha, AES128, AES192, AES256
*/
-/** @cond */
+/** @cond sbox */
// AES S-box (http://en.wikipedia.org/wiki/Rijndael_S-box)
static uint8_t const sbox[256] PROGMEM = {
@@ -179,7 +179,9 @@ static uint8_t const K[8] = {
#define OUT(col, row) output[(col) * 4 + (row)]
#define IN(col, row) input[(col) * 4 + (row)]
-static void subBytesAndShiftRows(uint8_t *output, const uint8_t *input)
+/** @cond aes_funcs */
+
+void AESCommon::subBytesAndShiftRows(uint8_t *output, const uint8_t *input)
{
OUT(0, 0) = pgm_read_byte(sbox + IN(0, 0));
OUT(0, 1) = pgm_read_byte(sbox + IN(1, 1));
@@ -199,7 +201,7 @@ static void subBytesAndShiftRows(uint8_t *output, const uint8_t *input)
OUT(3, 3) = pgm_read_byte(sbox + IN(2, 3));
}
-static void inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input)
+void AESCommon::inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input)
{
OUT(0, 0) = pgm_read_byte(sbox_inverse + IN(0, 0));
OUT(0, 1) = pgm_read_byte(sbox_inverse + IN(3, 1));
@@ -219,7 +221,7 @@ static void inverseShiftRowsAndSubBytes(uint8_t *output, const uint8_t *input)
OUT(3, 3) = pgm_read_byte(sbox_inverse + IN(0, 3));
}
-static void mixColumn(uint8_t *output, uint8_t *input)
+void AESCommon::mixColumn(uint8_t *output, uint8_t *input)
{
uint16_t t; // Needed by the gmul2 macro.
uint8_t a = input[0];
@@ -236,7 +238,7 @@ static void mixColumn(uint8_t *output, uint8_t *input)
output[3] = a2 ^ a ^ b ^ c ^ d2;
}
-static void inverseMixColumn(uint8_t *output, const uint8_t *input)
+void AESCommon::inverseMixColumn(uint8_t *output, const uint8_t *input)
{
uint16_t t; // Needed by the gmul2, gmul4, and gmul8 macros.
uint8_t a = input[0];
@@ -261,6 +263,8 @@ static void inverseMixColumn(uint8_t *output, const uint8_t *input)
output[3] = a8 ^ a2 ^ a ^ b8 ^ b4 ^ b ^ c8 ^ c ^ d8 ^ d4 ^ d2;
}
+/** @endcond */
+
void AESCommon::encryptBlock(uint8_t *output, const uint8_t *input)
{
const uint8_t *roundKey = schedule;
@@ -328,7 +332,7 @@ void AESCommon::clear()
clean(schedule, (rounds + 1) * 16);
}
-/** @cond */
+/** @cond aes_keycore */
void AESCommon::keyScheduleCore(uint8_t *output, const uint8_t *input, uint8_t iteration)
{
diff --git a/libraries/Crypto/examples/TestAESSmall/TestAESSmall.ino b/libraries/Crypto/examples/TestAESSmall/TestAESSmall.ino
new file mode 100644
index 00000000..88b1db9b
--- /dev/null
+++ b/libraries/Crypto/examples/TestAESSmall/TestAESSmall.ino
@@ -0,0 +1,170 @@
+/*
+ * 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 small AES implementation to verify behaviour.
+*/
+
+#include
+#include
+#include
+
+struct TestVector
+{
+ const char *name;
+ byte key[32];
+ byte plaintext[16];
+ byte ciphertext[16];
+};
+
+// Define the ECB test vectors from the FIPS specification.
+static TestVector const testVectorAES128 = {
+ .name = "AES-128-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
+ 0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A}
+};
+static TestVector const testVectorAES192 = {
+ .name = "AES-192-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0xDD, 0xA9, 0x7C, 0xA4, 0x86, 0x4C, 0xDF, 0xE0,
+ 0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91}
+};
+static TestVector const testVectorAES256 = {
+ .name = "AES-256-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0x8E, 0xA2, 0xB7, 0xCA, 0x51, 0x67, 0x45, 0xBF,
+ 0xEA, 0xFC, 0x49, 0x90, 0x4B, 0x49, 0x60, 0x89}
+};
+
+AESSmall128 aes128;
+AESSmall256 aes256;
+
+byte buffer[16];
+
+void testCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+ crypto_feed_watchdog();
+ Serial.print(test->name);
+ Serial.print(" Encryption ... ");
+ cipher->setKey(test->key, cipher->keySize());
+ cipher->encryptBlock(buffer, test->plaintext);
+ if (memcmp(buffer, test->ciphertext, 16) == 0)
+ Serial.println("Passed");
+ else
+ Serial.println("Failed");
+
+ Serial.print(test->name);
+ Serial.print(" Decryption ... ");
+ cipher->decryptBlock(buffer, test->ciphertext);
+ if (memcmp(buffer, test->plaintext, 16) == 0)
+ Serial.println("Passed");
+ else
+ Serial.println("Failed");
+}
+
+void perfCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ crypto_feed_watchdog();
+
+ Serial.print(test->name);
+ Serial.print(" Set Key ... ");
+ start = micros();
+ for (count = 0; count < 10000; ++count) {
+ cipher->setKey(test->key, cipher->keySize());
+ }
+ elapsed = micros() - start;
+ Serial.print(elapsed / 10000.0);
+ Serial.print("us per operation, ");
+ Serial.print((10000.0 * 1000000.0) / elapsed);
+ Serial.println(" per second");
+
+ Serial.print(test->name);
+ Serial.print(" Encrypt ... ");
+ start = micros();
+ for (count = 0; count < 5000; ++count) {
+ cipher->encryptBlock(buffer, buffer);
+ }
+ elapsed = micros() - start;
+ Serial.print(elapsed / (5000.0 * 16.0));
+ Serial.print("us per byte, ");
+ Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+
+ Serial.print(test->name);
+ Serial.print(" Decrypt ... ");
+ start = micros();
+ for (count = 0; count < 5000; ++count) {
+ cipher->decryptBlock(buffer, buffer);
+ }
+ elapsed = micros() - start;
+ Serial.print(elapsed / (5000.0 * 16.0));
+ Serial.print("us per byte, ");
+ Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+
+ Serial.println();
+}
+
+void setup()
+{
+ Serial.begin(9600);
+
+ Serial.println();
+
+ Serial.println("State Sizes:");
+ Serial.print("AESSmall128 ... ");
+ Serial.println(sizeof(AESSmall128));
+ Serial.print("AESSmall256 ... ");
+ Serial.println(sizeof(AESSmall256));
+ Serial.println();
+
+ Serial.println("Test Vectors:");
+ testCipher(&aes128, &testVectorAES128);
+ testCipher(&aes256, &testVectorAES256);
+
+ Serial.println();
+
+ Serial.println("Performance Tests:");
+ perfCipher(&aes128, &testVectorAES128);
+ perfCipher(&aes256, &testVectorAES256);
+}
+
+void loop()
+{
+}
diff --git a/libraries/Crypto/examples/TestAESTiny/TestAESTiny.ino b/libraries/Crypto/examples/TestAESTiny/TestAESTiny.ino
new file mode 100644
index 00000000..cc32fa29
--- /dev/null
+++ b/libraries/Crypto/examples/TestAESTiny/TestAESTiny.ino
@@ -0,0 +1,150 @@
+/*
+ * 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 tiny AES implementation to verify behaviour.
+*/
+
+#include
+#include
+#include
+
+struct TestVector
+{
+ const char *name;
+ byte key[32];
+ byte plaintext[16];
+ byte ciphertext[16];
+};
+
+// Define the ECB test vectors from the FIPS specification.
+static TestVector const testVectorAES128 = {
+ .name = "AES-128-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0x69, 0xC4, 0xE0, 0xD8, 0x6A, 0x7B, 0x04, 0x30,
+ 0xD8, 0xCD, 0xB7, 0x80, 0x70, 0xB4, 0xC5, 0x5A}
+};
+static TestVector const testVectorAES192 = {
+ .name = "AES-192-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0xDD, 0xA9, 0x7C, 0xA4, 0x86, 0x4C, 0xDF, 0xE0,
+ 0x6E, 0xAF, 0x70, 0xA0, 0xEC, 0x0D, 0x71, 0x91}
+};
+static TestVector const testVectorAES256 = {
+ .name = "AES-256-ECB",
+ .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F},
+ .plaintext = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
+ 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
+ .ciphertext = {0x8E, 0xA2, 0xB7, 0xCA, 0x51, 0x67, 0x45, 0xBF,
+ 0xEA, 0xFC, 0x49, 0x90, 0x4B, 0x49, 0x60, 0x89}
+};
+
+AESTiny128 aes128;
+AESTiny256 aes256;
+
+byte buffer[16];
+
+void testCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+ crypto_feed_watchdog();
+ Serial.print(test->name);
+ Serial.print(" Encryption ... ");
+ cipher->setKey(test->key, cipher->keySize());
+ cipher->encryptBlock(buffer, test->plaintext);
+ if (memcmp(buffer, test->ciphertext, 16) == 0)
+ Serial.println("Passed");
+ else
+ Serial.println("Failed");
+}
+
+void perfCipher(BlockCipher *cipher, const struct TestVector *test)
+{
+ unsigned long start;
+ unsigned long elapsed;
+ int count;
+
+ crypto_feed_watchdog();
+
+ Serial.print(test->name);
+ Serial.print(" Set Key ... ");
+ start = micros();
+ for (count = 0; count < 10000; ++count) {
+ cipher->setKey(test->key, cipher->keySize());
+ }
+ elapsed = micros() - start;
+ Serial.print(elapsed / 10000.0);
+ Serial.print("us per operation, ");
+ Serial.print((10000.0 * 1000000.0) / elapsed);
+ Serial.println(" per second");
+
+ Serial.print(test->name);
+ Serial.print(" Encrypt ... ");
+ start = micros();
+ for (count = 0; count < 5000; ++count) {
+ cipher->encryptBlock(buffer, buffer);
+ }
+ elapsed = micros() - start;
+ Serial.print(elapsed / (5000.0 * 16.0));
+ Serial.print("us per byte, ");
+ Serial.print((16.0 * 5000.0 * 1000000.0) / elapsed);
+ Serial.println(" bytes per second");
+
+ Serial.println();
+}
+
+void setup()
+{
+ Serial.begin(9600);
+
+ Serial.println();
+
+ Serial.println("State Sizes:");
+ Serial.print("AESTiny128 ... ");
+ Serial.println(sizeof(AESTiny128));
+ Serial.print("AESTiny256 ... ");
+ Serial.println(sizeof(AESTiny256));
+ Serial.println();
+
+ Serial.println("Test Vectors:");
+ testCipher(&aes128, &testVectorAES128);
+ testCipher(&aes256, &testVectorAES256);
+
+ Serial.println();
+
+ Serial.println("Performance Tests:");
+ perfCipher(&aes128, &testVectorAES128);
+ perfCipher(&aes256, &testVectorAES256);
+}
+
+void loop()
+{
+}
diff --git a/libraries/Crypto/keywords.txt b/libraries/Crypto/keywords.txt
index f80e4824..6f28a226 100644
--- a/libraries/Crypto/keywords.txt
+++ b/libraries/Crypto/keywords.txt
@@ -1,6 +1,10 @@
AES128 KEYWORD1
AES192 KEYWORD1
AES256 KEYWORD1
+AESTiny128 KEYWORD1
+AESTiny256 KEYWORD1
+AESSmall128 KEYWORD1
+AESSmall256 KEYWORD1
Speck KEYWORD1
SpeckTiny KEYWORD1
SpeckSmall KEYWORD1