Skip to content

Conversation

@zerzhang
Copy link
Collaborator

Breaking change

SwitchBot will be implementing the new AES_GCM encryption method in its upcoming firmware update.

Switchbot Encryption Modes (CTR/GCM)

Mode Identification

  • When the device returns a random number command, the 3rd byte (index 2) indicates the encryption mode:
  • 0x00: AES-CTR
  • 0x01: AES-GCM
  • Values ​​other than 0x00/0x01 will throw an exception.

IV Parsing

  • CTR: Take result[4:] from the random number response, length is 16 bytes.
  • GCM: Take result[4:-4] from the random number response, length is 12 bytes (the last 4 bytes are for compatibility padding).

GCM IV Increment Rule

  • Increment by +1 only after the encryption command is completed.
  • Only the 12-byte IV is incremented by one using big-endian carry.
  • Unencrypted commands do not trigger an increment.

GCM Tag Handling

  • The firmware only returns a 2-byte tag, therefore the decryption side does not perform full tag verification.
  • The decryption logic only calls decryptor.update(...), not finalize().
  • Authentication verification is handled by the firmware.

@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
switchbot/devices/device.py 67.34% <100.00%> (+2.90%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@zerzhang zerzhang changed the title Compatible with GCM mode encryption Newly implemented GCM encryption method Jan 23, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements support for SwitchBot’s upcoming AES-GCM encryption mode (alongside existing AES-CTR) for encrypted BLE devices.

Changes:

  • Added AESMode detection from the encryption init response and mode-specific IV parsing (CTR=16 bytes, GCM=12 bytes with padding trim).
  • Updated encryption/decryption flow to support GCM (including returning a 2-byte header derived from the GCM tag and incrementing GCM IV after encrypted commands).
  • Expanded unit tests to cover mode resolution, GCM IV increment behavior, and updated _encrypt return shape.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
switchbot/devices/device.py Adds AES mode enum/detection, GCM-specific IV parsing, encryption/decryption changes, and GCM IV increment logic.
tests/test_encrypted_device.py Updates tests for new _encrypt return type and adds coverage for GCM mode behavior and mode resolution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@bdraco
Copy link
Member

bdraco commented Jan 25, 2026

Thanks @zerzhang

I did some tweaks. If its all still working as expected, please let me know and I'll merge and do a release.

@bdraco bdraco merged commit 02d7a38 into sblibs:master Jan 26, 2026
6 checks passed
@zerzhang
Copy link
Collaborator Author

2026-01-26 11:35:59.778 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Initializing encryption
2026-01-26 11:35:59.778 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Connecting; RSSI: -80
2026-01-26 11:35:59.778 INFO (MainThread) [habluetooth.wrappers] B0:E9:FE:B2:87:5F - None: Found 1 connection path(s), preferred order: esp32s3-ble-proxy-fcae (98:A3:16:23:FC:AE) (RSSI=-77) (failures=0) (in_progress=0) (slots=3/3 free) (score=-77.0)
2026-01-26 11:36:00.399 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Connected; RSSI: -80
2026-01-26 11:36:00.399 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Starting notify and disconnect timer; RSSI: -80
2026-01-26 11:36:00.399 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Subscribe to notifications; RSSI: -80
2026-01-26 11:36:00.413 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Sending command: 570000000f210348
2026-01-26 11:36:00.615 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Notification received: 01000100a9c4688dfe3cd5189d40e85400000000
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Encryption init response: 01000100a9c4688dfe3cd5189d40e85400000000
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Detected encryption mode: GCM
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Encryption initialized successfully
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Scheduling command 57489eecff8cc5742153f25c
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Already connected before obtaining lock, resetting timer; RSSI: -80
2026-01-26 11:36:00.616 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Sending command: 57489eecff8cc5742153f25c
2026-01-26 11:36:00.815 DEBUG (MainThread) [switchbot.adv_parsers.lock] mfr_data: dd0a30601b1910970020
2026-01-26 11:36:00.816 DEBUG (MainThread) [switchbot.adv_parsers.lock] data: 6f8064
2026-01-26 11:36:00.816 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Notification received: 01480000
2026-01-26 11:36:00.816 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Scheduling command 57483c6993bd9bcf8b59db
2026-01-26 11:36:00.816 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Already connected before obtaining lock, resetting timer; RSSI: -80
2026-01-26 11:36:00.816 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Sending command: 57483c6993bd9bcf8b59db
2026-01-26 11:36:00.912 DEBUG (MainThread) [switchbot.adv_parsers.lock] mfr_data: b0e9febbf4ae12a830470001
2026-01-26 11:36:00.918 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Notification received: 0148f5bd0ee39ace8b
2026-01-26 11:36:00.919 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Scheduling command 57481dd2b7
2026-01-26 11:36:00.919 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Already connected before obtaining lock, resetting timer; RSSI: -80
2026-01-26 11:36:00.919 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Sending command: 57481dd2b7
2026-01-26 11:36:01.016 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Notification received: 0148045d8d57d4a4ba45
2026-01-26 11:36:01.016 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Fire callbacks
2026-01-26 11:36:01.115 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:B2:87:5F): Fire callbacks
2026-01-26 11:36:01.217 DEBUG (MainThread) [switchbot.adv_parsers.lock] mfr_data: b0e9feb2875f159200380008
2026-01-26 11:36:01.217 DEBUG (MainThread) [homeassistant.components.switchbot.coordinator] B0:E9:FE:B2:87:5F: Switchbot data: {'rawAdvData': None, 'data': {'sequence_number': 20, 'calibration': True, 'status': <LockStatus.LOCKING: 2>, 'update_from_secondary_lock': False, 'door_open_from_secondary_lock': False, 'door_open': False, 'auto_lock_paused': False, 'battery': 56, 'double_lock_mode': False, 'is_secondary_lock': False, 'manual_unlock_linkage': False, 'unclosed_alarm': False, 'unlocked_alarm': False, 'night_latch': False, 'power_alarm': False, 'battery_status': 0, 'firmware': 2.8}, 'model': b'\x00\x10\xa5\xb8', 'isEncrypted': False, 'modelFriendlyName': 'Lock Ultra', 'modelName': <SwitchbotModel.LOCK_ULTRA: 'Lock Ultra'>}
2026-01-26 11:37:00.117 DEBUG (MainThread) [switchbot.devices.smart_thermostat_radiator] Setting temperature 20.0°C in mode comfort → cmd=570F7803C8
2026-01-26 11:37:00.118 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Initializing encryption
2026-01-26 11:37:00.118 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Connecting; RSSI: -87
2026-01-26 11:37:00.118 INFO (MainThread) [habluetooth.wrappers] B0:E9:FE:AB:32:79 - None: Found 1 connection path(s), preferred order: esp32s3-ble-proxy-fcae (98:A3:16:23:FC:AE) (RSSI=-84) (failures=0) (in_progress=0) (slots=3/3 free) (score=-84.0)
2026-01-26 11:37:12.384 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Connected; RSSI: -87
2026-01-26 11:37:12.385 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Starting notify and disconnect timer; RSSI: -87
2026-01-26 11:37:12.385 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Subscribe to notifications; RSSI: -87
2026-01-26 11:37:12.417 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Sending command: 570000000f2103e4
2026-01-26 11:37:12.621 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Notification received: 0100000046a6017077fcc0e3fd4ff5b177f74a44
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Encryption init response: 0100000046a6017077fcc0e3fd4ff5b177f74a44
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Detected encryption mode: CTR
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Encryption initialized successfully
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Scheduling command 57e446a6e3c7722d
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Already connected before obtaining lock, resetting timer; RSSI: -87
2026-01-26 11:37:12.622 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Sending command: 57e446a6e3c7722d
2026-01-26 11:37:12.782 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Notification received: 01e446a6
2026-01-26 11:37:12.783 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Scheduling command 57e446a6ee
2026-01-26 11:37:12.783 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Already connected before obtaining lock, resetting timer; RSSI: -87
2026-01-26 11:37:12.783 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Sending command: 57e446a6ee
2026-01-26 11:37:12.888 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Notification received: 01e446a6acb44fc146fba5aefa24d3eda1f14d4c
2026-01-26 11:37:12.889 DEBUG (MainThread) [switchbot.devices.smart_thermostat_radiator] data: bytearray(b'\x01@\x0b>$\x81\x0e\x00\xc8\xc8\xa0<T\x00\x968\x00')
2026-01-26 11:37:12.889 DEBUG (MainThread) [switchbot.devices.smart_thermostat_radiator] Smart Thermostat Radiator basic info: {'battery': 64, 'firmware': 1.1, 'hardware': 62, 'last_mode': 'comfort', 'mode': 'comfort', 'temperature': 27.0, 'manual_target_temp': 20.0, 'comfort_target_temp': 20.0, 'economic_target_temp': 16.0, 'fast_heat_time': 60, 'child_lock': False, 'target_temp': 15.0, 'door_open': False}
2026-01-26 11:37:12.889 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Fire callbacks
2026-01-26 11:37:21.283 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Executing timed disconnect after timeout of 8.5
2026-01-26 11:37:21.283 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Executing disconnect with lock
2026-01-26 11:37:21.283 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Disconnecting
2026-01-26 11:37:21.336 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Disconnected from device; RSSI: -87
2026-01-26 11:37:21.337 DEBUG (MainThread) [switchbot.devices.device] None (B0:E9:FE:AB:32:79): Disconnect completed successfully

@bdraco All works well

fankai777 pushed a commit to fankai777/pySwitchbot that referenced this pull request Jan 26, 2026
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants