MQTT Güvenliği

AES-GCM ile Uçtan Uca MQTT Payload Şifreleme

TLS, MQTT mesajlarını aktarım sırasında korur ama broker yine de düz metni görür. Python'da AES-GCM ile uçtan uca MQTT payload şifrelemeyi nasıl ekleyeceğinizi ve anahtarları, nonce'ları ve topic'leri nasıl güvenle yöneteceğinizi öğrenin.

Bir sensörden bir uygulamaya broker üzerinden giden mühürlü bir zarfı gösteren MQTT payload şifreleme illüstrasyonu
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

MQTT payload şifreleme, mesaj gövdesinin kendisini şifrelemek demektir; böylece broker'ı kim işletirse işletsin, yalnızca publisher ve hedeflenen subscriber'lar mesajı okuyabilir. Bu rehberde uçtan uca şifrelemeyi TLS'in üzerine eklemenin ne zaman değdiğini, mesaj formatını nasıl güvenle tasarlayacağınızı ve bunu gerçek bir broker bağlantısı üzerinden AES-GCM ile Python'da nasıl uygulayacağınızı öğreneceksiniz.

TLS ve MQTT Payload Şifreleme

TLS, her istemci ile broker arasındaki bağlantıyı şifreler. Bu vazgeçilmezdir ve her zaman kullanmalısınız; MQTT TLS yapılandırması rehberimize bakın. Ancak TLS broker'da sonlanır. Broker'ın içinde mesaj düz metindir; öyle olmak zorundadır ki broker onu yönlendirebilsin, retained mesaj olarak saklayabilsin veya çevrimdışı bir oturum için kuyruğa alabilsin.

Payload şifreleme, publisher'dan subscriber'a kadar mesajla birlikte yolculuk eden ikinci bir katman ekler.

Özellik Yalnızca TLS TLS + payload şifreleme
Ağ üzerinde korunur Evet Evet
Broker operatörü okuyabilir Evet Hayır
Saklanan retained veya kuyruktaki mesajlarda okunabilir Evet Hayır
Topic adları gizlenir Hayır (broker görür) Hayır (broker yine görür)
Broker payload'ları inceleyebilir veya çözebilir Evet Hayır
Gereken anahtar yönetimi Yalnızca broker sertifikası Her istemcide uygulama anahtarları

İnsanların sıklıkla gözden kaçırdığı iki "Hayır" satırına dikkat edin. Broker yönlendirme için topic'lere ihtiyaç duyduğundan topic'ler okunabilir kalır; bu yüzden topic adlarına asla gizli bilgi koymayın. Ayrıca payload okuyan her broker özelliği şifreli mesajlar için çalışmayı bırakır. Örneğin ConnectMQ'nun canlı topic gezgini, eşik tabanlı uyarı kuralları ve Sparkplug B çözümlemesi yalnızca ayrıştırabildikleri verileri gösterebilir veya değerlendirebilir; dolayısıyla şifreli payload'lar orada opak baytlar olarak görünür.

Uçtan Uca Şifreleme Ne Zaman Mantıklıdır?

Şu durumlarda payload şifreleme ekleyin:

  • Düzenlemeler veya sözleşmeler altyapı sağlayıcılarının veriyi okuyamamasını gerektiriyorsa.
  • Mesajlar tam olarak kontrol etmediğiniz paylaşımlı veya herkese açık broker'lardan, bridge'lerden ya da gateway'lerden geçiyorsa.
  • Retained mesajlar veya kuyruklu oturumlar kişisel veriler ya da erişim kodları gibi hassas değerler tutuyorsa.
  • Sistemin bir bölümüne ait kimlik bilgilerinin sızması ihtimaline karşı derinlemesine savunma istiyorsanız.

Broker tarafında görünürlüğe ihtiyaç duyduğunuzda (broker portalındaki dashboard'lar, değerler üzerindeki uyarı kuralları) ve operatöre güvendiğinizde ya da kısıtlı cihazlar ek kodu ve anahtar yönetimini kaldıramadığında bundan vazgeçin. Pek çok ekip yalnızca .../commands/unlock gibi birkaç hassas topic'i şifreler ve telemetriyi TLS üzerinden düz metin olarak bırakır.

Algoritma Seçimi

Bir kimlik doğrulamalı şifreleme (authenticated encryption) algoritması kullanın. Bu, payload'u şifreler ve her türlü değişikliği tespit eden bir etiket (tag) ekler. İki iyi seçenek:

  • AES-256-GCM: Yaygın olarak bulunur, pek çok CPU'da ve ESP32'de (mbedTLS aracılığıyla) donanım hızlandırmalıdır. Aynı anahtar altında her mesaj için benzersiz bir 96 bitlik nonce gerektirir.
  • XChaCha20-Poly1305 (libsodium crypto_aead_xchacha20poly1305_ietf): Yazılımda hızlıdır ve 192 bitlik nonce'u, gerçekçi bir çakışma riski olmadan rastgele üretilebilir.

MAC olmadan AES-CBC, herhangi bir biçimde ECB modu ya da XOR veya Base64 ile "şifreleme" kullanmayın. Bunlar sahte bir güvenlik hissi verir.

Şifreli Mesaj Formatını Tasarlamak

AES-GCM için basit ve sağlam bir format şöyledir:

| version (1 byte) | nonce (12 bytes) | ciphertext + 16-byte tag |

Üç tasarım kuralı algoritmadan daha önemlidir:

  1. Aynı anahtarla bir nonce'u asla yeniden kullanmayın. AES-GCM'de yeniden kullanım düz metni açığa çıkarabilir ve sahteciliğe izin verebilir. Anahtar başına orta düzey mesaj hacimleri için rastgele 12 baytlık bir nonce kabul edilebilir; yüksek hacimli publisher'lar için anahtarları düzenli olarak döndürün.
  2. Şifreli metni topic'ine bağlayın. Topic'i ek kimlik doğrulamalı veri (AAD) olarak verin. Aksi halde broker erişimi olan bir saldırgan, şifreli bir "unlock" mesajını başka bir cihazın topic'inde yeniden oynatabilir.
  3. Düz metnin içine bir zaman damgası veya sayaç ekleyin; böylece subscriber'lar daha sonra yeniden oynatılan eski mesajları reddedebilir.

Python Örneği: Şifreli Publish ve Subscribe

Aşağıdaki örnek paho-mqtt 2.x ve cryptography paketini kullanır. Önce bunları kurun:

pip install "paho-mqtt>=2.0" cryptography

Bir kez anahtar üretin ve bunu publisher ve subscriber ile MQTT üzerinden değil, secret store'unuz veya ortam değişkenleri aracılığıyla paylaşın:

python -c "import os,base64;print(base64.b64encode(os.urandom(32)).decode())"

Aşağıdakini secure_mqtt.py olarak kaydedin:

import base64
import json
import os
import sys
import time

import paho.mqtt.client as mqtt
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag

BROKER = "broker.connectmq.com"
PORT = 8883
TOPIC = "connectmq/demo/e2e-91c4/door/command"
VERSION = b"\x01"
MAX_AGE_SECONDS = 30

KEY = base64.b64decode(os.environ["MQTT_PAYLOAD_KEY"])
aead = AESGCM(KEY)


def encrypt(topic: str, data: dict) -> bytes:
    data["ts"] = int(time.time())
    plaintext = json.dumps(data).encode()
    nonce = os.urandom(12)
    ciphertext = aead.encrypt(nonce, plaintext, topic.encode())
    return VERSION + nonce + ciphertext


def decrypt(topic: str, blob: bytes) -> dict:
    if len(blob) < 1 + 12 + 16 or blob[:1] != VERSION:
        raise ValueError("unknown message format")
    nonce, ciphertext = blob[1:13], blob[13:]
    plaintext = aead.decrypt(nonce, ciphertext, topic.encode())
    data = json.loads(plaintext)
    if abs(time.time() - data["ts"]) > MAX_AGE_SECONDS:
        raise ValueError("stale or replayed message")
    return data


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    if userdata == "sub":
        client.subscribe(TOPIC, qos=1)


def on_message(client, userdata, msg):
    try:
        print("Decrypted:", decrypt(msg.topic, msg.payload))
    except (InvalidTag, ValueError) as exc:
        print("Rejected message:", exc)


def make_client(role: str) -> mqtt.Client:
    client = mqtt.Client(
        mqtt.CallbackAPIVersion.VERSION2,
        client_id=f"e2e-{role}-91c4",
        userdata=role,
    )
    client.tls_set()  # TLS yine de kimlik bilgilerini ve meta veriyi korur
    # Özel ConnectMQ broker:
    # client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD")
    client.on_connect = on_connect
    client.on_message = on_message
    client.connect(BROKER, PORT, keepalive=60)
    return client


if __name__ == "__main__":
    role = sys.argv[1] if len(sys.argv) > 1 else "sub"
    client = make_client(role)
    if role == "pub":
        client.loop_start()
        time.sleep(1)
        payload = encrypt(TOPIC, {"action": "unlock", "door": "lab-2"})
        client.publish(TOPIC, payload, qos=1).wait_for_publish()
        print("Published", len(payload), "encrypted bytes")
        client.loop_stop()
        client.disconnect()
    else:
        client.loop_forever()

Subscriber'ı ve publisher'ı aynı anahtarla iki terminalde çalıştırın:

export MQTT_PAYLOAD_KEY="paste-the-base64-key-here"
python secure_mqtt.py sub      # terminal 1
python secure_mqtt.py pub      # terminal 2

Aynı topic'e mosquitto_sub ile subscribe olursanız yalnızca ikili gürültü görürsünüz; amaç da budur. Bir payload'un tek bir baytını değiştirin, başka bir topic'e publish edin veya şifreyi çözmeden önce 30 saniyeden fazla bekleyin; subscriber mesajı reddeder. Örnek herkese açık MQTT test broker'ını kullandığından şifreli metni herkes görebilir, ancak anahtarı olmayan hiç kimse onu okuyamaz veya taklit edemez.

Anahtar Yönetimi: İşin Zor Kısmı

Şifreleme kodu kısadır; projelerin başarıya ulaştığı veya başarısız olduğu yer anahtar yönetimidir.

  1. Anahtarları dar kapsamlı tutun. Tüm filo için tek bir anahtar değil, cihaz başına veya cihaz grubu başına bir anahtar kullanın. Sızan bir anahtar mümkün olduğunca az şeyi açığa çıkarmalıdır.
  2. Anahtarları cihazlarda güvenle saklayın. Mikrodenetleyicilerde şifreli flash veya bir secure element, sunucularda bir işletim sistemi anahtar deposu veya secret manager kullanın. Cihazlarda MQTT kimlik bilgilerini saklama rehberimiz şifreleme anahtarları için de aynen geçerlidir.
  3. Bir anahtar kimliği (key ID) ekleyin. Başlığı bir anahtar tanımlayıcısıyla genişletin; böylece subscriber'lar doğru anahtarı seçebilir ve siz kesinti olmadan rotasyon yapabilirsiniz.
  4. Anahtarları döndürün. Yeni bir anahtar tanıtın, subscriber'ların ikisini de kabul etmesini sağlayın, publisher'ları geçirin ve ardından eski anahtarı emekliye ayırın.
  5. Çoktan bire akışlar için asimetrik şemaları değerlendirin. libsodium sealed box'larıyla binlerce cihaz bir backend'in açık anahtarına şifreleme yapabilir ve özel anahtarı yalnızca backend tutar.

Gömülü Cihazlar

Bir ESP32'de mbedTLS (TLS için zaten mevcuttur) mbedtls_gcm_crypt_and_tag ve mbedtls_gcm_auth_decrypt fonksiyonlarını sağlar; bu yüzden payload şifreleme kod boyutuna çok az şey ekler. Nonce'lar için donanımsal rastgele sayı üretecini (esp_fill_random) kullanın ve RNG çıktısının gerçekten rastgele olması için Wi-Fi veya Bluetooth'un çalıştığından ya da uygun bir entropi kaynağının etkin olduğundan emin olun.

Yaygın Uygulama Hataları

IoT projelerinde payload şifreleme kodunu incelerken aynı hatalar tekrar tekrar karşımıza çıkar:

  • Sabit bir nonce veya yeniden başlatmada sıfırlanan bir sayaç nonce'u. Her güç döngüsünden sonra sayacını sıfırdan başlatan bir cihaz nonce'ları yeniden kullanır. Rastgele nonce'lar kullanın veya sayacı flash'ta kalıcı hale getirin.
  • Şifre çözme hatalarını görmezden gelmek. Bir InvalidTag hatasını loglayıp ardından mesajı yine de işleyen bir subscriber'ın hiçbir koruması yoktur. Her hatayı düşürülmüş bir mesaj olarak ele alın.
  • Şifreledikten sonra sıkıştırmak. Şifreli metin sıkıştırılamaz; boyut önemliyse önce JSON'u sıkıştırın.
  • Formatları bir sürüm baytı olmadan karıştırmak. Algoritmaları değiştirdiğinizde veya bir key ID eklediğinizde eski ve yeni cihazlar ayırt edilebilir olmalıdır. Örnekteki sürüm baytı tam da bu nedenle vardır.
  • Anahtarı kodla aynı depoya koymak. Anahtarların yeri bir secret store, ortam değişkenleri veya cihazdaki güvenli depolamadır; asla Git değil.

Olumsuz yolları bilinçli olarak test edin: kesilmiş bir mesaj, tek bir biti çevrilmiş bir mesaj ve yeniden oynatılmış eski bir mesaj publish edin ve her birinin reddedildiğini doğrulayın.

Şifreleme Broker Güvenliğinin Yerini Tutmaz

Payload şifreleme verinin gizliliğini ve bütünlüğünü korur, ancak birinin bağlanmasını, topic'leri boğmasını veya meta veriye subscribe olmasını engellemez. Yine de TLS'e, güçlü cihaz başına kimlik bilgilerine ve broker tarafı korumalarına ihtiyacınız vardır. ConnectMQ gibi barındırılan bir MQTT broker; TLS, client ID'lere bağlı kimlik bilgileri, plan bazlı kotalar, flood koruması ve tekrarlanan başarısız oturum açma denemelerinden sonra IP engelleme sağlarken uçtan uca şifreleme verinin kendisini gizli tutar. Birlikte MQTT güvenliği en iyi uygulamaları yazımızdaki kontrol listesini karşılarlar.

ConnectMQ ile Başlayın

app.connectmq.com adresinde Serverless planla, kredi kartı gerekmeden ücretsiz bir broker oluşturun ve yukarıdaki örneği kendi kimlik bilgilerinizle 8883 portunda çalıştırın. ConnectMQ TLS'i, kimlik doğrulamayı ve ölçeklemeyi üstlenirken payload anahtarları uygulamanızda kalır.

Sık Sorulan Sorular

MQTT'de yerleşik payload şifreleme var mı?

Hayır. MQTT 3.1.1 ve 5.0 hiçbir payload şifrelemesi tanımlamaz; payload'lar opak baytlardır. Şifreleme bağlantı üzerinde TLS tarafından ve isteğe bağlı olarak payload üzerinde uygulamanız tarafından yapılır.

Topic adını da şifreleyebilir miyim?

Yönlendirmeyi bozmadan hayır, çünkü broker subscription'ları topic adları üzerinden eşleştirir. Topic'lerde hassas bilgi bulundurmayın ve isimler veya adresler yerine opak cihaz ID'leri kullanın.

Payload şifreleme mesaj boyutunu artırır mı?

Biraz. Yukarıdaki AES-GCM formatı mesaj başına 29 bayt (sürüm, nonce ve tag) ekler; sonucu Base64 ile kodlarsanız buna kodlama ek yükü de eklenir.

Payload'lar şifreliyse yine de TLS kullanmalı mıyım?

Evet. TLS; MQTT kimlik bilgilerinizi, client ID'leri, topic adlarını ve protokol meta verisini korur ve broker'ın kimliğini doğrular. Bunların hiçbirini payload şifreleme kapsamaz.

İlgili yazılar

MQTT Kimlik Bilgileri Kesintisiz Nasıl Yenilenir?

Bir filo genelinde MQTT kullanıcı adı ve şifrelerini yenilemek için adım adım çift kimlik bilgisi stratejisi: yeni kimlik bilgisini hazırlayın, geri dönüş seçeneğiyle geçiş yapın, doğrulayın ve ancak ondan sonra eskisini iptal edin.

6 dk okuma

MQTT Kimlik Bilgilerini Gömülü Cihazlarda Güvenle Saklamak

ESP32 ve Linux cihazlarda MQTT kullanıcı adı ve şifrelerini korumanın yollarına pratik bir karşılaştırma; şifrelenmiş NVS ve flash şifrelemeden secure element'lere ve TPM destekli systemd kimlik bilgilerine kadar.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.