From c35172382bb1f680176579a41be3aebcbd0dc160 Mon Sep 17 00:00:00 2001 From: ant Date: Sun, 11 Apr 2021 09:31:05 +0100 Subject: [PATCH 01/16] Add base64 decode function --- src/utility/PEMUtils.cpp | 27 +++++++++++++++ src/utility/PEMUtils.h | 1 + src/utility/b64.h | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 src/utility/b64.h diff --git a/src/utility/PEMUtils.cpp b/src/utility/PEMUtils.cpp index e619e65..c7cf34d 100644 --- a/src/utility/PEMUtils.cpp +++ b/src/utility/PEMUtils.cpp @@ -18,6 +18,7 @@ */ #include "PEMUtils.h" +#include "b64.h" String PEMUtilsClass::base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix) { @@ -68,4 +69,30 @@ String PEMUtilsClass::base64Encode(const byte in[], unsigned int length, const c return out; } +int PEMUtilsClass::base64Decode(const String in, byte out[]) +{ + String inBase64 = String(in); + inBase64.trim(); + // check PEM encoded + if ( inBase64.startsWith("-----BEGIN ") && inBase64.endsWith("-----")) { + int endLine1Char = inBase64.indexOf('\n'); + if (endLine1Char < 0) { + return -2; + } + int endLastLineChar = inBase64.lastIndexOf('\n'); + if (endLastLineChar < 0) { + return -3; + } + inBase64 = inBase64.substring(endLine1Char, endLastLineChar); + for (int i=inBase64.indexOf('\n'); i != -1; i=inBase64.indexOf('\n')) { + inBase64.remove(i,1); + } + } + const char *input = inBase64.c_str(); + + int len = decode_base64(input, out); + + return len; +} + PEMUtilsClass PEMUtils; diff --git a/src/utility/PEMUtils.h b/src/utility/PEMUtils.h index 177aeec..70d6e61 100644 --- a/src/utility/PEMUtils.h +++ b/src/utility/PEMUtils.h @@ -25,6 +25,7 @@ class PEMUtilsClass { public: String base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix); + int base64Decode(const String in, byte out[]); }; extern PEMUtilsClass PEMUtils; diff --git a/src/utility/b64.h b/src/utility/b64.h new file mode 100644 index 0000000..ad717ba --- /dev/null +++ b/src/utility/b64.h @@ -0,0 +1,73 @@ +#ifndef _B64_H_ +#define _B64_H_ +/* + This base64 decode function is copied from v1.1.1 of: https://github.com/Densaugeo/base64_arduino + + MIT License (MIT) Copyright (c) 2016 Densaugeo + + Any other base64 decode function would do if there is an Arduino prefered one somewhere? +*/ +unsigned char base64_to_binary(unsigned char c) { + // Capital letters - 'A' is ascii 65 and base64 0 + if('A' <= c && c <= 'Z') return c - 'A'; + + // Lowercase letters - 'a' is ascii 97 and base64 26 + if('a' <= c && c <= 'z') return c - 71; + + // Digits - '0' is ascii 48 and base64 52 + if('0' <= c && c <= '9') return c + 4; + + // '+' is ascii 43 and base64 62 + if(c == '+') return 62; + + // '/' is ascii 47 and base64 63 + if(c == '/') return 63; + + return 255; +} + +unsigned int decode_base64_length(unsigned char input[]) { + unsigned char *start = input; + + while(base64_to_binary(input[0]) < 64) { + ++input; + } + + unsigned int input_length = input - start; + + unsigned int output_length = input_length/4*3; + + switch(input_length % 4) { + default: return output_length; + case 2: return output_length + 1; + case 3: return output_length + 2; + } +} + +unsigned int decode_base64(unsigned char input[], unsigned char output[]) { + unsigned int output_length = decode_base64_length(input); + + // While there are still full sets of 24 bits... + for(unsigned int i = 2; i < output_length; i += 3) { + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]); + + input += 4; + output += 3; + } + + switch(output_length % 3) { + case 1: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + break; + case 2: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + break; + } + + return output_length; +} + +#endif From 14f60e6bf3f14037440f9cac58da0d6b4557e1f3 Mon Sep 17 00:00:00 2001 From: ant Date: Sun, 11 Apr 2021 10:48:34 +0100 Subject: [PATCH 02/16] Add base64 decode function --- src/utility/PEMUtils.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utility/PEMUtils.cpp b/src/utility/PEMUtils.cpp index c7cf34d..08b0661 100644 --- a/src/utility/PEMUtils.cpp +++ b/src/utility/PEMUtils.cpp @@ -88,10 +88,8 @@ int PEMUtilsClass::base64Decode(const String in, byte out[]) inBase64.remove(i,1); } } - const char *input = inBase64.c_str(); - - int len = decode_base64(input, out); + int len = decode_base64((unsigned char*)inBase64.c_str(), out); return len; } From 0b0c19b8d01c30e4efc969e1a0fa185c4f6d8609 Mon Sep 17 00:00:00 2001 From: ant Date: Sun, 11 Apr 2021 11:22:32 +0100 Subject: [PATCH 03/16] Add ecdh function --- src/ECCX08.cpp | 22 ++++++++++++++++++++++ src/ECCX08.h | 2 ++ src/utility/PEMUtils.cpp | 24 ++++++++++++++++++++++++ src/utility/PEMUtils.h | 1 + 4 files changed, 49 insertions(+) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 8bb1a9a..29bfaf2 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -326,6 +326,28 @@ int ECCX08Class::endSHA256(const byte data[], int length, byte result[]) return 1; } +int ECCX08Class::ecdh(int slot, const byte pubKeyXandY[], byte sharedSecret[]) +{ + if (!wakeup()) { + return 0; + } + + if (!sendCommand(0x43, 0x0c, slot, pubKeyXandY, 64)) { + return 0; + } + + delay(55); + + if (!receiveResponse(sharedSecret, 32)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + int ECCX08Class::readSlot(int slot, byte data[], int length) { if (slot < 0 || slot > 15) { diff --git a/src/ECCX08.h b/src/ECCX08.h index 50490a5..1b6b6a4 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -50,6 +50,8 @@ class ECCX08Class int endSHA256(byte result[]); int endSHA256(const byte data[], int length, byte result[]); + int ecdh(int slot, const byte pubKeyXandY[], byte sharedSecret[]); + int readSlot(int slot, byte data[], int length); int writeSlot(int slot, const byte data[], int length); diff --git a/src/utility/PEMUtils.cpp b/src/utility/PEMUtils.cpp index 08b0661..e93faeb 100644 --- a/src/utility/PEMUtils.cpp +++ b/src/utility/PEMUtils.cpp @@ -93,4 +93,28 @@ int PEMUtilsClass::base64Decode(const String in, byte out[]) return len; } +int PEMUtilsClass::xyFromPubKeyPEM(const String publicKeyPem, byte xy[64]) +{ + byte derBytes[256]; + int derLen = PEMUtils.base64Decode(publicKeyPem, derBytes); + if (derLen < 1) { + return -4; + } + if (derLen < 65) { + return -5; + } + + int j = derLen-64; + if (derBytes[j-1] != 0x04) { // ASN.1 uncompressed public key + return -6; + } + + // the public key X and Y values are the last 64 bytes + for (int i = 0; i < 64; i++) { + xy[i] = derBytes[j++]; + } + + return 0; +} + PEMUtilsClass PEMUtils; diff --git a/src/utility/PEMUtils.h b/src/utility/PEMUtils.h index 70d6e61..ff2a007 100644 --- a/src/utility/PEMUtils.h +++ b/src/utility/PEMUtils.h @@ -26,6 +26,7 @@ class PEMUtilsClass { public: String base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix); int base64Decode(const String in, byte out[]); + int xyFromPubKeyPEM(const String in, byte xy[64]); }; extern PEMUtilsClass PEMUtils; From 597bdb61b4dd20c923b05b900be23b84ae33dc81 Mon Sep 17 00:00:00 2001 From: ant Date: Mon, 23 Aug 2021 07:07:07 +0100 Subject: [PATCH 04/16] start of example --- examples/ECCX08ECDH/ECCX08ECDH.ino | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 examples/ECCX08ECDH/ECCX08ECDH.ino diff --git a/examples/ECCX08ECDH/ECCX08ECDH.ino b/examples/ECCX08ECDH/ECCX08ECDH.ino new file mode 100644 index 0000000..be9f780 --- /dev/null +++ b/examples/ECCX08ECDH/ECCX08ECDH.ino @@ -0,0 +1,46 @@ +/* + ECCX08 ECDH + + This sketch uses the ECC608 to compute the ECDH shared secret + for a ECCX08 private key and a public key. + +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!ECCX08.begin()) { + Serial.println("Failed to communicate with ECC508/ECC608!"); + while (1); + } + + if (!ECCX08.locked()) { + Serial.println("The ECC508/ECC608 is not locked!"); + while (1); + } + + byte sharedSecret[64]; + if (!ECCX08.ecdh(2, sharedSecret)) { + Serial.println("The ecdh function failed!"); + while (1); + } + + Serial.print("Shared secret = "); + printHex(sharedSecret, 64); +} + +void loop() { +} + +void printHex(uint8_t *data, uint8_t length) { + char tmp[16]; + for (int i = 0; i < length; i++) { + sprintf(tmp, "%.2X", data[i]); + Serial.print(tmp); + Serial.print(" "); + } + Serial.println(); +} From e3ef8608602f898d52b2ccfe5ccaf83ed2b98fdf Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Wed, 25 Aug 2021 15:41:25 +0100 Subject: [PATCH 05/16] working example --- examples/ECCX08ECDH/ECCX08ECDH.ino | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/examples/ECCX08ECDH/ECCX08ECDH.ino b/examples/ECCX08ECDH/ECCX08ECDH.ino index be9f780..862bd77 100644 --- a/examples/ECCX08ECDH/ECCX08ECDH.ino +++ b/examples/ECCX08ECDH/ECCX08ECDH.ino @@ -22,14 +22,34 @@ void setup() { while (1); } - byte sharedSecret[64]; - if (!ECCX08.ecdh(2, sharedSecret)) { + int privateKeySlot = 2; + int counterpartyKeySlot = 3; + + byte devicePubKey[64]; + if (!ECCX08.generatePrivateKey(privateKeySlot, devicePubKey)){ + Serial.println("Failed to generate private key."); + } else { + Serial.println("Device Public key:"); + printHex(devicePubKey, 64); + } + + byte counterPartyPubKey[64]; + // Yes, this function actually returns a public key. + if (!ECCX08.generatePrivateKey(counterpartyKeySlot, counterPartyPubKey)){ + Serial.println("Failed to generate public key."); + } else { + Serial.println("Counterparty Public key:"); + printHex(counterPartyPubKey, 64); + } + + byte sharedSecret[32]; + if (!ECCX08.ecdh(privateKeySlot, counterPartyPubKey, sharedSecret)) { Serial.println("The ecdh function failed!"); while (1); } Serial.print("Shared secret = "); - printHex(sharedSecret, 64); + printHex(sharedSecret, 32); } void loop() { From 83f1b7f00f0e2b0ccdd61793e81fd842387c12d6 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Thu, 26 Aug 2021 17:02:55 +0100 Subject: [PATCH 06/16] Configure ECDH mode for key destination --- examples/ECCX08ECDH/ECCX08ECDH.ino | 11 ++++++++++- src/ECCX08.cpp | 14 ++++++++++---- src/ECCX08.h | 4 +++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/examples/ECCX08ECDH/ECCX08ECDH.ino b/examples/ECCX08ECDH/ECCX08ECDH.ino index 862bd77..d35698f 100644 --- a/examples/ECCX08ECDH/ECCX08ECDH.ino +++ b/examples/ECCX08ECDH/ECCX08ECDH.ino @@ -43,13 +43,22 @@ void setup() { } byte sharedSecret[32]; - if (!ECCX08.ecdh(privateKeySlot, counterPartyPubKey, sharedSecret)) { + if (!ECCX08.ecdh(privateKeySlot, ECDH_MODE_OUTPUT, counterPartyPubKey, sharedSecret)) { Serial.println("The ecdh function failed!"); while (1); } Serial.print("Shared secret = "); printHex(sharedSecret, 32); + + byte output[1]; + if (!ECCX08.ecdh(privateKeySlot, ECDH_MODE_TEMPKEY, counterPartyPubKey, output)) { + Serial.println("The ecdh function failed!"); + while (1); + } + + Serial.print("Return code = "); + printHex(output, 1); } void loop() { diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 29bfaf2..c65e09b 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -326,20 +326,26 @@ int ECCX08Class::endSHA256(const byte data[], int length, byte result[]) return 1; } -int ECCX08Class::ecdh(int slot, const byte pubKeyXandY[], byte sharedSecret[]) +int ECCX08Class::ecdh(int slot, byte mode, const byte pubKeyXandY[], byte output[]) { if (!wakeup()) { return 0; } - if (!sendCommand(0x43, 0x0c, slot, pubKeyXandY, 64)) { + if (!sendCommand(0x43, mode, slot, pubKeyXandY, 64)) { return 0; } delay(55); - if (!receiveResponse(sharedSecret, 32)) { - return 0; + if (mode == ECDH_MODE_OUTPUT) { + if (!receiveResponse(output, 32)) { + return 0; + } + } else if (mode == ECDH_MODE_TEMPKEY) { + if (!receiveResponse(output, 1)) { + return 0; + } } delay(1); diff --git a/src/ECCX08.h b/src/ECCX08.h index 1b6b6a4..ae53088 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -50,7 +50,9 @@ class ECCX08Class int endSHA256(byte result[]); int endSHA256(const byte data[], int length, byte result[]); - int ecdh(int slot, const byte pubKeyXandY[], byte sharedSecret[]); + int ecdh(int slot, byte mode, const byte pubKeyXandY[], byte sharedSecret[]); + #define ECDH_MODE_TEMPKEY ((uint8_t)0x08) //!< ECDH mode: write to TempKey + #define ECDH_MODE_OUTPUT ((uint8_t)0x0c) //!< ECDH mode: write to buffer int readSlot(int slot, byte data[], int length); int writeSlot(int slot, const byte data[], int length); From bf7690e0c61e2ce13c04aab859caa1cbb7d177ec Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Fri, 27 Aug 2021 17:21:20 +0100 Subject: [PATCH 07/16] Implement generating IV using counter on chip --- examples/ECCX08AES/ECCX08AES.ino | 54 ++++++++++++++++++ src/ECCX08.cpp | 97 ++++++++++++++++++++++++++++++++ src/ECCX08.h | 9 +++ 3 files changed, 160 insertions(+) create mode 100644 examples/ECCX08AES/ECCX08AES.ino diff --git a/examples/ECCX08AES/ECCX08AES.ino b/examples/ECCX08AES/ECCX08AES.ino new file mode 100644 index 0000000..88b003d --- /dev/null +++ b/examples/ECCX08AES/ECCX08AES.ino @@ -0,0 +1,54 @@ +/* + ECCX08 AES-GCM + + This sketch uses the ECC608 to compute + the AES_128_GCM encryption for some data + +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!ECCX08.begin()) { + Serial.println("Failed to communicate with ECC508/ECC608!"); + while (1); + } + + if (!ECCX08.locked()) { + Serial.println("The ECC508/ECC608 is not locked!"); + while (1); + } + + byte devicePubKey[64]; + if (!ECCX08.generatePublicKey(0, devicePubKey)){ + Serial.println("Failed to generate private key."); + } else { + Serial.println("Device Public key:"); + printHex(devicePubKey, 64); + } + + byte IV[12]; + if (!ECCX08.AESGenIV(IV)){ + Serial.println("Failed to initialize IV."); + } else { + Serial.print("IV: "); + printHex(IV, 12); + } + +} + +void loop() { +} + +void printHex(uint8_t *data, uint8_t length) { + char tmp[16]; + for (int i = 0; i < length; i++) { + sprintf(tmp, "%.2X", data[i]); + Serial.print(tmp); + Serial.print(" "); + } + Serial.println(); +} diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index c65e09b..15a41f6 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -354,6 +354,103 @@ int ECCX08Class::ecdh(int slot, byte mode, const byte pubKeyXandY[], byte output return 1; } +int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]) +{ + return 0; +} + +/** \brief Generates AES GCM initialization vector. + * + * \param[in,out] IV Initialization vector to be generated + * (12 bytes). See + * NIST Special Publication 800-38D + * 8.2.1 Deterministic Construction + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESGenIV(byte IV[]) +{ + // The device ID is determined by the public key in slot 0 + byte pubKey[64]; + if (!generatePublicKey(0, pubKey)){ + Serial.println("AESGenIV: failed to obtain device ID"); + return 0; + } + // XOR the 64 public key bytes to get 4 bytes + byte deviceID[4] = {0x00}; + for (int i=0; i<64; i++){ + deviceID[i%4] ^= pubKey[i]; + } + // First 4 bytes of IV are device ID + for (int i=0; i<4; i++){ + IV[i] = deviceID[i]; + } + + // Device only has two 4 byte counters + // instead of 8 byte counter. + byte counter0[4]; + if (!incrementCounter(0, counter0)){ + Serial.println("AESGenIV: failed to increment counter"); + // TODO: Reset counter0 and increment counter1 + return 0; + } + byte counter1[4]; + if (!readCounter(1, counter1)){ + Serial.println("AESGenIV: failed to read counter"); + return 0; + } + // Last 8 bytes of IV are counter + for (int i=0; i<4; i++){ + // chip counter is little endian + IV[11-i] = counter0[i]; + IV[7-i] = counter1[i]; + } + + return 1; +} + +int ECCX08Class::readCounter(int slot, byte counter[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x24, 0x00, slot)) { + return 0; + } + + delay(51); + + if (!receiveResponse(counter, 4)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + +int ECCX08Class::incrementCounter(int slot, byte counter[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x24, 0x01, slot)) { + return 0; + } + + delay(51); + + if (!receiveResponse(counter, 4)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + int ECCX08Class::readSlot(int slot, byte data[], int length) { if (slot < 0 || slot > 15) { diff --git a/src/ECCX08.h b/src/ECCX08.h index ae53088..abed5b0 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -54,6 +54,15 @@ class ECCX08Class #define ECDH_MODE_TEMPKEY ((uint8_t)0x08) //!< ECDH mode: write to TempKey #define ECDH_MODE_OUTPUT ((uint8_t)0x0c) //!< ECDH mode: write to buffer + int AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]); + int AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]); + #define AES_MODE_ENCRYPT ((uint8_t)0x00) //!< AES mode: encrypt + #define AES_MODE_GFM ((uint8_t)0x03) //!< AES mode: Galois Field Multiply + int AESGenIV(byte IV[]); + + int incrementCounter(int slot, byte counter[]); + int readCounter(int slot, byte counter[]); + int readSlot(int slot, byte data[], int length); int writeSlot(int slot, const byte data[], int length); From 12bb4710afc1c47e4ea00a135ad9918086ff0e40 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Mon, 6 Sep 2021 16:33:08 +0100 Subject: [PATCH 08/16] AES encrypt function --- examples/ECCX08AES/ECCX08AES.ino | 25 +++- src/ECCX08.cpp | 249 ++++++++++++++++++++++++++++++- src/ECCX08.h | 13 +- 3 files changed, 276 insertions(+), 11 deletions(-) diff --git a/examples/ECCX08AES/ECCX08AES.ino b/examples/ECCX08AES/ECCX08AES.ino index 88b003d..9c93d1c 100644 --- a/examples/ECCX08AES/ECCX08AES.ino +++ b/examples/ECCX08AES/ECCX08AES.ino @@ -4,6 +4,9 @@ This sketch uses the ECC608 to compute the AES_128_GCM encryption for some data + This sketch assumes TempKey has been set + (ECDH sketch will do this) + */ #include @@ -30,14 +33,28 @@ void setup() { printHex(devicePubKey, 64); } + byte ad[20] = {0x14}; + uint64_t adLength = (sizeof(ad)); + Serial.print("AD: "); + printHex(ad, adLength); + byte pt[40] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}; + uint64_t ptLength = (sizeof(pt)); + Serial.print("PT: "); + printHex(pt, ptLength); + byte IV[12]; - if (!ECCX08.AESGenIV(IV)){ - Serial.println("Failed to initialize IV."); + byte ct[40]; + byte tag[16]; + if (!ECCX08.AESEncrypt(IV, ad, pt, ct, tag, adLength, ptLength)){ + Serial.println("Failed to encrypt."); } else { - Serial.print("IV: "); + Serial.print("IV: "); printHex(IV, 12); + Serial.print("CT: "); + printHex(ct, ptLength); + Serial.print("tag: "); + printHex(tag, 16); } - } void loop() { diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 15a41f6..98e99ad 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -354,14 +354,243 @@ int ECCX08Class::ecdh(int slot, byte mode, const byte pubKeyXandY[], byte output return 1; } -int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]) +int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength) { - return 0; + byte H[16] = {0x00}; + if (!AESBlockEncrypt(H)){ + Serial.println("AESEncrypt: failed to compute H."); + return 0; + } + + byte J0[16] = {0x00}; + if (!AESGenIV(IV)){ + Serial.println("AESEncrypt: failed to generate IV."); + return 0; + } + memcpy(J0, IV, 12); + J0[15] = 0x01; + + byte counterBlock[16]; + memcpy(counterBlock, J0, 16); + if (!AESIncrementBlock(counterBlock)){ + Serial.println("AESEncrypt: failed to increment counter block."); + return 0; + } + + if (!AESGCTR(counterBlock, pt, ct, ptLength)){ + Serial.println("AESEncrypt: failed to encrypt."); + return 0; + } + + int adPad = (-adLength) % 16; + int ctPad = (-ptLength) % 16; + + byte S[16]; + uint64_t inputLength = adLength+adPad+ptLength+ctPad+16; + byte input[inputLength]; + memcpy(input, ad, adLength); + memset(input+adLength, 0, adPad); + memcpy(input+adLength+adPad, ct, ptLength); + memset(input+adLength+adPad+ptLength, 0, ctPad); + // Device is little endian + // GCM specification requires big endian length representation + // Hence we reverse the byte order of adLength and ptLength + for (int i=0; i<8; i++){ + input[adLength+adPad+ptLength+ctPad+i] = (adLength >> (56-8*i)) & 0xFF; + input[adLength+adPad+ptLength+ctPad+8+i] = (ptLength >> (56-8*i)) & 0xFF; + } + + if (!AESGHASH(H, input, S, inputLength)){ + Serial.println("AESEncrypt: failed to compute GHASH."); + return 0; + } + + if (!AESGCTR(J0, S, tag, 16)){ + Serial.println("AESEncrypt: failed to compute tag."); + return 0; + } + + return 1; +} + + +/** \brief GCTR function, see + * NIST Special Publication 800-38D + * 6.5 + * + * \param[in,out] counterBlock The initial counter block + * (16 bytes). + * \param[in] input The input bit string + * \param[out] output The output bit string + * \param[in] inputLength The length of the input + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESGCTR(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength) +{ + if(inputLength == 0){ + return 1; + } + int remainder = inputLength % 16; + int n = inputLength / 16 + (remainder != 0); + + int i; + for (i=0; i= 4) { + Serial.println("AESIncrementBlock: counter overflowed."); + return 0; + } + return 1; +} + +/** \brief AES encrypts a block using TempKey + * + * \param[in,out] block The block to be encrypted + * (16 bytes). + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESBlockEncrypt(byte block[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x51, 0x00, 0xFFFF, block, 16)) { + return 0; + } + + delay(51); + + if (!receiveResponse(block, 16)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + +/** \brief AES block multiplication with hash key H + * + * \param[in] H The hash subkey + * (16 bytes) + * \param[in,out] block The block to be multiplied + * (16 bytes). + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESBlockMultiplication(byte H[], byte block[]) +{ + if (!wakeup()) { + return 0; + } + byte data[32]; + memcpy(data, H, 16); + memcpy(data+16, block, 16); + if (!sendCommand(0x51, 0x03, 0xFFFF, block, 16)) { + return 0; + } + + delay(51); + + if (!receiveResponse(block, 16)) { + return 0; + } + + delay(1); + idle(); + + return 1; } /** \brief Generates AES GCM initialization vector. * - * \param[in,out] IV Initialization vector to be generated + * \param[out] IV Initialization vector to be generated * (12 bytes). See * NIST Special Publication 800-38D * 8.2.1 Deterministic Construction @@ -409,6 +638,13 @@ int ECCX08Class::AESGenIV(byte IV[]) return 1; } +/** \brief Reads the counter on the device. + * + * \param[in] slot counter slot to read from + * \param[out] counter counter value (4 bytes) + * + * \return 1 on success, otherwise 0. + */ int ECCX08Class::readCounter(int slot, byte counter[]) { if (!wakeup()) { @@ -430,6 +666,13 @@ int ECCX08Class::readCounter(int slot, byte counter[]) return 1; } +/** \brief Increments the counter on the device. + * + * \param[in] slot counter slot to increment + * \param[out] counter counter value (4 bytes) + * + * \return 1 on success, otherwise 0. + */ int ECCX08Class::incrementCounter(int slot, byte counter[]) { if (!wakeup()) { diff --git a/src/ECCX08.h b/src/ECCX08.h index abed5b0..632f1d8 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -54,12 +54,17 @@ class ECCX08Class #define ECDH_MODE_TEMPKEY ((uint8_t)0x08) //!< ECDH mode: write to TempKey #define ECDH_MODE_OUTPUT ((uint8_t)0x0c) //!< ECDH mode: write to buffer - int AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]); + int AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength); int AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]); - #define AES_MODE_ENCRYPT ((uint8_t)0x00) //!< AES mode: encrypt - #define AES_MODE_GFM ((uint8_t)0x03) //!< AES mode: Galois Field Multiply - int AESGenIV(byte IV[]); + int AESGCTR(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); + int AESGHASH(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); + + int AESIncrementBlock(byte counterBlock[]); + int AESBlockEncrypt(byte block[]); + int AESBlockMultiplication(byte H[], byte block[]); + + int AESGenIV(byte IV[]); int incrementCounter(int slot, byte counter[]); int readCounter(int slot, byte counter[]); From 6cf0cdd48b7df9ba2a116d86655e33e4ea982b93 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Tue, 7 Sep 2021 18:51:30 +0100 Subject: [PATCH 09/16] AES decrypt function --- examples/ECCX08AES/ECCX08AES.ino | 8 ++++ src/ECCX08.cpp | 65 ++++++++++++++++++++++++++++++++ src/ECCX08.h | 2 +- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/examples/ECCX08AES/ECCX08AES.ino b/examples/ECCX08AES/ECCX08AES.ino index 9c93d1c..dc719bd 100644 --- a/examples/ECCX08AES/ECCX08AES.ino +++ b/examples/ECCX08AES/ECCX08AES.ino @@ -55,6 +55,14 @@ void setup() { Serial.print("tag: "); printHex(tag, 16); } + + byte ptDecrypted[40]; + if (!ECCX08.AESDecrypt(IV, ad, ptDecrypted, ct, tag, adLength, ptLength)){ + Serial.println("Failed to decrypt."); + } else { + Serial.print("PT: "); + printHex(ptDecrypted, ptLength); + } } void loop() { diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 98e99ad..29aef8f 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -413,6 +413,71 @@ int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag return 1; } +int ECCX08Class::AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ctLength) +{ + uint64_t maxLength = 1ull << 36; + if (adLength >= maxLength || ctLength >= maxLength){ + return 0; + } + + byte H[16] = {0x00}; + if (!AESBlockEncrypt(H)){ + return 0; + } + + byte J0[16] = {0x00}; + memcpy(J0, IV, 12); + J0[15] = 0x01; + + int adPad = (-adLength) % 16; + int ctPad = (-ctLength) % 16; + + byte S[16]; + uint64_t inputLength = adLength+adPad+ctLength+ctPad+16; + byte input[inputLength]; + memcpy(input, ad, adLength); + memset(input+adLength, 0, adPad); + memcpy(input+adLength+adPad, ct, ctLength); + memset(input+adLength+adPad+ctLength, 0, ctPad); + // Device is little endian + // GCM specification requires big endian length representation + // Hence we reverse the byte order of adLength and ctLength + for (int i=0; i<8; i++){ + input[adLength+adPad+ctLength+ctPad+i] = (adLength >> (56-8*i)) & 0xFF; + input[adLength+adPad+ctLength+ctPad+8+i] = (ctLength >> (56-8*i)) & 0xFF; + } + + if (!AESGHASH(H, input, S, inputLength)){ + return 0; + } + + byte tagComputed[16]; + if (!AESGCTR(J0, S, tagComputed, 16)){ + return 0; + } + + uint8_t equalBytes=0; + for (int i=0; i<16; i++){ + equalBytes += (tag[i]==tagComputed[i]); + } + if (equalBytes!=16){ + // tag mismatch + return 0; + } + + byte counterBlock[16]; + memcpy(counterBlock, J0, 16); + if (!AESIncrementBlock(counterBlock)){ + return 0; + } + + if (!AESGCTR(counterBlock, ct, pt, ctLength)){ + return 0; + } + + return 1; +} + /** \brief GCTR function, see * NIST Special Publication 800-38D diff --git a/src/ECCX08.h b/src/ECCX08.h index 632f1d8..3257215 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -55,7 +55,7 @@ class ECCX08Class #define ECDH_MODE_OUTPUT ((uint8_t)0x0c) //!< ECDH mode: write to buffer int AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength); - int AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[]); + int AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ctLength); int AESGCTR(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); int AESGHASH(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); From 14fdd3a7f1c44ef30a6a30ecbc2e9f3b957c0abe Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Wed, 8 Sep 2021 14:43:53 +0100 Subject: [PATCH 10/16] actually send the data to the chip --- src/ECCX08.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 29aef8f..71c949e 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -637,7 +637,7 @@ int ECCX08Class::AESBlockMultiplication(byte H[], byte block[]) byte data[32]; memcpy(data, H, 16); memcpy(data+16, block, 16); - if (!sendCommand(0x51, 0x03, 0xFFFF, block, 16)) { + if (!sendCommand(0x51, 0x03, 0xFFFF, data, 32)) { return 0; } From c145ed183350956ab579aea9c55a3591f4ebb04d Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Wed, 8 Sep 2021 14:44:47 +0100 Subject: [PATCH 11/16] encode bit length instead of byte length --- src/ECCX08.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 71c949e..62778db 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -392,12 +392,14 @@ int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag memset(input+adLength, 0, adPad); memcpy(input+adLength+adPad, ct, ptLength); memset(input+adLength+adPad+ptLength, 0, ctPad); - // Device is little endian - // GCM specification requires big endian length representation - // Hence we reverse the byte order of adLength and ptLength + // Device is little endian. + // GCM specification requires big endian representation + // of bit length. + // Hence we multiply by 8 and + // reverse the byte order of adLength and ptLength. for (int i=0; i<8; i++){ - input[adLength+adPad+ptLength+ctPad+i] = (adLength >> (56-8*i)) & 0xFF; - input[adLength+adPad+ptLength+ctPad+8+i] = (ptLength >> (56-8*i)) & 0xFF; + input[adLength+adPad+ptLength+ctPad+i] = (adLength*8 >> (56-8*i)) & 0xFF; + input[adLength+adPad+ptLength+ctPad+8+i] = (ptLength*8 >> (56-8*i)) & 0xFF; } if (!AESGHASH(H, input, S, inputLength)){ @@ -439,12 +441,14 @@ int ECCX08Class::AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag memset(input+adLength, 0, adPad); memcpy(input+adLength+adPad, ct, ctLength); memset(input+adLength+adPad+ctLength, 0, ctPad); - // Device is little endian - // GCM specification requires big endian length representation - // Hence we reverse the byte order of adLength and ctLength + // Device is little endian. + // GCM specification requires big endian representation + // of bit length. + // Hence we multiply by 8 and + // reverse the byte order of adLength and ptLength. for (int i=0; i<8; i++){ - input[adLength+adPad+ctLength+ctPad+i] = (adLength >> (56-8*i)) & 0xFF; - input[adLength+adPad+ctLength+ctPad+8+i] = (ctLength >> (56-8*i)) & 0xFF; + input[adLength+adPad+ctLength+ctPad+i] = (adLength*8 >> (56-8*i)) & 0xFF; + input[adLength+adPad+ctLength+ctPad+8+i] = (ctLength*8 >> (56-8*i)) & 0xFF; } if (!AESGHASH(H, input, S, inputLength)){ From d689f0f69ed5a2a47e9c4af60bec8fe1fdb68b69 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Mon, 13 Sep 2021 11:01:08 +0100 Subject: [PATCH 12/16] encrypt/decrypt function descriptions --- src/ECCX08.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 62778db..5427582 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -354,6 +354,22 @@ int ECCX08Class::ecdh(int slot, byte mode, const byte pubKeyXandY[], byte output return 1; } +/** \brief AES_GCM encryption function, see + * NIST Special Publication 800-38D + * 7.1, using TempKey. + * + * \param[out] IV Initialization vector + * (12 bytes) + * \param[in] ad Associated data + * \param[in] pt Plaintext + * \param[out] ct Ciphertext + * \param[out] tag Authentication tag + * (16 bytes) + * \param[in] adLength The length of ad + * \param[in] ptLength The length of pt + * + * \return 1 on success, otherwise 0. + */ int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength) { byte H[16] = {0x00}; @@ -415,6 +431,22 @@ int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag return 1; } +/** \brief AES_GCM decryption function, see + * NIST Special Publication 800-38D + * 7.2, using TempKey. + * + * \param[in] IV Initialization vector + * (12 bytes) + * \param[in] ad Associated data + * \param[out] pt Plaintext + * \param[in] ct Ciphertext + * \param[in] tag Authentication tag + * (16 bytes) + * \param[in] adLength The length of ad + * \param[in] ctLength The length of ct + * + * \return 1 on success, otherwise 0. + */ int ECCX08Class::AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ctLength) { uint64_t maxLength = 1ull << 36; From be5c174eb08a0b1f0004100ea2bdede1162fef50 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Mon, 13 Sep 2021 13:02:53 +0100 Subject: [PATCH 13/16] decrease delay --- src/ECCX08.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 5427582..4de6806 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -644,7 +644,7 @@ int ECCX08Class::AESBlockEncrypt(byte block[]) return 0; } - delay(51); + delay(9); if (!receiveResponse(block, 16)) { return 0; @@ -677,7 +677,7 @@ int ECCX08Class::AESBlockMultiplication(byte H[], byte block[]) return 0; } - delay(51); + delay(9); if (!receiveResponse(block, 16)) { return 0; @@ -755,7 +755,7 @@ int ECCX08Class::readCounter(int slot, byte counter[]) return 0; } - delay(51); + delay(9); if (!receiveResponse(counter, 4)) { return 0; @@ -783,7 +783,7 @@ int ECCX08Class::incrementCounter(int slot, byte counter[]) return 0; } - delay(51); + delay(9); if (!receiveResponse(counter, 4)) { return 0; From 75ae80057237b3865f3af54f3e5a73aa8cf08f56 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Thu, 16 Sep 2021 10:32:45 +0100 Subject: [PATCH 14/16] counter comment --- src/ECCX08.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 4de6806..a29af7d 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -718,10 +718,11 @@ int ECCX08Class::AESGenIV(byte IV[]) // Device only has two 4 byte counters // instead of 8 byte counter. + // We increment one counter and read the other + // This should be enough for the lifetime of the device byte counter0[4]; if (!incrementCounter(0, counter0)){ Serial.println("AESGenIV: failed to increment counter"); - // TODO: Reset counter0 and increment counter1 return 0; } byte counter1[4]; From 5dc6c6f870702f144252ff01e53c20a4a1c326d8 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Thu, 16 Sep 2021 13:24:55 +0100 Subject: [PATCH 15/16] comment on example assumptions --- examples/ECCX08AES/ECCX08AES.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/ECCX08AES/ECCX08AES.ino b/examples/ECCX08AES/ECCX08AES.ino index dc719bd..024e54c 100644 --- a/examples/ECCX08AES/ECCX08AES.ino +++ b/examples/ECCX08AES/ECCX08AES.ino @@ -6,6 +6,8 @@ This sketch assumes TempKey has been set (ECDH sketch will do this) + and derives a deviceID from the public key + corresponding to the key in slot 0. */ From f885c399ab0ce0503f210c116f2b6ee55213e3c4 Mon Sep 17 00:00:00 2001 From: JPijnenburg Date: Thu, 16 Sep 2021 13:28:18 +0100 Subject: [PATCH 16/16] fix typo in b64.h --- src/utility/b64.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utility/b64.h b/src/utility/b64.h index ad717ba..6ad7b59 100644 --- a/src/utility/b64.h +++ b/src/utility/b64.h @@ -5,7 +5,7 @@ MIT License (MIT) Copyright (c) 2016 Densaugeo - Any other base64 decode function would do if there is an Arduino prefered one somewhere? + Any other base64 decode function would do if there is an Arduino preferred one somewhere? */ unsigned char base64_to_binary(unsigned char c) { // Capital letters - 'A' is ascii 65 and base64 0