11.13. Сопряжение и привязка

Всё, что было рассмотрено до этого момента, передаёт байты по радиоканалу в открытом виде. Любой человек с ноутбуком, поддерживающим BLE, находящийся в той же комнате, может слушать каналы оповещения, отслеживать последовательность скачков открытого соединения и считывать каждую операцию чтения, записи и каждое уведомление, которые проходят по каналу. Для большинства общедоступных данных датчиков (уровень заряда батареи, температура окружающей среды) это нормально. Для всего, что обе конечные точки хотят сохранить в тайне – управляющий регистр, активирующий реле, пароль, измерение, которое не следует широко транслировать – канал должен быть зашифрован, и в идеале камера должна знать, с кем она разговаривает.

BLE обеспечивает и то, и другое через сопряжение и привязку.

11.13.1. Сопряжение, привязка, шифрование

Три тесно связанных понятия:

  • Шифрование – это конечная цель. Как только канал зашифрован, каждый пакет на каналах данных может быть расшифрован только двумя конечными точками; подслушивающий видит шум.

  • Сопряжение – это процедура, которую выполняют две конечные точки, чтобы договориться о ключах, используемых шифрованием. Это однократный обмен, который создаёт общий ключевой материал, подключаемый канальным уровнем к своему механизму шифрования.

  • Привязка – это решение сохранить ключи в энергонезависимом хранилище после завершения сопряжения, чтобы следующее соединение между теми же двумя устройствами пропускало сопряжение и сразу переходило к шифрованию.

Простыми словами: сопряжение – это «представьтесь друг другу»; привязка – это «запомните это знакомство»; шифрование – это «с этого момента говорите наедине».

Two columns labelled "Central" and "Peripheral". A dashed line near the top labelled "BLE connection open (unencrypted)". Below it, three arrows: "pairing request" from central to peripheral, "key exchange" both directions, "pairing complete" forward. A second dashed line below labelled "link encrypted". Two thick bidirectional arrows carry "encrypted GATT traffic". An optional "store keys to flash" box on the side, labelled "bonding".

Поток сопряжения поверх открытого BLE-соединения. Как только обмен ключами завершается, канальный уровень шифрует каждый последующий пакет. Привязка – это дополнительный шаг записи ключей во флеш-память.

11.13.2. LE Secure Connections

Современный обмен ключами, используемый в BLE, – это LE Secure Connections, построенный на алгоритме Диффи-Хеллмана на эллиптических кривых. Обе стороны генерируют временную пару ключей, обмениваются открытыми половинами и объединяют результат со своими закрытыми ключами, чтобы получить один и тот же общий секрет – секрет, который подслушивающий не сможет вычислить даже при наличии полной записи обмена.

Более старый метод LE Legacy менее безопасен (подслушивающий, имеющий полную запись обмена, обычно может восстановить ключ) и существует только для обратной совместимости со старыми периферийными устройствами. По умолчанию в aioble используется современный метод (le_secure=True); оставьте его.

11.13.3. Инициирование сопряжения

Центральное устройство выполняет сопряжение, вызывая aioble.DeviceConnection.pair() на уже открытом соединении:

async with await device.connect() as connection:
    await connection.pair(bond=True, le_secure=True, mitm=False)
    # ... GATT work, now over an encrypted link ...

После возврата pair атрибуты encrypted, authenticated, bonded и key_size соединения отражают то, что было согласовано.

Четыре наиболее полезных именованных аргумента:

  • bond=True – сохранить полученные ключи во флеш-память, чтобы следующее соединение между теми же двумя устройствами пропускало рукопожатие сопряжения. По умолчанию True.

  • le_secure=True – использовать LE Secure Connections. По умолчанию True. Оставьте включённым.

  • mitm=False – требовать ли защиту от атаки посредника (man-in-the-middle). Это требует внеполосного канала (числовой код, отображаемый на одной стороне и подтверждаемый на другой, вводимый пароль доступа, …), чтобы пользователь мог убедиться, что два устройства в рукопожатии сопряжения действительно те, за кого он их принимает. По умолчанию False (без защиты от MITM – пассивный подслушивающий не может прочитать канал, но злоумышленник, активно перенаправляющий соединения, мог бы вклиниться в сопряжение). Установите True для всего конфиденциального, но учтите, что это требует, чтобы периферийное устройство действительно поддерживало возможность ввода-вывода.

  • io=3 – возможность ввода-вывода, заявляемая устройством. Спецификация Bluetooth определяет пять: 0 только дисплей, 1 дисплей + да/нет, 2 только клавиатура, 3 без ввода и вывода, 4 клавиатура + дисплей. Камера без пользовательского интерфейса обычно сообщает 3; если у самой камеры есть дисплей, приложение могло бы отображать числовое подтверждение и использовать 1. Сочетание возможностей ввода-вывода двух сторон определяет, достижима ли настоящая защита от MITM.

Периферийные устройства не вызывают pair сами – они отвечают на то, что инициирует центральное устройство. Требуется ли шифрование для данной характеристики – это свойство того, как она объявлена в базе данных GATT; биты доступа с требованием шифрования являются частью низкоуровневого API bluetooth и в настоящее время не предоставляются через конструктор характеристики aioble.

11.13.4. Привязка – и где хранятся ключи

Когда bond=True, aioble записывает ключи в JSON-файл в локальной файловой системе. Имя файла по умолчанию – ble_secrets.json, записываемый относительно текущего рабочего каталога. На только что загруженной камере _boot.py уже выбрал этот каталог: /sdcard, когда карта смонтирована, в противном случае /flash – так что файл оказывается по пути /sdcard/ble_secrets.json или /flash/ble_secrets.json. Файл содержит записи, необходимые для повторного шифрования канала при следующем переподключении привязанного устройства, включая идентификационный адрес устройства.

Одну асимметрию стоит держать в уме: сохранение происходит автоматически по мере изменения ключей, но загрузка файла при следующей загрузке – нет. Вызовите aioble.security.load_secrets() один раз при запуске (до любого сопряжения или оповещения), чтобы ранее привязанные устройства были распознаны:

import aioble
aioble.security.load_secrets()        # default path: ble_secrets.json

После этого, когда привязанное устройство появится в следующий раз, aioble повторно использует сохранённые ключи, и канал станет зашифрованным без дальнейшего рукопожатия.

Два практических следствия хранения ключей во флеш-памяти:

  • Забыть устройство. Удалите ble_secrets.json (или удалите соответствующую запись), чтобы забыть все привязанные устройства, а затем выполните сопряжение заново.

  • Физический доступ раскрывает ключи. Любой, кто имеет доступ к файловой системе камеры, может прочитать JSON. Это то же самое ограничение, что возникло на сетевой стороне с ключами TLS (Эксплуатация: ключи, истечение срока и устранение неполадок): используйте отдельные ключи для каждого устройства, считайте любой сохранённый ключ восстанавливаемым и полагайтесь на возможность отзыва (здесь – удаление привязки на стороне центрального устройства), а не на то, что ключ останется секретным.

11.13.5. Что гарантирует шифрование – и чего не гарантирует

Канал «сначала сопряжение, потом шифрование» даёт, в порядке убывания надёжности:

  • Конфиденциальность. Всегда. Подслушивающий не может прочитать байты.

  • Целостность. Всегда. Изменённые пакеты не проходят проверку аутентифицированного шифрования канального уровня и отбрасываются.

  • Аутентификация. Только при mitm=True и подходящем вводе-выводе. Без неё посредник, перехвативший исходный обмен сопряжения, мог бы вклиниться; без защиты от MITM у двух сторон нет способа об этом узнать.

Для большинства сценариев использования камеры – телефон один раз выполняет сопряжение с камерой, а затем подключается снова позже – mitm=False обычно достаточно, потому что исходное сопряжение происходит в контролируемой среде (пользователь держит оба устройства в одной комнате). Для приложений, где сопряжённое устройство может впервые встретить камеру на большом расстоянии или через недоверенного посредника, MITM – правильная настройка.

11.13.6. Когда сопряжение – неверный ответ

Сопряжение имеет реальную цену: несколько секунд обмена при первом подключении, постоянное использование флеш-памяти для каждого привязанного устройства и необходимость «забыть привязку», если что-то пойдёт не так. Для действительно общедоступных данных – показаний датчика окружающей среды, публикуемых в виде маяка, вывески, отображающей своё имя, всего, что не меняет мир от того, что его прочитали или записали – правильный ответ не шифровать вообще и позволить любому ближайшему сканеру читать значения.

Для всего остального connection.pair(bond=True) на центральном устройстве – это однострочное дополнение, превращающее канал из публичного в частный.