Мышка и наушники, накануне работавшие безупречно, после перезагрузки из Linux в Windows отказываются подключаться, а при возврате обратно история повторяется в зеркальном отражении. Виноват не адаптер и не драйверы: пара Bluetooth держится на ключах шифрования, и у каждой системы оказываются свои. Устройство видит от знакомого адреса незнакомые данные и обрывает канал, поэтому владельцу остаётся заново запускать сопряжение. Хорошая новость в том, что конфликт лечится за один вечер: значения из хранилища Windows переносятся в конфигурацию BlueZ, после чего обе системы держат одинаковые ключи, и перезагрузки перестают трогать связь.
Причина в ключах шифрования, а не в адаптере
При сопряжении адаптер и устройство договариваются о секретном ключе и записывают его каждый у себя. В классическом Bluetooth это link key длиной 16 байт, в версии с Low Energy набор шире: LTK для шифрования, IRK для разрешения приватных адресов, значения EDIV и ERand для восстановления шифрованной сессии. Ключ существует ровно у двух участников пары, никаких серверов с копиями здесь не предусмотрено, потому схема и зовётся сопряжением, а не просто подключением.
Радиомодуль в ноутбуке один, и его MAC-адрес одинаков в обеих операционных системах. Наушники держат для такого адреса ровно одну запись в своей таблице привязок. У таблицы есть и предел: многие наушники хранят ограниченное число пар, и новая запись вытесняет самую старую, поэтому после очередной перезаписи внезапно отваливается связь с телефоном. Windows при каждом сопряжении генерирует свежий ключ и затирает старый прямо на устройстве, а Linux после этого остаётся с устаревшей копией. При попытке подключения значения не совпадают, защищённый канал не поднимается, и наушники выглядят сломанными, хотя железо исправно.
Выход один: сделать так, чтобы обе системы хранили тот же ключ, которым устройство пользуется прямо сейчас. Windows записывает ключи в реестр, в ветку SYSTEM\CurrentControlSet\Services\BTHPORT\Parameters\Keys, куда допущена лишь учётная запись SYSTEM. BlueZ хранит те же данные текстом в каталоге /var/lib/bluetooth. Задача сводится к переносу значений из одного хранилища в другое, а читать реестр Windows офлайн удобно утилитой chntpw.
Подготовка систем и правильный порядок сопряжения
Порядок здесь важнее, чем кажется. Свежая пара побеждает: последний ключ, попавший на устройство, становится действующим. Если сопрячь устройство сначала в Windows, а потом в Linux, переносить значения придётся в обратную сторону, то есть дописывать чужой реестр офлайн, а это заметно рискованнее, чем поправить текстовый файл у себя. Поэтому сперва пара ставится в Linux, затем пользователь перезагружается в Windows и сопрягает устройство там, убеждаясь, что и мышь, и звук работают.
Дальше устройство выключают, чтобы оно не пыталось переподключиться в самый неподходящий момент. Потом неплохо заглянуть в настройки электропитания Windows: Панель управления, раздел Электропитание, пункт Действия кнопок питания, и снять отметку "Включить быстрый запуск". С быстрым запуском Windows фактически не выключается, файловая система остаётся помеченной как занятая, и монтировать раздел из Linux безопаснее только для чтения, а порой он и вовсе не поддаётся.
Перед правками не помешает резервная копия хранилища BlueZ, делается она одной командой:
sudo cp -a /var/lib/bluetooth /var/lib/bluetooth.bak
ls /var/lib/bluetooth
Вторая команда покажет каталог с MAC-адресом адаптера, внутри него лежат каталоги сопряжённых устройств с файлами info. Имена стоит записать: они понадобятся на этапе переноса. Сама работа укладывается в пять шагов:
- Сопряжение устройства с Linux и проверка связи;
- Перезагрузка в Windows и повторное сопряжение там же;
- Выключение устройства и извлечение ключей из реестра Windows;
- Перенос значений в файлы info хранилища BlueZ;
- Перезапуск службы bluetooth и контрольное подключение.
Извлечение ключей из реестра Windows утилитой chntpw
Раздел с Windows чаще всего виден в системе как /dev/nvme0n1p3 или /dev/sda3, точное имя подскажет lsblk. Раздел монтируется только для чтения, это гарантирует, что в чужую файловую систему не попадёт ни один лишний байт:
lsblk -f
sudo mkdir -p /mnt/windows
sudo mount -o ro /dev/nvme0n1p3 /mnt/windows
ls /mnt/windows/Windows/System32/config
Утилита chntpw есть в репозиториях Debian, Ubuntu и большинства дистрибутивов, пакет называется так же; в комплекте рядом лежит консольный редактор reged, но для разовой задачи хватает интерактивного режима. Если системный раздел зашифрован BitLocker, файл hive без ключа восстановления не прочитается, и путь начнётся с расшифровки.
Имена ключей заранее сверяются со стороны Linux: адрес адаптера покажет команда bluetoothctl list, а адрес нужного устройства появится в выводе bluetoothctl devices после включения мыши. Такая сверка избавляет от перебора каталогов вслепую, если сопряжённых гаджетов за месяцы накопилось немало.
sudo apt install chntpw
sudo chntpw -e /mnt/windows/Windows/System32/config/SYSTEM
Ключ -e запускает интерактивный редактор реестра. Навигация напоминает файловую, только разделителем служит обратная косая черта. CurrentControlSet в офлайн-копии отсутствует, вместо него видны наборы ControlSet001 и ControlSet002; в свежих установках активен первый:
> cd ControlSet001\Services\BTHPORT\Parameters\Keys
> ls
Node has 1 subkeys and 0 values
key name
<0c3461d2b04e>
> cd 0c3461d2b04e
Под ключом адаптера прячутся записи сопряжённых устройств, названные их MAC-адресами без двоеточий. Дальше развилка. Если устройство классическое, например большинство мышей, ls покажет одно значение размером 16 байт, названное адресом мыши:
> ls
Node has 0 subkeys and 1 values
size type value name
16 3 REG_BINARY <5ceb68aabbcc>
> hex 5ceb68aabbcc
:00000 8A 2B 4C 5D 6E 7F 0A 1B 2C 3D 4E 5F 60 71 82 93
Устройства с Low Energy, к которым относятся современные наушники и игровые контроллеры, хранят данные иначе: под тем же адресом открывается подкаталог с целым набором значений:
> ls
Node has 1 subkeys and 0 values
key name
<5ceb68aabbcc>
> cd 5ceb68aabbcc
> ls
Node has 0 subkeys and 8 values
size type value name [value if type DWORD]
16 3 REG_BINARY <LTK>
4 4 REG_DWORD <KeyLength> 16 [0x10]
8 b REG_QWORD <ERand>
4 4 REG_DWORD <EDIV> 37520 [0x9290]
16 3 REG_BINARY <IRK>
8 b REG_QWORD <Address>
4 4 REG_DWORD <AddressType>
> hex LTK
> hex ERand
> hex EDIV
> hex IRK
Команда hex печатает дамп байт, а строка с типом DWORD сразу даёт готовое десятичное число, его удобно переписать как есть. Выход из редактора по q; на вопрос о записи изменений в файл отвечает n, чтобы реестр Windows остался нетронутым.
Если загрузка в Linux по каким-то причинам неудобна, ключи снимают из самой Windows. Встроенный regedit без прав SYSTEM значения не покажет, поэтому редактор запускают от имени SYSTEM через утилиту PsExec, открывают ту же ветку Keys и экспортируют её в файл .reg, откуда цифры переписываются в заметки. Путь длиннее, а данных в итоге не больше, так что способ с chntpw остаётся основным.
Преобразование значений для конфигурации BlueZ
Форматы у реестра и BlueZ разные, и именно на этом шаге чаще всего теряются. С байтовыми ключами всё просто: LTK и IRK переписываются в верхнем регистре без пробелов, порядок байт сохраняется. Дамп из примера выше превращается в такую строку:
484DAFCD0F9222880A529AF476DA8B94
С ERand хитрее: реестр держит 8 байт от младшего к старшему, а BlueZ ждёт Rand в виде одного десятичного числа. Байты разворачиваются и переводятся одной командой:
python3 -c "print(int(''.join(reversed('63 02 84 B8 5D 40 44 DF'.split())), 16))"
16088054540146049635
Внутрь кавычек подставляются свои байты из дампа ERand, разделённые пробелами. Для EDIV, если число подсмотрено не в строке DWORD, а в дампе из двух байт, пригодится та же команда с парой байт на входе. Соответствие полей получается прямое: LTK идёт в Key секции LongTermKey, IRK в Key секции IdentityResolvingKey, ERand в Rand, EDIV в EDiv, KeyLength со значением 16 превращается в EncSize. Поле KeyLength на современных гаджетах всегда равно 16, меньшие числа встречаются разве что на совсем старых моделях. Все значения из примера складываются в готовый набор: Key=484DAFCD0F9222880A529AF476DA8B94, EDiv=37520, Rand=16088054540146049635. Изобретать ничего не требуется, нужна лишь аккуратность: одна потерянная пара символов, и наушники снова покажут характер.
Перенос ключей в файлы info и перезапуск службы bluetooth
Хранилище BlueZ лежит в /var/lib/bluetooth. Каталог верхнего уровня назван MAC-адресом адаптера, внутри него каталоги устройств, а в каждом файл info со всей сопроводительной информацией. Файлы принадлежат root, поэтому редактор запускается через sudo, а после сохранения права и владелец остаются прежними. Правка проходит спокойнее при остановленной службе, чтобы bluetoothd не перезаписал файл в процессе:
sudo systemctl stop bluetooth
sudo nano /var/lib/bluetooth/0C:34:61:D2:B0:4E/5C:EB:68:AA:BB:CC/info
Секции, которых не хватает, дописываются в конец файла, существующие поля Name, Class и Services трогать не нужно. Для Low Energy устройства файл после правки выглядит примерно так:
[LongTermKey]
Key=484DAFCD0F9222880A529AF476DA8B94
Authenticated=0
EncSize=16
EDiv=37520
Rand=16088054540146049635
[IdentityResolvingKey]
Key=7E1A2B3C4D5E6F70819A2B3C4D5E6F70
Значение Authenticated оставляется прежним, а EncSize повторяет KeyLength из реестра; строка Trusted=true в секции General разрешает устройству подключаться самому. Для классического устройства достаточно обновить одну строку в секции LinkKey:
[LinkKey]
Key=8A2B4C5D6E7F0A1B2C3D4E5F60718293
Type=4
PIN=0
Поля Type и PIN тоже сохраняются, меняется лишь Key. Дальше служба запускается заново и начинается проверка:
sudo systemctl restart bluetooth
bluetoothctl connect 5C:EB:68:AA:BB:CC
Строка Connection successful означает, что устройство приняло знакомый ключ и защищённый канал поднялся. Если менеджер Bluetooth артачится, помогает полная перезагрузка: некоторые оболочки держат кэш состояния службы и не замечают подмену ключей на лету. Через графический апплет подключение делается тем же путём: значок Bluetooth в трее, пункт Устройства, выбор нужного, кнопка подключения.
Типичные ошибки и обслуживание пары в дальнейшем
Список промахов у процедуры короткий, но каждый стоит времени. Строчные буквы в ключах BlueZ не признаёт, весь hex приводится к верхнему регистру. Разворот байт ERand забывается чаще всего, и связь не поднимается при идеально скопированном LTK. Отдельная засада подстерегает владельцев некоторых мышей Logitech, например MX Master: при каждом новом сопряжении такие устройства сами увеличивают последний байт своего адреса, поэтому каталогу устройства в /var/lib/bluetooth может понадобиться переименование под адрес из реестра Windows, каждый октет при этом остаётся двузначным шестнадцатеричным числом.
Пара живёт долго, но не вечно. Любое повторное сопряжение в Windows генерирует новые ключи, и вся процедура переноса повторяется заново, целиком. По этой причине устройство из списков обеих систем после успеха не удаляется, а пункт "Удалить устройство" в Windows обходится стороной. Крупное обновление Windows или сброс адаптера приводят к тому же результату. Кто обновляет связку регулярно, может присмотреться к сценарию bt-dualboot: он переносит ключи автоматически, хотя с Low Energy устройствами пока не справляется.
Запись значений в обратную сторону, из Linux в реестр Windows, технически возможна, но на практике почти не нужна: офлайн-правка hive чревата рассинхроном наборов ControlSet, и чинить придётся загрузку, а не мышь. Текстовый файл info виден, читается и откатывается обычным копированием из резервной копии, так что надёжная схема ровно одна: Linux первым, Windows вторым, ключи в Linux. Между двумя установками Linux задача решается ещё проще: содержимое каталога /var/lib/bluetooth просто копируется или связывается символической ссылкой.
После переноса перезагрузка между системами проходит без единого клика по настройкам: наушники подключаются сами, мышь оживает через секунду после включения. Времени вся процедура стоит один раз, а экономит при каждой перезагрузке.