YubiKey стоит как небольшой ужин в ресторане. Raspberry Pi Pico — как стакан кофе. А функция та же: подписывает WebAuthn challenge, хранит passkey и не даёт фишингу шанса. Разница только в том, что внутри Yubico у вас закрытая прошивка и доверие к вендору, а у Pico — открытый код, который можно прочитать, собрать и при желании поправить.
Покажу как это сделать за вечер.
Что понадобится
- Raspberry Pi Pico (RP2040) или Pico 2 (RP2350) — берите оригинал, не клон, на клонах попадаются битые USB-контроллеры
- USB-кабель для прошивки и работы
- Пять минут терпения
Опционально — кнопка и корпус, чтобы выглядело как ключ, а не как голая плата с гвоздями. На AliExpress есть готовые комплекты “под Pico FIDO” за пару долларов.
Прошивка
Идём в polhenarejos/pico-fido releases и качаем последний pico_fido_pico-X.Y.uf2 (для RP2040) или pico_fido_pico2-X.Y.uf2 (для RP2350).
Дальше классический Pico-флоу:
- Зажимаем кнопку BOOTSEL на плате
- Втыкаем USB в компьютер, кнопку держим
- В Finder/Проводнике появляется диск RPI-RP2
- Перетаскиваем UF2-файл на этот диск
- Pico перезагружается, в системе появляется HID-устройство
Pico Key
Всё, токен живой.
Первая настройка — задать PIN
Без PIN ключ работает только как старый U2F-токен (классическая 2FA-кнопка). Passkey’ы сохранять не сможет — это требование протокола CTAP2.
Проще всего задать PIN через Chrome:
chrome://settings/securityKeys
Жмём Create a PIN, вводим минимум 4 символа, тапаем по плате (это и есть подтверждение присутствия пользователя). Готово.
То же можно сделать из терминала через python-fido2:
pip install fido2
python3 -c "
from fido2.hid import CtapHidDevice
from fido2.client import ClientPin
from fido2.ctap2 import Ctap2
import getpass
dev = next(iter(CtapHidDevice.list_devices()))
ClientPin(Ctap2(dev)).set_pin(getpass.getpass('New PIN: '))
"
Регистрация в сервисах
GitHub, Google, Cloudflare, AWS, 1Password — везде один и тот же путь:
- Заходим в настройки безопасности аккаунта
- Ищем Security keys или Passkeys
- Жмём Add new
- Браузер просит ввести PIN ключа и тапнуть кнопку
- Через секунду сервис привязан
Важный нюанс — на GitHub есть два разных раздела:
- Two-factor authentication → Security keys — это классический 2FA. Ключ ничего не хранит, сервер сам помнит
credentialId. Подходит когда вам нужен второй фактор поверх пароля. - Passkeys → Add a passkey — это passkey. Креденшл сохраняется НА токене, вход без пароля. Требует PIN.
Не путайте — раздел разный, поведение разное.
Подводный камень с macOS
Тут я наступил на грабли и потратил вечер на дебаг, так что делюсь.
На стоковой прошивке pico-fido v7.6 + SDK 8.6 есть неприятный баг: обработчик CTAPHID_INIT на первом вызове запускает тяжёлую инициализацию (генерация ключей при первом запуске или инкремент OTP-счётчика), которая заканчивается записью во flash. А запись во flash на RP2040 требует отключения всех прерываний на 50-150 мс — за это время macOS успевает решить, что USB-pipe стух, и больше команды на токен не шлёт. До физического передёргивания кабеля.
Проявляется так: Chrome, Safari и Firefox дружно показывают “ждём ключ”, при том что в ioreg устройство видно, а Python через hidapi честно получает ответ на ПЕРВЫЙ CTAPHID_INIT — а на второй уже таймаут.
Фикс — однострочный, перенести init_fido() из обработчика CTAPHID_INIT в picokey_init(), который вызывается на boot ДО старта USB. Я уже отправил PR в апстрим, но пока не смерджили — если столкнётесь, собирайте с моего форка или применяйте патч руками.
Проверить что у вас не этот баг можно так:
python3 -c "
import hid, os, struct
t = next(d for d in hid.enumerate() if d['vendor_id']==0x2E8A and d.get('usage_page')==0xF1D0)
dev = hid.device(); dev.open_path(t['path'])
for i in range(5):
nonce = os.urandom(8)
dev.write(bytes([0])+struct.pack('>IBBB',0xFFFFFFFF,0x86,0,8)+nonce+b'\x00'*47)
r = dev.read(64, timeout_ms=2000)
print(f'INIT {i+1}: {\"OK\" if r else \"FAIL\"}')
"
Все пять должны пройти. Если падает на втором — у вас тот самый баг.
Итог
За цену чашки кофе и час времени у вас на руках полноценный FIDO2-аутентификатор: passkey’и, U2F, OTP, всё открытое, всё проверяемое. Не для параноиков с золотыми наручными — а для нормальной личной гигиены, чтобы не зависеть от вендора и не платить $50 за чёрный пластиковый брелок.
Один минус — DIY-токен не получит сертификацию FIDO Alliance, поэтому строго регулируемые сервисы (некоторые банки, госконторы) его могут не принять. Для всего остального — пользуйтесь на здоровье.
Фото на обложке: Michael H. (Laserlicht) / Wikimedia Commons, CC BY-SA 4.0
