Repository navigation
Conversation
Debug logging no longer switches v3.5 messages to the fixed IV b'0123456789ab'. The fixed IV (added in 0caf951 for v3.5 bring-up and restored in cdc351c) is now enabled only by set_debug(True, static_iv=True), which logs a warning; set_debug(False) clears it. Because tests.py runs with DEBUG on, test_gcm_iv_is_random_and_12_bytes was always skipped; it now runs, and a new test covers the opt-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The security behavior and regression coverage are sound; only a non-blocking documentation wording correction remains.
1 open finding
What changed in this PR
Decouples insecure fixed AES-GCM IVs from DEBUG logging while preserving explicit packet-analysis support.
Changes:
- Adds
static_ivopt-in with a security warning. - Restores random IV behavior under DEBUG logging and adds regression tests.
- Updates security, protocol, API, and release documentation.
| File | Description |
|---|---|
tinytuya/core/crypto_helper.py |
Gates the fixed IV behind explicit state. |
tinytuya/core/core.py |
Extends set_debug() with the warned opt-in. |
tinytuya/__init__.py |
Documents the new parameter. |
tests.py |
Tests random, fixed, and reset behavior. |
RELEASE.md |
Records the security change. |
README.md |
Updates the public API listing. |
PROTOCOL.md |
Documents IV generation and override. |
AGENTS.md |
Adds a crypto safety constraint. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jasonacox-sam
left a comment
There was a problem hiding this comment.
This closes the loop #722 opened and cdc351c accidentally re-opened: "debug on" was silently downgrading every v3.5 session to a reused GCM IV, and test_gcm_iv_is_random_and_12_bytes was skipping itself on every CI run because tests.py enables DEBUG at import — so the guard for #722 was never actually guarding anything. That's a great catch on the test, not just the code.
The opt-in shape is right: STATIC_DEBUG_IV as a class flag set only by set_debug(..., static_iv=True), decoupled from the log level, with a log.warning at enable time, cleared when debug is toggled off, documented in PROTOCOL.md/README, and the previously-skipped test now asserts DEBUG is on while still requiring random IVs. The new test_static_iv_only_when_explicitly_requested covers on/off/reset paths. AGENTS.md exception note prevents future "convenience" reintroduction. No concerns; approving.
— Sam ⚙️

Why
When DEBUG logging is enabled,
AESCipher.get_encryption_iv()makes every v3.5 (AES-GCM) message use the fixed IVb'0123456789ab'. GCM must never reuse an IV under the same key: with a reused IV, someone capturing LAN traffic can recover message contents and, worse, GCM's authentication subkey, letting them forge commands the device accepts for the rest of that session. Users are routinely asked to enable debug logging when troubleshooting (e.g. Home Assistant), and persistent sessions can last days, so "debug on" silently re-opened the hole #722 closed.History: uzlonewolf added the fixed debug IV in 0caf951 (Jan 2023) during v3.5 bring-up (#256) to make captures reproducible. #722 replaced it with
os.urandom(12), and cdc351c restored it on review feedback to preserve that troubleshooting workflow. This PR keeps the workflow but makes it an explicit choice instead of a side effect of the log level.A side effect of the old behavior:
tests.pyenables DEBUG at import, sotest_gcm_iv_is_random_and_12_bytes(the guard for #722) was skipped on every run.Changes
AESCipher.STATIC_DEBUG_IV(defaultFalse) controls the fixed IV; the log level no longer does.set_debug(toggle=True, color=True, static_iv=False): new parameter added at the end (existing calls unchanged).static_iv=Trueenables the fixed IV and logs a WARNING that encryption is not secure;set_debug(False)clears it.__init__.pyfunction lists, PROTOCOL.md IV field, AGENTS.md safety rail (documents the single allowed exception), RELEASE.md.No wire-format change: the IV is carried in each frame, and the device accepts any value.
Note: v3.5 session keys depend on the device's random nonce, so ciphertext still differs across sessions even with
static_iv=True; the fixed IV only makes messages repeatable within a session. Debug logs already show each message's plaintext (final payload) and full encrypted frame including its IV.Testing
python -m testspasses with 0 skips on cryptography (3.8, 3.10), pycryptodome, and with pyaes (GCM tests skip as expected);pylint -Eclean.🤖 Generated with Claude Code