Skip to content

Fix retrieve ltk key with ediv and rand instead of role#890

Open
yannpoupon wants to merge 1 commit intogoogle:mainfrom
yannpoupon:fix-wrong-ltk-key-retrieved
Open

Fix retrieve ltk key with ediv and rand instead of role#890
yannpoupon wants to merge 1 commit intogoogle:mainfrom
yannpoupon:fix-wrong-ltk-key-retrieved

Conversation

@yannpoupon
Copy link

@yannpoupon yannpoupon commented Feb 13, 2026

Fix: Incorrect LTK Retrieved on Reconnection Causing MIC Failures

Context

In my test I simulate a peripheral device that use legacy OOB :
image

And try to do a connection, disconnection and then a reconnection with a Central.
The first connection works without any issue, but the second connection fails with some MIC failure 100% of my test.

Problem

When a previously paired device reconnects and the controller requests the Long Term Key (LTK) via HCI_LE_Long_Term_Key_Request_Event, Bumble was returning the wrong LTK key, resulting in connection termination with error code 0x3D (Connection Terminated Due To MIC Failure).

Root Cause

The bug occurred due to a mismatch between storage logic (based on pairing role) and retrieval logic (based on connection role):

Storage (smp.py:1376-1381):

  • Keys are stored based on pairing initiator role (is_initiator)
  • When device is pairing responder: ltk_central = our_ltk, ltk_peripheral = peer_ltk

Retrieval (device.py - before fix):

  • Keys were retrieved based on connection role (connection.role)
  • When device is connection peripheral: fetched ltk_peripheral → returned peer's LTK

The Issue:
In normal/expected scenarios where the central initiates pairing:

  • Device acting as peripheral (connection role) is also the responder (pairing role)
  • Storage puts our LTK in ltk_central field
  • Retrieval tries to fetch from ltk_peripheral field
  • Result: Peer's LTK returned instead of our own LTK

Documentation

The Peripheral may store the mapping between EDIV, Rand and LTK in a security database so the correct LTK value is used when the Central requests encryption.

Solution

Match LTKs using their cryptographic identifiers (EDIV + Rand) instead of relying on role-based field names:
I am not sure if the change is only valid for the legacy version or not.

# Check both ltk_central and ltk_peripheral by matching EDIV+Rand
if keys.ltk_central and keys.ltk_central.ediv == ediv and keys.ltk_central.rand == rand:
    return keys.ltk_central.value
if keys.ltk_peripheral and keys.ltk_peripheral.ediv == ediv and keys.ltk_peripheral.rand == rand:
    return keys.ltk_peripheral.value

@barbibulle
Copy link
Collaborator

You're right, there's a bug here. I'll need to double check how the non-legacy pairing modes may be also affected, and follow up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants