11.13. Сопряжение и привязка¶
Всё, что было рассмотрено до этого момента, передаёт байты по радиоканалу в открытом виде. Любой человек с ноутбуком, поддерживающим BLE, находящийся в той же комнате, может слушать каналы оповещения, отслеживать последовательность скачков открытого соединения и считывать каждую операцию чтения, записи и каждое уведомление, которые проходят по каналу. Для большинства общедоступных данных датчиков (уровень заряда батареи, температура окружающей среды) это нормально. Для всего, что обе конечные точки хотят сохранить в тайне – управляющий регистр, активирующий реле, пароль, измерение, которое не следует широко транслировать – канал должен быть зашифрован, и в идеале камера должна знать, с кем она разговаривает.
BLE обеспечивает и то, и другое через сопряжение и привязку.
11.13.1. Сопряжение, привязка, шифрование¶
Три тесно связанных понятия:
Шифрование – это конечная цель. Как только канал зашифрован, каждый пакет на каналах данных может быть расшифрован только двумя конечными точками; подслушивающий видит шум.
Сопряжение – это процедура, которую выполняют две конечные точки, чтобы договориться о ключах, используемых шифрованием. Это однократный обмен, который создаёт общий ключевой материал, подключаемый канальным уровнем к своему механизму шифрования.
Привязка – это решение сохранить ключи в энергонезависимом хранилище после завершения сопряжения, чтобы следующее соединение между теми же двумя устройствами пропускало сопряжение и сразу переходило к шифрованию.
Простыми словами: сопряжение – это «представьтесь друг другу»; привязка – это «запомните это знакомство»; шифрование – это «с этого момента говорите наедине».
Поток сопряжения поверх открытого 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) на центральном устройстве – это однострочное дополнение, превращающее канал из публичного в частный.