src.bluetooth_sig.advertising.ead_decryptor

Pure decryption functions for BLE Encrypted Advertising Data (EAD).

This module provides framework-agnostic decryption functions for EAD per Bluetooth Core Spec Supplement Section 1.23.

The decryption uses AES-CCM with: - 128-bit (16-byte) session key - 13-byte nonce (5-byte randomizer + 6-byte device address + 2-byte padding) - 4-byte MIC (authentication tag)

Requires the ‘cryptography’ package: pip install bluetooth-sig[ead]

Attributes

Classes

Name

Description

EADDecryptor

Stateful EAD decryptor with cipher caching and key provider support.

Functions

Name

Description

build_ead_nonce(→ bytes)

Build the 13-byte nonce for EAD decryption.

decrypt_ead(→ bluetooth_sig.types.ead.EADDecryptResult)

Decrypt BLE Encrypted Advertising Data.

decrypt_ead_from_raw(...)

Convenience function to decrypt raw EAD bytes.

Module Contents

class src.bluetooth_sig.advertising.ead_decryptor.EADDecryptor(*, _key_provider: bluetooth_sig.advertising.encryption.EADKeyProvider | None = None, _static_key: bytes | None = None)

Stateful EAD decryptor with cipher caching and key provider support.

For one-off decryption, use the module-level decrypt_ead() function. For repeated decryption with the same key, this class caches the AESCCM cipher instance for better performance.

This class also integrates with EADKeyProvider for automatic key lookup by MAC address.

Use the factory methods from_key() or from_provider() to create instances.

_key_provider

Optional key provider for MAC-based key lookup

_static_key

Static session key (used if no provider)

_cipher_cache

Cached cipher instances keyed by session key

Example with static key:
>>> from bluetooth_sig.advertising import EADDecryptor
>>>
>>> decryptor = EADDecryptor.from_key(bytes.fromhex("0123456789abcdef0123456789abcdef"))
>>> result = decryptor.decrypt(raw_ead_data, "AA:BB:CC:DD:EE:FF")
>>> if result.success:
...     print(result.plaintext)
Example with key provider:
>>> from bluetooth_sig.advertising import EADDecryptor, DictKeyProvider
>>> from bluetooth_sig.types.ead import EADKeyMaterial
>>>
>>> provider = DictKeyProvider()
>>> provider.set_ead_key(
...     "AA:BB:CC:DD:EE:FF",
...     EADKeyMaterial(
...         session_key=bytes.fromhex("0123456789abcdef0123456789abcdef"),
...         iv=bytes.fromhex("0102030405060708"),
...     ),
... )
>>> decryptor = EADDecryptor.from_provider(provider)
>>> result = decryptor.decrypt(raw_ead_data, "AA:BB:CC:DD:EE:FF")
clear_cache() None

Clear the cipher cache.

Call this if you need to free memory or if keys have been rotated.

decrypt(raw_ead_data: bytes, mac_address: str, associated_data: bytes | None = None) bluetooth_sig.types.ead.EADDecryptResult

Decrypt EAD data using cached cipher.

Looks up the session key from the key provider (if configured) or uses the static key. Caches the cipher instance for reuse.

Parameters:
  • raw_ead_data – Raw EAD advertisement bytes (AD Type 0x31 payload)

  • mac_address – Device MAC address (e.g., “AA:BB:CC:DD:EE:FF”)

  • associated_data – Optional additional authenticated data (AAD)

Returns:

EADDecryptResult with success status and plaintext or error details

classmethod from_key(session_key: bytes) EADDecryptor

Create decryptor with a static session key.

Parameters:

session_key – 16-byte AES-128 session key

Returns:

Configured EADDecryptor instance

Raises:

ValueError – If session_key is not 16 bytes

Example

>>> decryptor = EADDecryptor.from_key(bytes.fromhex("0123456789abcdef0123456789abcdef"))
classmethod from_provider(key_provider: bluetooth_sig.advertising.encryption.EADKeyProvider) EADDecryptor

Create decryptor with a key provider for MAC-based lookup.

Parameters:

key_provider – Provider that looks up keys by MAC address

Returns:

Configured EADDecryptor instance

Example

>>> provider = DictKeyProvider()
>>> provider.set_ead_key("AA:BB:CC:DD:EE:FF", key_material)
>>> decryptor = EADDecryptor.from_provider(provider)
src.bluetooth_sig.advertising.ead_decryptor.build_ead_nonce(randomizer: bytes, device_address: bytes) bytes

Build the 13-byte nonce for EAD decryption.

The nonce is constructed as:

Randomizer[5] + DeviceAddress[6] + Padding[2]

Parameters:
  • randomizer – 5-byte randomizer from EAD advertisement

  • device_address – 6-byte BLE device address (little-endian)

Returns:

13-byte nonce for AES-CCM decryption

Raises:

ValueError – If randomizer is not 5 bytes or address is not 6 bytes

Example

>>> randomizer = bytes.fromhex("0102030405")
>>> address = bytes.fromhex("aabbccddeeff")
>>> nonce = build_ead_nonce(randomizer, address)
>>> len(nonce)
13
src.bluetooth_sig.advertising.ead_decryptor.decrypt_ead(encrypted_data: bluetooth_sig.types.ead.EncryptedAdvertisingData, session_key: bytes, device_address: bytes, associated_data: bytes | None = None) bluetooth_sig.types.ead.EADDecryptResult

Decrypt BLE Encrypted Advertising Data.

Performs AES-CCM decryption per Bluetooth Core Spec Supplement Section 1.23. This function never raises exceptions on decryption failure; instead, it returns a result with appropriate error information.

Parameters:
  • encrypted_data – Parsed EAD structure containing randomizer, encrypted payload, and MIC

  • session_key – 16-byte AES-128 session key

  • device_address – 6-byte BLE device address (little-endian bytes)

  • associated_data – Optional additional authenticated data (AAD). Must match the AAD used during encryption.

Returns:

EADDecryptResult with success status and plaintext or error details

Example

>>> ead = EncryptedAdvertisingData.from_bytes(raw_advertisement)
>>> result = decrypt_ead(ead, session_key, device_address)
>>> if result.success:
...     process_sensor_data(result.plaintext)
... elif result.error_type == EADError.INVALID_KEY:
...     logger.warning("Incorrect encryption key for device")
src.bluetooth_sig.advertising.ead_decryptor.decrypt_ead_from_raw(raw_ead_data: bytes, session_key: bytes, mac_address: str, associated_data: bytes | None = None) bluetooth_sig.types.ead.EADDecryptResult

Convenience function to decrypt raw EAD bytes.

Combines parsing and decryption in a single call for simpler usage.

Parameters:
  • raw_ead_data – Raw EAD advertisement bytes (AD Type 0x31 payload)

  • session_key – 16-byte AES-128 session key

  • mac_address – Device MAC address string (e.g., “AA:BB:CC:DD:EE:FF”)

  • associated_data – Optional additional authenticated data

Returns:

EADDecryptResult with success status and plaintext or error details

Example

>>> result = decrypt_ead_from_raw(
...     raw_ead_data=advertisement_payload,
...     session_key=bytes.fromhex("0123456789abcdef0123456789abcdef"),
...     mac_address="AA:BB:CC:DD:EE:FF",
... )
>>> if result.success:
...     print(f"Decrypted: {result.plaintext.hex()}")
src.bluetooth_sig.advertising.ead_decryptor.EAD_ADDRESS_SIZE: int = 6
src.bluetooth_sig.advertising.ead_decryptor.EAD_KEY_SIZE: int = 16
src.bluetooth_sig.advertising.ead_decryptor.EAD_MIC_SIZE: int = 4
src.bluetooth_sig.advertising.ead_decryptor.EAD_NONCE_SIZE: int = 13
src.bluetooth_sig.advertising.ead_decryptor.logger