aldadic / esp-smartmeter-reader

Arduino sketch to read and decrypt the data from my Smart Meter (Landis+Gyr E450 / Wiener Netze) with an ESP32 or ESP8266. Sends energy data via MQTT (e.g. to Home Assistant).
GNU General Public License v3.0
24 stars 4 forks source link

ESP 8266; Mbedtls Libary #1

Closed doli08 closed 1 year ago

doli08 commented 1 year ago

first of all thank you for effort pushing this project to GH (!)

I am struggling loading the project to a Lolin Wemos D1 Mini (ESP 8266). I cannot activate the Mbedtls libary in the arduino IDE.

what I have tried (but not solved):

error msg:

C:\~~\SmartMeterSchnittstelle\esp32-smartmeter-reader-main\esp32-smartmeter-reader-main\esp32-smartmeter-reader\esp32-smartmeter-reader.ino:2:10: fatal error: mbedtls/aes.h: No such file or directory 2 | #include "mbedtls/aes.h" //=> not found :( | ^~~~~~~ compilation terminated.

exit status 1

Compilation error: mbedtls/aes.h: No such file or directory

aldadic commented 1 year ago

Hi @doli08!

I have never questioned how I got the mbedtls library in the Arduino IDE. It was simply available when I needed it, which is why I thought that this might be always the case. But it seems I was wrong on this assumption 😅. After a bit of googling I found out that the library was probably installed as a part of the Arduino core for the ESP32 (see here). Therefore I would suggest that you install the ESP32 board in your Arduino IDE by adding

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

as "Additional Board Manager URL".

leecher1337 commented 1 year ago

Otherwise, crypto library should also work, I guess..

#include <AES.h>
#include <CTR.h>

    // Initialisation Vector zusammenbaun:
    uint8_t iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
    // 8 Bytes von 13 (=system-title)
    memcpy(&iv[0], fullData->sys_t, 8);
    // 4 Bytes von 23 (=nonce). INFO: Nonce wird bei jedem Paket um 1 größer (=increment counter)
    memcpy(&iv[8], &fullData->inv_cnt, 4);

    // Entschlüsseln
    uint8_t plainText[sizeof(fullData->data)];
    CTR<AES128> ctr;
    ctr.setKey(Key, sizeof(Key));
    ctr.setIV(iv, sizeof(iv));
    ctr.decrypt(plainText, (unsigned char*)&fullData->data, sizeof(fullData->data));

image

aldadic commented 1 year ago

Thanks a lot for sharing @leecher1337! I was unfamiliar with that library. As soon as i have time i will try your code with an ESP8266 and probably update the repository accordingly.

curtosvienna commented 1 year ago

first of all thank you for effort pushing this project to GH (!)

I am struggling loading the project to a Lolin Wemos D1 Mini (ESP 8266). I cannot activate the Mbedtls libary in the arduino IDE.

what I have tried (but not solved):

  • find the libary in the libary manager (not found the correct one, just "Seeed_Arduino_mbedtls"
  • install the mbedtls lib from github via zip file -> faulted
  • copy the unzipped folders to: install folder or to the project folder

error msg:

C:\SmartMeterSchnittstelle\esp32-smartmeter-reader-main\esp32-smartmeter-reader-main\esp32-smartmeter-reader\esp32-smartmeter-reader.ino:2:10: fatal error: mbedtls/aes.h: No such file or directory 2 | #include "mbedtls/aes.h" //=> not found :( | ^~~~~ compilation terminated.

exit status 1

Compilation error: mbedtls/aes.h: No such file or directory

Hallo @doli08 !

habe hier selbes Problem. Hast du eine Lösung gefunden.? stehe hier komplett an, komme nicht weiter

leecher1337 commented 1 year ago

Hallo, ich hab meine eigene Variante von dem Code geschrieben. Soll ich sie auf Github stellen? Ich konnte sie nur noch nicht verifizieren, weil der Netzbetreiber mir wahrscheinlich den falschen Schlüssel geschickt hat o.Ä., jedenfalls kann ich die Pakete von meinem Smartmeter nicht entschlüsseln, aber in meinem Code selbst hab ich keinen Fehler gefunden. Eigentlich wollte ich den erst online stellen, wenn ich ihn verifiziert habe, ob er auch funktioniert, aber das kann bei dem Netzbetreiber noch Monate dauern, bis der mal irgendwas macht, wenn er überhaupt was macht :(

curtosvienna commented 1 year ago

Hallo @leecher1337,

ich hätte nichts dagegen. ;-) Dann könnte ich deine Version mit meinem Schlüssel testen ob es funktioniert und hier Rückmeldung geben.

aldadic commented 1 year ago

Hi @curtosvienna!

Ich habe meinen Code mit der library, die @leecher1337 vorgeschlagen hat, umgeschrieben. Hier ist der Code:

https://gist.github.com/aldadic/31abea0001c0a231c59814a3e0c68146

Auf meinem ESP32 klappt es tadellos.

aldadic commented 1 year ago

Update: Der Code läuft nun erfolgreich auf meinem ESP8266. Im obigen Code musste ich dafür noch die Wifi library ändern (#include <ESP8266WiFi.h> um genau zu sein; ich habe das GitHub gist entsprechend angepasst). Den Lesekopf habe ich wie folgt angeschlossen:

IR reader ESP8266
VCC 3.3V
GND GND
RX GPIO3 (RX)
TX GPIO1 (TX)

Im config.h musste ich daher die letzte Zeile zu

HardwareSerial *smart_meter = &Serial;

ändern. Ich würde mich freuen zu hören, ob das auch bei dir klappt @curtosvienna. Dann könnte ich das nämlich im repository entsprechend ergänzen.

curtosvienna commented 1 year ago

Hi, das kompilieren hat schon mal fehlerlos funktioniert und das hochladen des Codes auf den ESP ebenfalls. muss noch den Kopf und den ESP verbinden und dann gehts an den Zähler.Wahrscheinlich erst morgen oder heute zu später Stunde. Mein Zähler ist in einem Zählerbock an der Grundstücksgrenze. WLAN-Verbindung sollte sich ausgehen. Danke jedenfalls vielmals !!!! Halte euch am laufenden halte euch am laufenden.

Am Fr., 10. Feb. 2023 um 14:09 Uhr schrieb Aleksandar Dadic < @.***>:

Update: Der Code läuft nun erfolgreich auf meinem ESP8266. Im obigen Code musste ich dafür noch die Wifi library ändern (#include um genau zu sein; ich habe das GitHub gist https://gist.github.com/aldadic/31abea0001c0a231c59814a3e0c68146 entsprechend angepasst). Den Lesekopf habe ich wie folgt angeschlossen: IR reader ESP8266 VCC 3.3V GND GND RX GPIO3 (RX) TX GPIO1 (TX)

Im config.h musste ich daher die letzte Zeile zu

HardwareSerial *smart_meter = &Serial;

ändern. Ich würde mich freuen zu hören, ob das auch bei dir klappt @curtosvienna https://github.com/curtosvienna. Dann könnte ich nämlich das repository entsprechend ergänzen.

— Reply to this email directly, view it on GitHub https://github.com/aldadic/esp32-smartmeter-reader/issues/1#issuecomment-1425787329, or unsubscribe https://github.com/notifications/unsubscribe-auth/A4G6W5M4FWBACA2OXQC3VNDWWY4XTANCNFSM6AAAAAARXT73FU . You are receiving this because you were mentioned.Message ID: @.***>

curtosvienna commented 1 year ago

Hallo @leecher1337 @aldadic @,

alles zusammengebaut, konfiguriert, ...... Funktioniert einwandfrei, Daten werden an den MQTT Server übertragen. MQTT werd ich mich noch spielen bzw. in influxdb schieben und in Grafana visualisieren Danke für eure Hilfe. falls noch von Interesse .... Hichie Lesekopf(Clon) > ESB8266NodeMCU > RaspberryPI4 > Mosquitto Broker habe es auch mit einem Wemos ESP-Wroom-02 D1 Mini WiFi Module ESP8266 + 18650 mit Akku versucht, da ist aber die WLAN Verbindung nicht ausrreichend.

Grüße Kurt

aldadic commented 1 year ago

Super! Freut mich zu hören, dass der Code auch bei dir funktioniert @curtosvienna. Dann werde ich den Code bzw. die Anleitung im repository demnächst entsprechend aktualisieren.

leecher1337 commented 1 year ago

Hallo @curtosvienna

Prima, dass es funzt! Was mich noch interessieren würde, weil ichs mit meinem Cryptoschlüssel ja nicht entschlüsseln kann: Korrespondiert der SystemTitle eigentlich mit der Zählernummer im Portal (die Wiener Netze haben so ein Portal, da steht eine Zählernummer und da kann man sich auch den zugehörigen Cryptoschlüssel anzeigen lassen)? Ich hab nen Landis&Gyr, da beginnt die Zählernummer mit LGZ... Und auch das SystemTitle beginnt so:

Beispiel:

0x4C,0x47,0x5A,0x68,0x71,0x69,0xA4,0x2A
L   ,G   ,Z   ,103 ,113 ,105 ,164 ,42

Aber die Nummer hinter dem LGZ passt nicht mit dem Zähler zusammen, von dem ich auslese, daher die Frage, ob das korrespondieren sollte?

curtosvienna commented 1 year ago

Hallo @leecher1337,

Ich habe hier einen Landis &Gyr E450 Zählernummer beginnt mit : LGZ103075 Und der Schlüssel begint mit 2A47 F8A4 9DAA Ich denke da korrespondiert nichts. Nummer am Zähler) Keine Ahnung ob das relevant ist, ich habe die Buchstaben im key in der config.h kleingeschrieben eingegeben 🤔.

Am 11.02.2023 um 18:18 schrieb leecher1337 @.***>:

 Hallo @curtosvienna

Prima, dass es funzt! Was mich noch interessieren würde, weil ichs mit meinem Cryptoschlüssel ja nicht entschlüsseln kann: Korrespondiert der SystemTitle eigentlich mit der Zählernummer im Portal (die Wiener Netze haben so ein Portal, da steht eine Zählernummer und da kann man sich auch den zugehörigen Cryptoschlüssel anzeigen lassen)? Ich hab nen Landis&Gyr, da beginnt die Zählernummer mit LGZ... Und auch das SystemTitle beginnt so:

Beispiel:

0x4C,0x47,0x5A,0x68,0x71,0x69,0xA4,0x2A L ,G ,Z ,103 ,113 ,105 ,164 ,42 Aber die Nummer hinter dem LGZ passt nicht mit dem Zähler zusammen, von dem ich auslese, daher die Frage, ob das korrespondieren sollte?

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.

leecher1337 commented 1 year ago

Hallo @curtosvienna Ich meine nicht den Schlüssel, sondern den SystemTitle! Also die ersten 8 Bytes, die Du in den IV kopierst.

aldadic commented 1 year ago

@leecher1337 Ich habe mal bei mir nachgesehen. Bei mir beginnt der SystemTitle ebenfalls mit 0x4C, 0x47, 0x5A, 0x67. Die nächsten vier Stellen stimmen aber nicht mit der Zählernummer überein, die ich Smartmeter Portal sehe.

leecher1337 commented 1 year ago

Danke, war bei mir auch so. Ok, dann ist das auch kein Hinweis darauf, warum ich offensichtlich einen Schlüssel habe, der nicht richtig entschlüsselt. Habe neben meiner eigenen Implementierung in C auch dieses Python-Script probiert: https://gist.github.com/pocki80/941fa090a8d6269a9b3b68c195f8750f Kommt exakt dasselbe raus bei dem Python Script, Rahmen-Prüfsumme ist OK, Inhalt ist wirr. Dann kann ich nur hoffen, dass die Wiener Netze rausfinden, warum mein Schlüssel nicht passt. :-(

curtosvienna commented 1 year ago

Also ich gebe in der config.h folgendes ein: const byte KEY[] = ( 0x2a, 0x47, 0xf8, 0xa4, 0x9d, 0xaa, 0x37……. Das sind doch genau die Daten aus dem Schlüssel. 2a 47 f8 a4 9d aa 37 ( 2A47 F8A4 9DAA)

leecher1337 commented 1 year ago

Ich auch, nur bei mir kommt eben dann Schrott :( landis.c ist mein eigenes Entschlüssler, der dasselbe macht wie bei diesem Projekt hier und das og. Python-Script.

$ gcc -o landis -lssl landis.c
$ ./landis
Rahmen-Pruefsumme des Pakets ist gueltig
Entschluesseltes Datenpaket: 52 F6 AA ED 3F A6 C9 E6 C8 39 C5 91 EB D5 02 01 D9 C1 42 6A 54 76 BE E6 CB E5 A5 FF 82 DA B7 22 34 5F 93 9F F8 8E 45 8E CD 40 57 99 0B AC C4 A1 F8 EF 72 EB 01 36 0F E5 23 E7 6A 83 A1 80 4F EE 4A 9B 9A 8F D4 6B B9 2C 5E 4D

Entschluesselung fehlgeschlagen? Falscher Paketstart

Mir sieht das nach Schrott aus...

Mein Code, wenns wen interessiert:

//
// Smartmeter Datenpaket Entschluesselungs Testprogramm
//
#include <stdio.h>
#include <stdint.h>
#include <memory.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/hmac.h>
#include <openssl/buffer.h>

// Schluessel aus dem Kundenportal der Wiener Netze
uint8_t Key[16] = {0x30, 0xCF, 0x95, ....};

// Beispiel
unsigned char MBusData[] = {
    0x7E,
    0xA0,
    0x67,
    0xCE,
    0xFF,
    0x03,
    0x13,
    0x38,0xBD,
    0xE6,
    0xE7,
    0x00,
    0xDB,
    0x08,
    0x4C,0x47,0x5A,0x??,0x??,0x??,0x??,0x??,
    0x4F,
    0x20,
    0x00,0x20,0xE4,0x10,
    0x27,0xDE,0xCF,0x3D,0x4D,0x8D,0xA8,0x00,0xF3,0x20,0x6C,0x16,0x04,0xA0,0xEC,0x9B,0xB0,0x32,0x56,0x10,0x28,0x48,0x72,0x69,0xAF,0x53,0x05,0xE0,0x1A,0x9E,0x1E,0xE1,0x67,0x73,0xD9,0x7C,0x48,0xC5,0x83,0xF1,0x7F,0x7D,0x3D,0x8C,0xAC,0xDF,0x58,0x35,0x5A,0x8B,0x74,0xE8,0x54,0xFA,0xFB,0x37,0x53,0x9F,0xE5,0x2B,0x68,0x95,0xAE,0xBE,0x23,0x8A,0x71,0x0C,0x80,0xF0,0xC6,0xB5,0x60,0x6B,
    0x5D,0xBB,
    0x7E};

// Strukturen der Datenpakete
#pragma pack(1)
typedef uint32_t  TYP_PacketNumber;
typedef struct
{
    uint16_t Year;
    uint8_t Month;
    uint8_t Day;
    uint8_t DayOfWeek;
    uint8_t Hour;
    uint8_t Minute;
    uint8_t Second;
    uint32_t Unknown;
} TYP_TimeStamp;
typedef uint16_t TYP_Unk1;
typedef uint32_t TYP_32BitVal;
typedef uint8_t TYPID;

enum
{
    TYPID_Unk1 = 0x02,
    TYPID_32BitVal = 0x06,
    TYPID_TimeStamp = 0x0C,
    TYPID_PacketNumber = 0x0F
};

typedef struct
{
    TYPID Type;
    TYP_PacketNumber Data;
} DS_PacketNumber;

typedef struct
{
    TYPID Type;
    TYP_TimeStamp Data;
} DS_TimeStamp;

typedef struct
{
    TYPID Type;
    TYP_Unk1 Data;
} DS_Unk1;

typedef struct
{
    TYPID Type;
    TYP_32BitVal Data;
} DS_32BitVal;

// Variante 3
// https://oesterreichsenergie.at/fileadmin/user_upload/Smart_Meter-Plattform/20200201_Konzept_Kundenschnittstelle_SM.pdf
typedef struct
{
    DS_PacketNumber PacketNumber;
    DS_TimeStamp TimeStamp1;
    DS_Unk1 Unk1;
    DS_TimeStamp TimeStamp2;
    DS_32BitVal pA;    // Wirkenergie +A Wh
    DS_32BitVal nA;   // Wirkenergie -A Wh
    DS_32BitVal pR;   // Blindenergie +R varh
    DS_32BitVal nR;   // Blindenergie -R varh
    DS_32BitVal pP;   // Momentanleistung +P W
    DS_32BitVal nP;   // Momentanleistung -P W
    DS_32BitVal pQ;   // Momentanleistung +Q var
    DS_32BitVal nQ;   // Momentanleistung -Q var
} PAYLOAD_PACKET;

typedef struct {

    uint8_t flag;               // Opening flag (0x7E)
    uint8_t type;               // Frame format type
    uint8_t length;             // Length of packet
    uint8_t client_nr;          // Client number?
    uint8_t dst_address;        // Destination address
    uint8_t src_address;        // Source address
    uint8_t control;            // Control field
    uint16_t hcs;               // Header check sequence, CRC-16/X25 (Byte 2-7)
    uint8_t dst_lsap;           // Destination LSAP
    uint8_t src_lsap;           // Source LSAP
    uint8_t llc_quality;        // LLC quality
    uint8_t llc_sdu;            // LLC service data unit
    uint8_t len_sys_t;          // Length of system title
    uint8_t sys_t[8];           // System title
    uint8_t len_content;        // Length of ciphered content
    uint8_t sc_e;               // SC-E: bit 5=1 = Encryption only
    uint32_t inv_cnt;           // Invocation counter
    PAYLOAD_PACKET data;        // Crypted payload (74 bytes)
    uint16_t fcs;               // Frame check sequence
    uint8_t end;                // End flag (0x7E)
} DATA_FRAME;

#pragma pack()

//
// CRC validierung der Pakete
//
uint16_t HdlcCrcReflect(uint16_t crc, int bitnum)
{

  // reflects the lower 'bitnum' bits of 'crc'

  uint16_t i, j = 1, crcout = 0;

  for (i = (uint16_t)1 << (bitnum - 1); i; i >>= 1)
  {
    if (crc & i)
      crcout |= j;
    j <<= 1;
  }
  return (crcout);
}

#define order 16
#define polynom 0x1021
#define crcinit 0xffff
#define crcxor 0xffff

// http://www.zorc.breitbandkatze.de/crc.html
// http://www.zorc.breitbandkatze.de/crctester.c
uint16_t HdlcCrcBitByBit(unsigned char *p, uint16_t len)
{
  uint16_t crchighbit;
  uint16_t i, j, c, bit, crc;
  uint16_t crcmask;

  crcmask = ((((uint16_t)1 << (order - 1)) - 1) << 1) | 1;
  crchighbit = (uint16_t)1 << (order - 1);

  crc = crcinit;
  for (i = 0; i < order; i++)
  {

    bit = crc & 1;
    if (bit)
      crc ^= polynom;
    crc >>= 1;
    if (bit)
      crc |= crchighbit;
  }

  // bit by bit algorithm with augmented zero bytes.
  // does not use lookup table, suited for polynom orders between 1...32.

  for (i = 0; i < len; i++)
  {

    c = (uint16_t)*p++;
    c = HdlcCrcReflect(c, 8);

    for (j = 0x80; j; j >>= 1)
    {

      bit = crc & crchighbit;
      crc <<= 1;
      if (c & j)
        crc |= 1;
      if (bit)
        crc ^= polynom;
    }
  }

  for (i = 0; i < order; i++)
  {
    bit = crc & crchighbit;
    crc <<= 1;
    if (bit)
      crc ^= polynom;
  }

  crc = HdlcCrcReflect(crc, order);
  crc ^= crcxor;
  crc &= crcmask;

  return (crc);
}

int CheckValues(uint8_t *data, size_t dataSize)
{
  uint16_t crc = HdlcCrcBitByBit(data, dataSize - 2); // -2 because last 2 bytes are the checksum I have to compare it to
  uint8_t hiByte = (crc & 0xff00) >> 8;
  uint8_t loByte = (crc & 0xff);

  return hiByte == data[dataSize - 1] && loByte == data[dataSize - 2];
}

int main(int argc, char **argv)
{
    AES_KEY key;
    unsigned char ecount_buf[AES_BLOCK_SIZE] = {0};
    int num = 0;
    unsigned int i;
    DATA_FRAME *fullData = (DATA_FRAME*)MBusData;
    // Initialisation Vector zusammenbaun:
    uint8_t iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
    uint8_t plainText[sizeof(fullData->data)]={0};

    // Rahmen-CRC pruefen
    printf ("Rahmen-Pruefsumme des Pakets ist %s\n", (CheckValues((unsigned char*)&fullData->type, sizeof(DATA_FRAME)-2)?"gueltig":"UNGUELTIG!!!"));

    // 8 Bytes von 13 (=system-title)
    memcpy(&iv[0], fullData->sys_t, 8);
    // 4 Bytes von 23 (=nonce). INFO: Nonce wird bei jedem Paket um 1 groesser (=increment counter)
    memcpy(&iv[8], &fullData->inv_cnt, 4);

    // Entschluesseln
    AES_set_encrypt_key(Key, 128, &key);
    AES_ctr128_encrypt((unsigned char*)&fullData->data, plainText, sizeof(fullData->data), &key, iv, ecount_buf, &num);
    //memcpy(plainText, &fullData->data, sizeof(fullData->data));

    printf ("Entschluesseltes Datenpaket: ");
    for (i=0; i<sizeof(plainText); i++) printf ("%02X ", plainText[i]);
    printf("\n\n");

    if (fullData->data.PacketNumber.Type != TYPID_PacketNumber)
        fprintf(stderr, "Entschluesselung fehlgeschlagen? Falscher Paketstart\n");

} 
aldadic commented 1 year ago

The changes from the GitHub gist were integrated into esp-smartmeter-reader.ino. This sketch now compiles for both ESP32 and ESP8266.