diff --git a/src/bthome_ble/parser.py b/src/bthome_ble/parser.py index a88f496..9c00ed8 100644 --- a/src/bthome_ble/parser.py +++ b/src/bthome_ble/parser.py @@ -156,6 +156,10 @@ def __init__(self, bindkey: bytes | None = None) -> None: # or encryption is not in use self.bindkey_verified = False + # If True then the decryption has failed or has not been verified yet. + # If False then the decryption has succeeded. + self.decryption_failed = True + # If this is True, then we have not seen an advertisement with a payload # Until we see a payload, we can't tell if this device is encrypted or not self.pending = True @@ -701,7 +705,11 @@ def _decrypt_bthome( nonce, encrypted_payload + mic, associated_data ) except InvalidTag as error: - self.bindkey_verified = False + if self.decryption_failed is True: + # we only ask for reautentification after the decryption has failed twice. + self.bindkey_verified = False + else: + self.decryption_failed = True _LOGGER.warning("%s: Decryption failed: %s", self.title, error) _LOGGER.debug("%s: mic: %s", self.title, mic.hex()) _LOGGER.debug("%s: nonce: %s", self.title, nonce.hex()) @@ -717,6 +725,7 @@ def _decrypt_bthome( to_mac(bthome_mac), ) raise ValueError + self.decryption_failed = False self.bindkey_verified = True return decrypted_payload diff --git a/tests/test_parser_v2.py b/tests/test_parser_v2.py index c0299e2..b736f69 100644 --- a/tests/test_parser_v2.py +++ b/tests/test_parser_v2.py @@ -246,6 +246,7 @@ def test_bindkey_wrong(): device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert not device.bindkey_verified + assert device.decryption_failed assert device.update(advertisement) == SensorUpdate( title="Test Sensor 18B2", devices={ @@ -285,6 +286,7 @@ def test_bindkey_correct(): device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert device.bindkey_verified + assert not device.decryption_failed assert device.update(advertisement) == SensorUpdate( title="TEST DEVICE 80A5", devices={ @@ -342,6 +344,7 @@ def test_incorrect_bindkey_length(caplog): device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) assert device.supported(advertisement) assert not device.bindkey_verified + assert device.decryption_failed assert ( "TEST DEVICE 80A5: Encryption key should be 16 bytes (32 characters) long" in caplog.text @@ -416,8 +419,22 @@ def test_bindkey_verified_can_be_unset(): device = BTHomeBluetoothDeviceData(bindkey=bytes.fromhex(bindkey)) device.bindkey_verified = True + device.decryption_failed = False assert device.supported(advertisement) + # the first advertisement will fail decryption, but we don't ask to reauth yet + assert device.bindkey_verified + assert device.decryption_failed + + data_string = b"\x41\xa4\x72\x66\xc9\x5f\x73\x01\x11\x22\x33\x78\x23\x72\x14" + advertisement = bytes_to_service_info( + data_string, + local_name="ATC_8D18B2", + address="A4:C1:38:8D:18:B2", + ) + assert device.supported(advertisement) + # the second advertisement will fail decryption again, but now we ask to reauth + assert device.decryption_failed assert not device.bindkey_verified