Skip to content

Protect MQTT telemetry with authenticated encryption

Encrypt one fictional telemetry message before publishing and prove that tamper, wrong context, replay, and wrong-key inputs fail without releasing plaintext.

Shield Shelly: I want you to test the protected message's boundary and prove every unsafe path rejects before plaintext is trusted., your practice guide

Shield Shelly: I want you to test the protected message's boundary and prove every unsafe path rejects before plaintext is trusted.
Predict the reading, then compare it with the measurement.

Python 3 in your browser (JupyterLite)

Python · no install

Encrypt one fictional telemetry message before publishing and prove that tamper, wrong context, replay, and wrong-key inputs fail without releasing plaintext.

Tier 2 · Web · paste-in setup · No account

Version tested: Python 3.12.7 / Pyodide 0.27.6 in JupyterLite 0.6.4; Chromium 148.0.7778.96; captured against a local Mosquitto-compatible WebSocket broker because the capture host cannot reach the public broker; the notebook targets test.mosquitto.org; captureSource playwright:jupyterlite. Date: 2026-09-09.

Open the notebook in your browser and run each Python cell; no install or account is needed.

Three ways to run: use JupyterLite here with no install; run main.py locally from the downloadable lab folder; or open the same notebook in Google Colab.

Open in your browser (new tab)

Steps

Screens captured against JupyterLite Python 3.12.7 / Pyodide 0.27.6; Chromium 148.0.7778.96 on 2026-09-10; the tool may have moved on — the text steps are the contract.

  1. 1 Step 1

    Do
    Run `python3 main.py --step 1` to generate a local AES-256-GCM key and inspect only its boundary metadata.
    You will see
    The `Local key boundary` table shows AES-256-GCM, `key length 32 bytes`, `key output hidden`, and the broker-safe fields.
    Why it matters
    The visible metadata proves the key's size and local boundary while keeping the secret itself out of logs and traffic.
    JupyterLite notebook output for mqtt-aead-telemetry step 1, captured after running the real Python cell.
    Step 1 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  2. 2 Step 2

    Do
    Run `python3 main.py --step 2` online to encrypt the fictional reading locally and publish its protected packet to the random broker topic.
    You will see
    The output places the fictional plaintext beside truncated ciphertext, prints the received `topic payload`, then shows the authenticated decrypted plaintext.
    Why it matters
    The side-by-side forms and actual broker packet show that routing succeeds while object security hides plaintext from the broker.
    JupyterLite notebook output for mqtt-aead-telemetry step 2, captured after running the real Python cell.
    Step 2 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  3. 3 Step 3

    Do
    Run `python3 main.py --step 3` to authenticate the topic and schema as associated data while decrypting the packet.
    You will see
    The authentication view shows plaintext, truncated ciphertext, `AAD topic=iotclass/pump-7/telemetry;schema=1`, `Tag check VALID`, and the released message.
    Why it matters
    Showing all three inputs and the valid-tag result demonstrates how visible routing context is authenticated with encrypted content.
    JupyterLite notebook output for mqtt-aead-telemetry step 3, captured after running the real Python cell.
    Step 3 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  4. 4 Step 4

    Do
    Run `python3 main.py --step 4` to flip the first ciphertext byte before tag verification.
    You will see
    The original and altered ciphertext differ in the first hex byte beside `Rule broken: authentication tag must match every ciphertext byte.` and `plaintext: NOT RELEASED`.
    Why it matters
    The before/after bytes connect a concrete mutation to failed authentication and prove the receiver fails closed.
    JupyterLite notebook output for mqtt-aead-telemetry step 4, captured after running the real Python cell.
    Step 4 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  5. 5 Step 5

    Do
    Run `python3 main.py --step 5` to verify the pump-7 packet with associated data naming pump-8.
    You will see
    The unchanged ciphertext appears beside expected pump-7 AAD and supplied pump-8 AAD, followed by `Tag check: INVALID; plaintext: NOT RELEASED`.
    Why it matters
    Comparing the two contexts shows that associated data is authenticated even though it is not encrypted.
    JupyterLite notebook output for mqtt-aead-telemetry step 5, captured after running the real Python cell.
    Step 5 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  6. 6 Step 6

    Do
    Run `python3 main.py --step 6` to submit message ID pump-7:42 twice to the receiver replay window.
    You will see
    The replay table shows arrival 1 for `pump-7:42` as ACCEPT and arrival 2 for the same ID as REJECT_REPLAY.
    Why it matters
    The two submissions expose the receiver state that AEAD itself cannot provide and make the freshness rule reproducible.
    JupyterLite notebook output for mqtt-aead-telemetry step 6, captured after running the real Python cell.
    Step 6 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  7. 7 Step 7

    Do
    Run `python3 main.py --step 7` to generate 1,000 96-bit nonces for one bounded test key scope.
    You will see
    The check lists AES-GCM, 96-bit nonce width, one-key scope, sample size 1000, and `unique count 1000` before the no-reuse rule.
    Why it matters
    The scope and sample rows explain what the bounded uniqueness observation does and does not prove about production nonce allocation.
    JupyterLite notebook output for mqtt-aead-telemetry step 7, captured after running the real Python cell.
    Step 7 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  8. 8 Step 8

    Do
    Run `python3 main.py --step 8` to decrypt a valid packet with a different generated key.
    You will see
    The truncated ciphertext appears beside hidden key A and hidden key B, then `Tag check: INVALID; plaintext: NOT RELEASED`.
    Why it matters
    The named-but-hidden key boundaries demonstrate wrong-key failure without leaking either secret into diagnostics.
    JupyterLite notebook output for mqtt-aead-telemetry step 8, captured after running the real Python cell.
    Step 8 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)
  9. 9 Step 9

    Do
    Run `python3 main.py --step 9` and review the expected receiver decision for every tested path.
    You will see
    The release matrix shows valid as `ACCEPT RELEASED` while tamper, context, replay, and wrong key each read `REJECT NOT RELEASED`.
    Why it matters
    The row-by-row matrix consolidates the demonstrated paths into reviewable release evidence for the claimed fail-closed boundary.
    JupyterLite notebook output for mqtt-aead-telemetry step 9, captured after running the real Python cell.
    Step 9 · Python 3 in your browser (JupyterLite); numbered callout added to a real capture. Enlarge screenshot (new tab)

Chapter checks

These questions refer to the chapter’s examples. Use the return links to review their answers.

  1. A vendor says sensor readings are end-to-end secure because each device uses TLS to the cloud message broker, which then forwards the readings to other services. What is the strongest correction?

    Return to the chapter’s knowledge check
  2. Telemetry must stay confidential from the cloud message broker, which only needs to route it. The team secures every device-to-broker link with TLS. What is the correct review conclusion?

    Return to the chapter’s knowledge check
  3. A device uses a fixed long-term key to derive the same session key for every TLS connection, to save computation. Years of recorded ciphertext are later exposed when that long-term key leaks. Why is this worse than a design that uses ephemeral key agreement per session?

    Return to the chapter’s knowledge check

Caution

The public broker sees topics, associated data, nonces, ciphertext lengths, and timing, and it offers no availability or identity guarantee. Use only the fictional payload. This ephemeral-key, in-memory replay exercise is not production key provisioning, durable nonce allocation, replay persistence, authenticated transport, or endpoint authorization.

Return to Secure Communication and Firmware · Browse Labs