Tutorials:
Sensors, interfaces and bus systems (SENIN, BUSSY)

Encryption

last updated: 07/01/19

Introduction

Song of this chapter: The Beatles > Please Please Me > Do You Want To Know A Secret

Because of the EU Energy Efficiency Directive from 2012 the gas and electricity Distribution System Operators (DSO) in Luxembourg replaced there gas and energy meters with smartmeters (named smarty :(). Besides gas and electricity metering, the system is open for other metering data like water and district heat (M-Bus).
The French group Sagemcom delivered the smartmeters. All meters have to be read by one national central system, operated by a common operator. This is an economic group of interest (G.I.E.) of the 7 Luxembourgian gas and electricity DSO‘s named Luxmetering G.I.E.
Luxmetering is getting the data from 4 registers for active, reactive, import and export energy (1/4h) and the 3 registers for gas, water & heat (1 h) over Power Line Communication (PLC). The smartmeters have also alarms and logs for quality of electrical energy supply (voltage, outages,...) and fraud detection, and calendar functions for the 2 external relays (home applications).

smarty

The customer wants to get his data and this is possible by reading the blinking LED of the smartmeter. Another possibility is the 10 second data from the smartmeter P1 port (RJ12 connector under the green lid). The P1 data output communication protocol and format is specified in the Dutch Smart Meter Requirements v5.0.2 . The solution deployed in Luxembourg includes an additional security layer standard that is conform to the IDIS package 2.0 requirement.
The encryption layer is based on DLMS security suite 0 algorithm: AES128-GCM. More information can be found in this document.

AES128-Galois Counter Mode

We want to learn how to encrypt and decrypt and our data in a safe way and Galois Counter Mode (GCM) in combination with the Advanced Encryption Standard (AES) block cipher used by the smartmeters is a good starting point.

There are 3 properties we want for securing our data:

AES with Galois/Counter Mode (GCM) block mode provides all those properties. GCM is a mode of operation for symmetric-key cryptographic block ciphers that has been widely adopted because of its efficiency and performance. GCM is defined for block ciphers with a block size of 128 bit.

GCM can be realised with our Arduinos or ESPs because it does not need powerful hardware. The throughput rates allow high-speed communication. GCM provides both data authenticity (integrity) and confidentiality.

Authenticated encryption

In GCM data blocks are numbered sequentially (counter), and then this block number is combined with an Initialization Vector (IV) and encrypted with a block cipher E, usually AES-128.

For authenticated encryption we need four inputs:

GCM encryption

After the encryption we get two outputs:

Authenticated decryption

To be able to decrypt the message, we need the Initialisation Vector IV, the Ciphertext C , the authentication Tag T, the Additional Authenticated Data (AAD) A and the Key K. So we get five inputs, but only one output, the Plaintext P (or an error message if the decryption wasn't successful).

GCM decryption

In the following picture we see the output of a smartmeter P1 port. The port uses inverted TIA232, and the serial stream is encrypted with AES128-GCM:

GCM1

"Just do it" Encryption 1:
    0xDB, 0x08, 0x53, 0x41, 0x47, 0x67, 0x70, 0x01, 0x5B, 0xEB, 0x82, 0x02,
    0x7A, 0x30, 0x00, 0x04, 0xE9, 0x14, 0x06, 0xF4, 0x36, 0x4C, 0x43, 0xBB,
    ...
    0x60, 0xDD, 0xC9, 0xD2, 0x67, 0x0E, 0xD0, 0xCB, 0x31, 0x15, 0x37, 0x8C,
    0x4E, 0xCD, 0x9A, 0xF6, 0x8D, 0x05, 0x2C, 0xD8, 0x97, 0x94, 0x26, 0x39
    /* decrypt_AES128_GCM_smarty.ino
       You need the crypto library. Install it in Arduino (Manage libraries) */

    //#define ESP

    #include <Crypto.h>
    #include <AES.h>
    #include <GCM.h>

    #ifndef ESP
      #include <avr/pgmspace.h>
    #endif // ifndefESP

    #define MAX_PLAINTEXT_LEN 620

    struct TestVector {
        const char *name;
        uint8_t key[32];
        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;
    };

    static TestVector const testVectorGCM PROGMEM = {
        .name        = "AES-128 GCM",
        .key         = {0xAE, 0xBD, 0x21, 0xB7, 0x69, 0xA6, 0xD1, 0x3C, 0x0D, 0xF0, 0x64, 0xE3,
                        0x83, 0x68, 0x2E, 0xFF},
        .ciphertext  = {0x06, 0xF4, 0x36, 0x4C, 0x43, 0xBB, 0x04, 0xA1, 0x3E, 0xE9, 0xBC, 0x0E,
                        0xBE, 0x1F, 0x92, 0xA0, 0x1B, 0x5E, 0x02, 0x12, 0x93, 0x1F, 0x32, 0x63,
                        0xB5, 0xE6, 0x37, 0x15, 0x2D, 0x77, 0x59, 0x86, 0x53, 0x18, 0x60, 0x5B,
                        0xAD, 0xF8, 0xB4, 0xE1, 0x2F, 0x74, 0xF0, 0xE8, 0xD9, 0x4C, 0xC3, 0xD3,
                        0xED, 0x77, 0x9A, 0x69, 0x15, 0x13, 0x65, 0xA8, 0x72, 0x5B, 0x49, 0xD5,
                        0x1D, 0x77, 0x33, 0x2C, 0xE2, 0x83, 0xF7, 0x67, 0xA9, 0x7C, 0x93, 0xA6,
                        0x74, 0xEF, 0xEF, 0x40, 0x2D, 0x21, 0x30, 0x0E, 0xDC, 0x8A, 0xBC, 0x1F,
                        0x52, 0x16, 0xD2, 0xBD, 0x4E, 0xC9, 0x18, 0xA8, 0x62, 0xE7, 0xAC, 0xFD,
                        0x1F, 0x07, 0x38, 0xCD, 0x3F, 0xDE, 0xF7, 0xC8, 0xBF, 0xA3, 0x51, 0xD6,
                        0x2C, 0xE0, 0xBF, 0x5A, 0x0C, 0x68, 0xC6, 0x81, 0xA1, 0xF9, 0x00, 0x43,
                        0xE3, 0x1C, 0x66, 0x44, 0x9B, 0x20, 0x9D, 0x19, 0x3C, 0x48, 0xB0, 0xA4,
                        0x8D, 0x8C, 0x2A, 0x1F, 0x3B, 0xB0, 0xEF, 0xE2, 0x5B, 0xA6, 0x5E, 0x7C,
                        0x35, 0x66, 0x5E, 0x26, 0x53, 0x51, 0x0E, 0x47, 0xB0, 0x28, 0x73, 0x79,
                        0x04, 0xB7, 0x75, 0xC0, 0x80, 0xEA, 0xB8, 0x29, 0x32, 0xC3, 0x2B, 0x9B,
                        0xCB, 0x09, 0x62, 0xFE, 0x16, 0x86, 0x26, 0x24, 0xAA, 0x1F, 0x2E, 0xC8,
                        0xEB, 0x8F, 0x3D, 0xB1, 0x65, 0x82, 0x6C, 0x14, 0x09, 0x05, 0xB5, 0xE6,
                        0x23, 0x52, 0x73, 0x24, 0xF2, 0xE2, 0x5A, 0x35, 0x7B, 0x99, 0xE7, 0x32,
                        0x05, 0xD8, 0xA8, 0xD2, 0x99, 0xC9, 0x7D, 0x2D, 0x8F, 0x34, 0xDA, 0x52,
                        0x41, 0x27, 0x35, 0x4B, 0x6B, 0x13, 0xBF, 0x70, 0x86, 0x79, 0xEA, 0x26,
                        0x38, 0x3B, 0x56, 0x21, 0xED, 0xB6, 0x89, 0xC4, 0x01, 0x8C, 0x9E, 0x72,
                        0x4A, 0xC2, 0xA4, 0xE3, 0xD0, 0xF1, 0x37, 0x0C, 0x3A, 0xC4, 0x6E, 0x17,
                        0x32, 0x57, 0x7D, 0x32, 0xFD, 0x5D, 0x6B, 0x18, 0x9A, 0xC1, 0x5E, 0x74,
                        0x15, 0x37, 0x6C, 0xE8, 0x87, 0xCD, 0x52, 0xC8, 0x80, 0xA1, 0x42, 0xC0,
                        0x23, 0x0C, 0x21, 0xFA, 0xCE, 0x9F, 0x95, 0xAB, 0x18, 0xFB, 0xBB, 0x92,
                        0xC9, 0xE1, 0xF4, 0xD2, 0xE5, 0xC2, 0x22, 0x03, 0x72, 0xF2, 0x40, 0xFF,
                        0x3B, 0x39, 0xB6, 0x75, 0xE1, 0x5A, 0x2B, 0xBA, 0x4D, 0x6F, 0x4A, 0x7A,
                        0xEE, 0x55, 0x1C, 0xC4, 0x5B, 0x11, 0x22, 0x0F, 0x3A, 0x0E, 0xF8, 0x20,
                        0x72, 0x08, 0x92, 0x59, 0x56, 0xC1, 0x7E, 0xF3, 0xF4, 0x18, 0x1F, 0xFD,
                        0xAC, 0x8C, 0x47, 0x82, 0x56, 0xF2, 0x72, 0x23, 0x92, 0x3D, 0xAC, 0xA7,
                        0x06, 0x58, 0x52, 0x63, 0xF4, 0x41, 0xBC, 0x15, 0x1B, 0xC8, 0x1D, 0xAE,
                        0x0E, 0xDE, 0x9B, 0x35, 0x78, 0x19, 0x17, 0x3D, 0x95, 0x72, 0x09, 0x85,
                        0x44, 0x90, 0x9C, 0x3A, 0xEE, 0xF2, 0x11, 0x29, 0x85, 0x40, 0xB6, 0x47,
                        0x2F, 0x92, 0x4D, 0x35, 0x29, 0x05, 0x58, 0x4A, 0x68, 0x7E, 0xBA, 0x2D,
                        0x71, 0xEC, 0x3C, 0x9B, 0x7A, 0x87, 0x37, 0x1E, 0x0C, 0xF0, 0x78, 0xE3,
                        0xC9, 0x18, 0xD3, 0x47, 0xEA, 0xF3, 0x7E, 0x9F, 0x76, 0x0B, 0x5F, 0x32,
                        0x88, 0xF3, 0x8F, 0xE3, 0x64, 0x92, 0xBB, 0x13, 0x2C, 0x86, 0x82, 0x2D,
                        0x32, 0x8E, 0xF6, 0x20, 0x30, 0xA5, 0xBE, 0x23, 0x9A, 0x25, 0x3D, 0x48,
                        0x82, 0xBE, 0xA2, 0xC1, 0xEA, 0x53, 0xB9, 0xAF, 0x64, 0x58, 0x3E, 0x9B,
                        0x2A, 0x47, 0xC2, 0x82, 0x7B, 0x95, 0x87, 0xE9, 0xE2, 0x4F, 0x03, 0xC5,
                        0xAC, 0xAD, 0x09, 0x21, 0x1F, 0x3C, 0x30, 0x70, 0x4E, 0xFD, 0xE8, 0x03,
                        0xEF, 0xCD, 0x86, 0x82, 0x1C, 0xBE, 0x7D, 0xEE, 0x92, 0x95, 0x73, 0x76,
                        0x65, 0xA4, 0xA9, 0xBF, 0x48, 0xD0, 0x10, 0x0D, 0xD2, 0xC6, 0x84, 0x88,
                        0x18, 0x28, 0xFA, 0xD3, 0x76, 0x6F, 0x96, 0xA6, 0xCE, 0x0E, 0xF2, 0xE3,
                        0xF9, 0xF3, 0xEA, 0x4A, 0x20, 0x7C, 0x54, 0x45, 0xB2, 0xCE, 0x5C, 0x47,
                        0xF5, 0xDB, 0x98, 0x3B, 0xE5, 0x46, 0x11, 0xCF, 0xD9, 0x94, 0xC5, 0xEC,
                        0x48, 0x3B, 0x16, 0x7C, 0x2E, 0x8F, 0xC3, 0x42, 0xD5, 0xA9, 0x06, 0x8F,
                        0x76, 0x67, 0x61, 0x9C, 0x1F, 0xA4, 0xF2, 0x03, 0x38, 0x6E, 0xED, 0x54,
                        0x14, 0x9F, 0x5B, 0x11, 0x09, 0x8C, 0x9C, 0xCC, 0xB0, 0xF2, 0xEC, 0x66,
                        0xCB, 0x5D, 0x15, 0x44, 0xA0, 0x1E, 0xCB, 0x01, 0x74, 0x06, 0xE7, 0x67,
                        0x81, 0x85, 0x17, 0x3E, 0xD2, 0x80, 0x9E, 0xD6, 0xBB, 0x10, 0x92, 0xBB,
                        0xC3, 0xCB, 0xA4, 0x0B, 0xEA, 0x60, 0xDD, 0xC9, 0xD2, 0x67, 0x0E, 0xD0,
                        0xCB, 0x31, 0x15, 0x37, 0x8C},
        .authdata    = {0x30, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA,
                        0xBB, 0xCC, 0xDD, 0xEE, 0xFF},
        .iv          = {0x53, 0x41, 0x47, 0x67, 0x70, 0x01, 0x5B, 0xEB, 0x00, 0x04, 0xE9, 0x14},
        .tag         = {0x4E, 0xCD, 0x9A, 0xF6, 0x8D, 0x05, 0x2C, 0xD8, 0x97, 0x94, 0x26, 0x39},
        .authsize    = 17,
        .datasize    = 617,
        .tagsize     = 12,
        .ivsize      = 12
    };

    TestVector testVector;
    GCM<AES128> *gcmaes128 = 0;
    #ifdef ESP
      byte buffer[MAX_PLAINTEXT_LEN];
    #else
      byte buffer[64];
    #endif // ifdef ESP

    void setup() {
        Serial.begin(115200);
        Serial.println();
        Serial.println("State Sizes:");
        Serial.print("GCM<AES128> ... ");
        Serial.println(sizeof(*gcmaes128));
        Serial.println();
        Serial.println("Test Vectors:");
        gcmaes128 = new GCM<AES128>();
        memcpy_P(&testVector, &testVectorGCM, sizeof(TestVector));
        Serial.println(testVector.tagsize);
        gcmaes128->setKey(testVector.key, gcmaes128->keySize());
        gcmaes128->setIV(testVector.iv, testVector.ivsize);
        gcmaes128->decrypt(buffer, testVector.ciphertext, testVector.datasize);
        for (int i=0; i<testVector.datasize; i++) {
          Serial.write(buffer[i]);
        }
        delete gcmaes128;
    }

    void loop() {
}
"Just do it" Encryption 2:
"Just do it" Encryption 3:
    /* encrypt_decrypt_GCM128.ino
       You need the crypto library. Install it in Arduino (Manage libraries) */

    #include <Crypto.h>
    #include <AES.h>
    #include <GCM.h>

    #define MAX_PLAINTEXT_LEN 200

    // Text to encrypt:
    const char mytext[] = "HAI";
    const char myvname[] = "AES-128 GCM";      // vector name
    char mykey[] = "5A0BDB8CE3FDB7121635FF6FB02EE1DF";    // 16 byte
    char myAAD[] = "3000112233445566778899AABBCCDDEEFF"; // 17 byte
    char myIV[] = "53414767700067C800000782";            // both in HEX
    const byte tagsize = 12;    // 12 byte

    struct Vector_GCM {
        const char *name;
        uint8_t key[16];
        uint8_t plaintext[MAX_PLAINTEXT_LEN];
        uint8_t ciphertext[MAX_PLAINTEXT_LEN];
        uint8_t authdata[17];
        uint8_t iv[12];
        uint8_t tag[16];
        size_t authsize;
        size_t datasize;
        size_t tagsize;
        size_t ivsize; };

    Vector_GCM vector;
    GCM<AES128> *gcmaes128 = 0;
    const byte line_length_raw_serial = 50;
    byte serial_data_length = 100;
    byte buffer[300];

    void setup() {
        Serial.begin(115200);
        delay(500);
        Serial.println("Serial ok");
        if (init_vector_GCM(myvname, mykey, mytext, myAAD, myIV, tagsize) != 0) {
          Serial.print("error while initialising vector: ");
        }
        // encrypt
        gcmaes128 = new GCM<AES128>();
        gcmaes128->setKey(vector.key, gcmaes128->keySize());
        gcmaes128->setIV(vector.iv, vector.ivsize);
        gcmaes128->encrypt(buffer, vector.plaintext, vector.datasize);
        Serial.print("Encrypted text: ");
        for (int i=0; i<vector.datasize; i++) {
          vector.ciphertext[i] = buffer[i];
          Serial.print(buffer[i],HEX);
        }
        Serial.println();
        gcmaes128->computeTag(vector.tag, vector.tagsize);
        Serial.print("The GCM-tag is: ");
        for (int i=0; i<vector.tagsize; i++) {
          vector.tag[i] = vector.tag[i];
          Serial.print(vector.tag[i],HEX);
        }
        Serial.println();
        delete gcmaes128;
        //clear the plaintext
        for (int i=0; i<vector.datasize; i++) {
          vector.plaintext[i] = 0xBB;
        }
        // decrypt
        gcmaes128 = new GCM<AES128>();
        Serial.println(vector.tagsize);
        gcmaes128->setKey(vector.key, gcmaes128->keySize());
        gcmaes128->setIV(vector.iv, vector.ivsize);
        gcmaes128->decrypt(buffer, vector.ciphertext, vector.datasize);
        for (int i=0; i<vector.datasize; i++) {
          Serial.write(buffer[i]);
        }
        delete gcmaes128;
    }

    void loop() {
    }

    int init_vector_GCM(const char *myvname, char *key, const char *plaintext,
                        char *aad, char *iv, const byte tagsize) {
      byte tmp_array[40];          // needed because 0 terminates string
      if (strlen(key) != 32) return -1;
      if (strlen(aad) != 34) return -1;
      if (strlen(iv) != 24) return -1;
      vector.name = myvname;                     // init vector name
      for (byte i=0; i<strlen(key); i++) {              // init key (convert from ASCII)
        if ((key[i]>='A') && (key[i]<='F')) tmp_array[i] = byte(key[i]-55);
        else if ((key[i]>='a') && (key[i]<='f')) tmp_array[i] = byte(key[i]-87);
        else if ((key[i]>='0') && (key[i]<='9')) tmp_array[i] = byte(key[i]-48);
        else return -1;
        if (i%2==1) {                                    // if i is odd (all second character)
          vector.key[(i-1)/2] = byte((tmp_array[i-1]*16)+tmp_array[i]);
        }
      }
      vector.datasize = strlen(plaintext);       // init plaintext
      for (int i=0; i<strlen(plaintext); i++) {
        vector.plaintext[i] = mytext[i];
      }
      vector.authsize = strlen(aad)/2;           // init AAD (convert from ASCII)
      for (byte i=0; i<strlen(aad); i++) {
        if ((aad[i]>='A') && (aad[i]<='F')) tmp_array[i] = byte(aad[i]-55);
        else if ((aad[i]>='a') && (aad[i]<='f')) tmp_array[i] = byte(aad[i]-87);
        else if ((aad[i]>='0') && (aad[i]<='9')) tmp_array[i] = byte(aad[i]-48);
        else return -1;
        if (i%2==1) {                                    // if i is odd (all second character)
          vector.authdata[(i-1)/2] = byte((tmp_array[i-1]*16)+tmp_array[i]);
        }
      }
      vector.ivsize = strlen(iv)/2;           // init IV (convert from ASCII)
      for (byte i=0; i<strlen(iv); i++) {
        if ((iv[i]>='A') && (iv[i]<='F')) tmp_array[i] = byte(iv[i]-55);
        else if ((iv[i]>='a') && (iv[i]<='f')) tmp_array[i] = byte(iv[i]-87);
        else if ((iv[i]>='0') && (iv[i]<='9')) tmp_array[i] = byte(iv[i]-48);
        else return -1;
        if (i%2==1) {                                    // if i is odd (all second character)
          vector.iv[(i-1)/2] = byte((tmp_array[i-1]*16)+tmp_array[i]);
        }
      }
      vector.tagsize = tagsize;           // init tagsize
      return 0;
    }