M-Bus Gateway
← Tilbage til blog
· wM-Bus· AES-128· CTR· dekryptering· kryptering· OMS· sikkerhed· nøgler· wmbusmeters

wM-Bus AES-128 CTR dekryptering — nøglehåndtering og implementering

Teknisk guide til wM-Bus AES-128 CTR dekryptering: telegram-struktur, initialization vector, nøglehåndtering, wmbusmeters konfiguration og hvem der ejer AES-nøglerne.

Af M-Bus Gateway

De fleste moderne wM-Bus målere krypterer deres data med AES-128 i CTR-mode. Her er den tekniske forklaring på krypteringsmekanismen og den praktiske guide til nøglehåndtering.


Hvorfor krypteres wM-Bus telegrammer?

wM-Bus sender data ukrypteret som radiosignaler på 868 MHz — enhver SDR-modtager inden for rækkevidde kan opfange dem. Kryptering sikrer at forbrugsdata kun kan læses af autoriserede modtagere.

Ukrypteret C1 telegram:
  2C 44 96 14 AB CD 12 34 06 A1 ... [forbrugsdata i klartekst]
  ↑ FAB ID ↑ serial nr.

Krypteret C1 telegram (AES-128 CTR, mode 5):
  2C 44 96 14 AB CD 12 34 06 A5 2F 67 89 F2 A4 ... [krypteret payload]
  ↑ Encryption mode = 5 (AES-128 CTR)

OMS standard: Encryption mode 5 (AES-128 CTR) er den primære krypteringsmetode i OMS EN 13757-3.


AES-128 CTR: Teknisk forklaring

CTR (Counter mode) er en stream cipher — den konverterer AES-blokchiffer til en keystream:

Initialization Vector (IV):
┌─────────────────┬──────────────────┬──────────────┐
│ Meter address   │ Access number    │ 0x00 padding │
│ (8 bytes)       │ (1 byte counter) │ (7 bytes)    │
└─────────────────┴──────────────────┴──────────────┘
           = 16 bytes total IV

Dekryptering:
  keystream = AES_ECB_encrypt(IV, aes_key)
  plaintext = ciphertext XOR keystream

For payload > 16 bytes:
  IV[counter] inkrementeres pr. 16-byte blok
# Simpel demonstration (ikke produktionskode):
from Crypto.Cipher import AES
import struct

def decrypt_wmbus_aes128_ctr(
    ciphertext: bytes,
    aes_key: bytes,
    meter_address: bytes,   # 8 bytes (FAB+ID+version+medium)
    access_number: int,     # Fra telegram header
) -> bytes:
    """Dekryptér wM-Bus AES-128 CTR payload."""
    # Byg Initialization Vector:
    iv = meter_address + bytes([access_number]) + b'\x00' * 7

    plaintext = bytearray()
    for block_idx in range(0, len(ciphertext), 16):
        # Inkrementer counter i IV:
        iv_block = bytearray(iv)
        iv_block[9] = (access_number + block_idx // 16) & 0xFF

        # Krypter IV med AES-ECB:
        keystream = AES.new(aes_key, AES.MODE_ECB).encrypt(bytes(iv_block))

        # XOR med ciphertext:
        block = ciphertext[block_idx:block_idx + 16]
        plaintext.extend(b ^ k for b, k in zip(block, keystream))

    return bytes(plaintext)

I praksis: wmbusmeters håndterer al kryptering automatisk — du behøver ikke implementere dette selv.


wmbusmeters konfiguration med AES-nøgle

# /etc/wmbusmeters/meter.d/engelmann_stue_2tv.conf
name=engelmann_stue_2tv
type=hca
id=12AB34CD
# AES-nøgle (16 bytes = 32 hex-tegn):
key=A1B2C3D4E5F60718293A4B5C6D7E8F90

# Alternativt: Nøglefil (mere sikker):
# keyfile=/etc/mbus-gateway/keys/12AB34CD.key
# Verificér dekryptering:
wmbusmeters --listento=c1 /dev/wmbus engelmann_stue_2tv

# Output ved korrekt nøgle:
# {"media":"heat_cost_allocator","meter":"hca",
#  "id":"12AB34CD","status":"OK",
#  "current_hca":342,...}

# Output ved forkert nøgle:
# {"media":"heat_cost_allocator","meter":"hca",
#  "id":"12AB34CD","status":"DEC_ERR",...}

Nøglehåndtering i M-Bus Gateway platformen

# server/src/db/models.py
from sqlalchemy import Column, String
from sqlmodel import SQLModel, Field
from typing import Optional

class Meter(TenantBase, table=True):
    id: UUID = Field(default_factory=uuid4, primary_key=True)
    fab_id: str = Field(max_length=4)
    serial_no: str = Field(max_length=20)
    # AES-nøgle krypteret med server-side encryption key:
    aes_key_encrypted: Optional[str] = Field(default=None)
    # Aldrig gem rå nøgle i DB
# server/src/security/crypto.py
from cryptography.fernet import Fernet
import os

ENCRYPTION_KEY = os.environ["METER_KEY_ENCRYPTION_KEY"]  # Fra secrets
fernet = Fernet(ENCRYPTION_KEY)

def encrypt_aes_key(raw_key: str) -> str:
    """Kryptér AES-nøgle inden DB-lagring."""
    return fernet.encrypt(raw_key.encode()).decode()

def decrypt_aes_key(encrypted_key: str) -> str:
    """Dekryptér AES-nøgle ved brug."""
    return fernet.decrypt(encrypted_key.encode()).decode()
# Gateway modtager nøgler krypteret i config-bundle:
# (aldrig i klartekst via MQTT)
async def deploy_meter_keys(gateway_id: str, session: AsyncSession):
    """Send AES-nøgler til gateway via krypteret config-bundle."""
    meters = await get_gateway_meters(gateway_id, session)
    keys = {}
    for meter in meters:
        if meter.aes_key_encrypted:
            raw_key = decrypt_aes_key(meter.aes_key_encrypted)
            keys[meter.serial_no] = raw_key  # Til wmbusmeters config

    # Transmit kun via TLS-sikret MQTT til gateway's egne topics:
    await publish_encrypted_config(gateway_id, {"meter_keys": keys})

Hvem ejer AES-nøglerne?

Dette er et centralt spørgsmål ved valg af operatør:

ScenarieAES-nøgle ejerskabKonsekvens
Techem/ista/Brunata installererOperatøren — du har dem ikkeLock-in: skift kræver EU EED art. 9c anmodning
OMS-kompatibel måler (Engelmann/Kamstrup)Distributør/installatørKan overdrages ved salg
Du installerer selvDig — fuldt ejerskabFuld fleksibilitet
Danfoss AllyIngen nøgle — ukrypteret T1Ingen krypteringsproblem

EU EED artikel 9c (2023): Kunder har ret til AES-nøgler til egne målere. Operatører er forpligtet til at udlevere dem inden for rimelig tid.


Hvad sker ved manglende AES-nøgle?

# wmbusmeters output uden korrekt nøgle:
{
  "media": "heat_cost_allocator",
  "id": "12AB34CD",
  "status": "DEC_ERR",
  "current_hca": null,  # Ingen data
  "rssi_dbm": -72
}

Platformen registrerer status = "DEC_ERR" og:

  1. Viser amber advarsel på gateway-dashboard
  2. Sender email til udlejer om manglende nøgle
  3. Inkluderer måleren i AES-dekrypteringsdækning-rapporten

Nøgle-rotation

# Ved mistanke om kompromitteret nøgle:
# 1. Få ny nøgle fra målerfabrikant (kræver fysisk adgang til måler)
# 2. Opdatér i portal → Meter > Rediger > AES-nøgle
# 3. Platform sender ny konfiguration til gateway ved næste connection
# 4. wmbusmeters genstartes automatisk på Pi

# VIGTIGT: AES-nøgle er normalt statisk for målerens levetid (10 år)
# Rotation er sjælden og kræver fysisk adgang til måleren

Konklusion

wM-Bus AES-128 CTR kryptering er standard for alle OMS-kompatible målere. wmbusmeters håndterer kryptering automatisk givet korrekt nøgle. Det kritiske punkt er nøgleejerskab — ved selveje har du fuld kontrol over nøglerne. AES-nøgler gemmes krypteret i platformen og transmitteres kun via TLS-sikrede kanaler til gateways.

Se AES-dekrypteringsdækning dashboard eller Engelmann HCA guide.