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:
- 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.
- Ş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.
- 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.
- 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.
- 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.
- 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.
- 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.
- Ç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
InvalidTaghatası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.



