ESPNJimmy Butler's mission to return to the Warriors: 'I felt I let them down'ESPN DeportesRayados sufre ante Pachuca y regresa a zona de LiguillaThe Jerusalem PostLebanon must refuse Hezbollah’s demand to return seized explosives - editorialInquirerSenate to extend courtesy to OVP in budget hearing한겨레중국 문샷AI ‘17살 천재 연구원’ 퇴사…미 실리콘밸리행에 쏠리는 관심ZDF heuteAktuelle Pressemitteilungen des ZDFDaily MaverickPODCAST: Johannesburg’s junior mayor race put youth mental health and civic duty centre stageThe RegisterEngineer was so sick of office pranks, he 'wiped' his antagonist's disk경향신문‘구의회 정상화 촉구 단식’ 대전 대덕구청장 병원 이송···의회는 여전히 파행 중Il Sole 24 OreNuovo Codice della strada, corsia virtuale e protezioni obbligatorie in autostrada: le novità per i motociclistiComplete SportsEUROMATCH NPFL: We Had A Bad Day – Ogunbote Speaks On Plateau United’s Home Loss To Sporting LagosRappler[EDITORIAL] Cruelty is not wit — itigil ang disabled-shaming
The Daily Newsstand · Free, Always
Monday, October 19, 2026

Шифровать нельзя доверять: как защитить биометрию вен ладони на пути от сенсора до сервера СКУД

Translate

Привет, Хабр!

Меня зовут @momentics. Я строю HW/SW команды, процессы производства, делаю реинвент технологий, починяю старые и создаю новые HW/SW продукты. Хочу поведать свою недавнюю историю. Надеюсь, вам понравится.

Некоторое время назад я случайно оказался в демозале одной компании, где мне показали её продукцию. Называть её не буду, просто «одна компания». Компания проектирует и создает решения СКУД на основе биометрии: лицо, вены ладони, отпечатки и так далее, плюс серверы и собственное программное обеспечение. Посмотрел, потрогал демо-образцы, шапочно познакомился с несколькими разработчиками. В целом прикольно. Дай им Бог здоровья и долгих лет…

Больше всех зацепил бесконтактный сканер вен ладони. Я поднес руку к сенсору. Меня в их базе нет, но цикл отработал целиком: захват изображения, обработка, выравнивание картинки, выделение характерного участка, вычисление дескриптора, сверка... и на экране появилось «пользователь не найден».

Я по привычке сразу подумал «а как бы я такое сделал»? Я инженер, и без ответа на этот вопрос мне некомфортно. Пришлось сделать домашнюю работу в полном объеме. Конечно, мне детали реализации и ноу-хау компании никто не раскрывал. Но к моменту завершения презентации я уже успел получить некоторый инсайд, что в основе лежат собственные ноу-хау алгоритмы на свёрточных нейросетях (CNN). Это просто сузило возможные варианты решений этих задач. Но в целом, ничего не добавило.

Первое желание было описать все устройство целиком: оптика, захват, проверка живости (liveness), защита от муляжей, как выполняются свёртки, как сеть выдаёт дескриптор и сопоставление. Кто в теме, тот знает - тут интересного минимум на толстую книгу. Но для статьи я выбрал одну тему, наименее освещённую но нужную всем. И ту, где чаще всего ошибаются разработчики. Я расскажу о небольшой части продукта — о доверии и защите от взлома.

Проектирование

Что будет, если терминал ночью снимут со стены и отнесут к человеку с Б-о-о-о-льшим паяльником? Если инсайдер по ту сторону корпоративной сети просто откроет базу данных? Если кто-то принесёт на проходную ИК-муляж ладони с подогревом? Или «поимеет вась-вась» с персоналом производителя?

Для определенности изложения, примем, что входом нашей схемы является готовый биометрический вектор, результат работы фото/видеомодуля и ноу-хау алгоритмов. Мы сознательно считаем его чёрным ящиком для упрощения. Аппаратная платформа терминала, встроенное ПО и центральный сервер СКУД — это собственная разработка этой компании (в статье условно считаем, что наша). Опереться на «встроенную безопасность стороннего вендора» не выйдет: все подсистемы защиты закладываются в архитектуру с нуля. В профессиональной среде этот подход называют security by design, то есть защита, заложенная ещё на этапе проектирования.

Почему безопасность это важно? Да потому что биометрия — это не пароль. Если пароль скомпрометировали, то его можно легко заменить. Сертификат - перевыпустить. Если же утек качественный шаблон вен ладони, то потребуется пластика вен! А умеют делать их, возможно, одна-две команды в мире. Но мы исходим из того, что биометрию изменить нельзя. И это критический момент всей архитектуры.

В безопасности есть один важный момент. Техника называется Zero Trust, и по-русски звучит как «никому не доверяй, всегда проверяй». Полная модель «нулевого доверия» невозможна. Как следствие, в нашем случае формулировка приобретает специфический вид: шифруем то, что можно проверить, и доверяем только тому, что нельзя заменить. Из этого следует, что обязан существовать аппаратный корень доверия. Это база доверенных вычислений (Trusted Computing Base, TCB). Вопрос лишь в том, насколько она мала. Задача инженера проектирующего безопасность — минимизировать TCB и заменить всё, что за её пределами, проверяемыми средствами: криптографией, цифровыми подписями, защитой от несанкционированного вмешательства, неизменяемым аудитом.

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

Спускаемся с небес на землю

Для подобных систем, самый важный документ — это модель угроз и нарушителей (МУиН). В российском регулировании она входит в состав более громоздкой конструкции: модели угроз безопасности информации (МУБИ) по методике ФСТЭК 2021 года. Документ подробный и скучный, а о том, как его пишут, уже сказано достаточно, поэтому углубляться не буду. Но для нас это главный документ проекта. Без него объект с персональными данными не пройдёт экспертизу, а с ним понятно, зачем нужна каждая мера защиты. Чтобы не перегружать читателя отдельным документом, я построю статью иначе. Любой наш выбор, от кода БЧХ до протокола OSDP, будет отвечать на какой-то пункт этой цепочки. МУиН будет присутствовать всюду, но не как отдельный документ.

МУиН рассматривает каждую точку пути данных (у нас это сенсор, терминал, канал, сервер и замок) с трёх позиций: что на этой точке может сделать нарушитель, чем он располагает и что ему противостоит. Так получаются различные профили нарушителей.

Нормативная база

Международные стандарты:

  • МЭК 24745:2022 определяет, что считать защищённым биометрическим шаблоном и какие у него обязательные свойства: необратимость, отменяемость, несвязываемость.

  • ISO/IEC 30136 задаёт методику измерения этих свойств; по ней проверяют, что и как защищено.

Российские нормы. Для нашей задачи достаточно четырёх документов:

  • 152-ФЗ, статья 10. Биометрические персональные данные требуют отдельного согласия субъекта. Общей галочки «обрабатываем ПДн» недостаточно, а собственная система с биометрией проходит полную оценку соответствия, то есть сертификацию или аттестацию.

  • Постановление Правительства № 1119. Определяет четыре уровня защищённости информационных систем персональных данных (ИСПДн): от УЗ-1 — самые строгие требования, для объектов, где последствия утечки критичнее всего (крупные банки, федеральные реестры), — до УЗ-4 при минимальных рисках. Уровень выводится из модели угроз и определяет набор обязательных мер. Для биометрической СКУД нашего класса это УЗ-2, для объектов повыше — УЗ-1.

  • Приказ ФСТЭК № 21. Набор технических мер, которые мы будем реализовывать прямо в коде: контроль целостности программной среды, защита от несанкционированного доступа, неизменяемое журналирование. По сути, это перевод модели угроз на язык требований к системе.

  • Приказ ФСБ № 378. Требования к сертифицированным средствам криптографической защиты информации (СКЗИ) и классы их стойкости. Применяется, когда защита строится на сертифицированных средствах. Выбор между алгоритмами шифрования разберём в разделе про транспорт.

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

  • Разрабатываем и утверждаем МУиН по методике ФСТЭК. Сюда попадают физический взлом терминала, кража кэша и подмена прошивки, то есть рабочие сценарии с конкретным нарушителем, ресурсами и целью. Всё, что в модель не попало, защищать не обязаны; что попало, обязаны.

  • Разрабатываем техническое задание на защиту. Требования направлены прямо к коду и железу: защищённая загрузка (Secure Boot), шифрование памяти, взаимно аутентифицированное TLS-соединение (mTLS) с уникальным сертификатом у каждого терминала.

  • Реализуем всё это в коде и железе: контроль целостности программной среды, системный журнал всех событий доступа и административных действий, защита от инъекций.

  • Система проходит аттестацию ИСПДн с привлечением лицензиата ФСТЭК/ФСБ, то есть испытания на проникновение и аттестат соответствия для класса УЗ-2. Именно сюда приходит внешний специалист с нормальным паяльником. Не выдержит — вернёмся на третий шаг :)

Путь данных и зоны доверия

Определяем границы зон доверия. Границ у нас три.

  1. Больше всего доверяем серверной комнате. Охраняемый периметр, журнал доступа, контроль среды — всё как принято в дата-центрах. Сервер СКУД находится в отдельном закрытом сегменте, который не пересекается ни с корпоративной сетью, ни с интернетом.

  2. Терминалы СКУД находятся в собственной LAN сети здания. Хотя корпоративная сеть и интернет сюда не входят, но до LAN может добраться физически подключенный зловред. Поэтому всё, что ходит по этому каналу, шифруется и подписывается. Как именно, разберём в разделе про транспорт.

  3. В проходной, доверия к физической среде нет вообще. У терминала может постоять любой человек с любым намерением, так что проектируем его так, будто терминал уже украден. Сначала сделаем бесполезным в чужих руках, а потом труднокрадным.

Путь данных и зоны доверия

Путь данных и зоны доверия

Из этих границ следует главное. Каждый распределённый терминал с локальным кэшем сам обрабатывает персональные данные и сам принимает решение о проходе даже тогда, когда сети нет. Атака «сервер лёг» — это не повод запереть здание, проходка продолжает работать. Но чем больше терминал делает сам, тем ценнее то, что в нём лежит. Эту поверхность атаки мы будем разбирать всю оставшуюся статью!

Среди выделенных компонентов есть единственный, которому мы доверяем без криптографии. Это сенсор (с теми самыми ноу-хау алгоритмами). В архитектуре он является открытым остаточным риском. Защитой здесь занимается проверка живости (liveness detection), которую выполняет модуль компьютерного зрения непосредственно. Именно он сдерживает силиконовый ИК-муляж с подогревом. На все остальные компоненты действует одно и то же правило: каждый получает проверяемую защиту, либо шифрование, либо подпись или аудит, либо выводится из доверенной зоны.

Нарушители и сценарии атак

  • Н1, внешний. Находится за периметром, работает через кабель и сенсор. Использует пассивный и активный перехват, инъекцию пакетов, презентационные атаки (например, электронный эквивалент ИК-изображения, муляжа ладони, имитация кровотока, предъявление электронного эквивалента чужой руки).

  • Н2, локальный физический. Может держать терминал в руках. Использует вскрытие корпуса, JTAG/UART/SWD, дамп флеш-памяти с ключами и кэшем шаблонов, подмена оптического модуля. Самый неприятный его шаг, это записать свою биометрию в локальный кэш под видом легитимного сотрудника.

  • Н3, инсайдер. Сразу два профиля. Первый — администратор СКУД, у которого есть доступ к серверу и базе. Использует компрометацию учётки (фишинг, захват сессии удалённого доступа) или вводит вредоносы в логику авторизации, например, которая всегда разрешает проход. Второй — сотрудник отдела кадров или службы безопасности, который ведёт первичную регистрацию профилей. Использует сговор и создание двойников.

Возможность автономной работы терминала, устраняет атаку отказа в обслуживании. Но это создает дополнительные сценарии атак. Это физическое извлечение кэша, его подмена или модификация, перехват при синхронизации. Отдельно — саботаж исполнительных устройств. Ведь в офлайн-режиме терминал действует сам, и канал команд открытия должен защищаться так же, как канал данных.

Сведем все в единую табличку.

Компонент / процесс

Угроза

Требуемая мера

Техническая реализация

Сенсор

ИК-изображение, муляж ладони с подогревом, чужая рука

Проверка liveness

Модуль компьютерного зрения; вне рамок статьи, остаточный риск

Хранение кэша (NVM терминала)

Сняли терминал, сделали дамп флеш-памяти или полезли через JTAG

Шифрование, неизвлекаемые ключи, кэш ограничен подмножеством своей двери

AES-256 или ГОСТ «Кузнечик»; ключ в SE; сервер подписывает записи; в кэше только вспомогательные данные горячего подмножества и билеты для своей двери

Сеть сервер - терминал

MitM при синхронизации кэша

Взаимная аутентификация, контроль целостности пакетов

mTLS, у каждого терминала свой сертификат; дельта-пакет подписан

Запуск терминала

Подмена или правка прошивки

Secure Boot

Аппаратный корень в ROM, каждая следующая ступень подписана (ECDSA); ключ подписи не хранится на терминале

Софт сервера авторизации

Вредоносная заплатка, SQL-инъекция, захват учётки админа

Контроль целостности среды, сегментация, MFA + SIEM

Подписанный релиз из сборочной среды; SAST/DAST в конвейере сборки; закрытый VLAN без пересечения с корпоративной сетью; холодный бэкап без прав записи

Управление замком

Замыкание реле, инъекция Wiegand

Шифрованный исполнительный протокол

OSDP v2.2 с шифрованием; контроллер с реле — в охраняемом периметре

Регистрация в отделе кадров

«Двойник», сговор оператора

Проверка личности + двойной контроль

Регистрация — только по токену; сверка документа; подтверждение вторым оператором

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

Почему SHA-256 «от ладони» нас не спасёт

Итак, у нас есть биометрический вектор. Его размер и способ получения — это ноу-хау. Но с другой стороны, это «отпечаток» ладони, который строит модуль фото/видео обработки. Чтобы соответствовать требованиям которые запрещают его хранение в сыром виде, нужно сохранить в базе что-то производное от него. Далее, сверять с ним живые ладони и сделать так, чтобы сохранённый артефакт был бесполезен нарушителю даже при полном доступе к базе.

Но тут есть неприятный нюанс. Вектор никогда не бывает константным. То-есть, два считывания одной и той же ладони различаются! Насколько? В основном это зависит от алгоритмов обработки, которые мы сознательно вынесли за скобки как коммерческое ноу-хау. Но по известным замерам в литературе, на одиночном кадре, это в среднем 7% битов. Для измеримых расчётов предположим, что отпечаток вен — это двоичный вектор из 1024 бит (128 байт), а способ его получения — чёрный ящик. Тогда 7% — это порядка 70 из 1024 бит.

Факторов из-за которых происходит различие, много. Это и мокрая кожа, и разное расстояние до сенсора, и погрешности в аффинных преобразованих (ладонь наклонили, поднесли ближе), и разные длины волн подсветки а также их количество, и тип оптического сенсора, и алгоритмы повышение контрастности, и работа с разрешением (апскейлинг и даунскейлинг). У конкретного решения полный список алгоритмов обработки находится в его конвейере обработки данных. Но какой бы ни был конвейер, шум присутствует всегда! Он реальный, измеримый и со стабильным статистическим профилем. Хороший конвейер обычно работает не на одиночном кадре. В выбранном нами окне захвата ~120 мс сенсор успевает сделать три кадра, а дескриптор строится как медиана значений каждого признака по кадрам окна. Хотя независимые кадровые ошибки медианой гасятся, но структурированные возмущения (угол, дальность, влага кожи) остаются всегда.

В литературе про схожие разработки, пилотные замеры после медианы дают порядка 4–5% в типичном случае и до 9–10% на границе. Вообще, итоговые цифры фиксируются измерениями по ISO/IEC 30136. Но для нашего проектирования, примем их как полученные по методике. Итак, 4-5% и 9-10%.

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

Нужна функция, которая:

  1. склеивает все считывания одной ладони в один устойчивый вектор

  2. разносит считывания разных ладоней в пространстве признаков как можно дальше друг от друга

  3. восстановление оригинального вектора из хеша должно быть экономически нецелесообразным, то есть, практически невозможным

Напримиер, возьмём SHA-256. Это функция с лавинным эффектом, т.е. один изменённый бит на входе меняет примерно половину битов на выходе. Два вектора с разницей в 70 бит дают два совершенно неродственных хеша. Расширение пространства признаков и необратимость она обеспечивает, но допустимую ошибку не решает вовсе! Поэтому тут нужно выбрать более «чувственное» решение.

Нечёткое обязательство (fuzzy commitment): биометрия как зашумлённый канал

Обратите внимание, получается наш вектор зашумлен, но все же тот же самый вектор. Значит, надо убрать этот шум. Коды коррекции ошибок в системах связи решают ровно эту задачу (передаёшь сообщение по каналу, который случайно разрушает биты. Приёмник не знает, какие именно биты разрушены, но при количестве ошибок ниже порога t восстанавливает исходное сообщение). Только наш «канал» — это физическая вариативность считывания. Где между сохранённым образцом и живой проекцией, вектор различается ровно теми случайными битами.

В литературе эта схема известна под именем fuzzy commitment, то есть нечёткое обязательство (Juels–Weis, 2003). Суть в том, чтобы не хранить секрет w напрямую, а хранить «открытый» артефакт c, который становится читаемым только в сочетании со свежим биометрическим вектором. Дисеры такой хранимый артефакт называют helper data. В ISO/IEC 24745 для такого хранимого артефакта закреплён термин «вспомогательные данные» (Auxiliary Data, AD). Мы будем называть с - вспомогательные данные.

Зафиксирую обозначения, потому что дальше пойдут формулы. Пусть w — секретный виртуальный ключ на 128 бит, b — биометрический вектор, T — номер проекции (маска и выборка координат. Как она ротируется и зачем, разберём ниже), k — номер версии виртуального ключа пользователя (перевыпускается без обращения к биометрии), MK — парковый мастер-ключ (один на все терминалы конкретного объекта, почему так, разберём ниже).

c = BCH_encode(w_{u,k}) ⊕ y_T(b),   где y_T(b) = select(b ⊕ M_T, I_T)

Здесь y_T — это проекция вектора в пространство ошибок кода (о маске и выборке координат, ниже), а c — вспомогательные данные (AD), то есть это «открытый» артефакт, который лежит в базе и передается по сети. При проверке живой вектор b' даёт:

w' = decode(c ⊕ y_T(b'))

Если расстояние между b и b' не превышает исправляющей способности кода t, декодер возвращает ровно исходный w. Если превышает, выдаёт какой-нибудь мусор в w', который не пройдёт контрольную проверку. В результате, система переходит в отказ, а не в допуск.

Виртуальная биометрия: три детали

Отменяемая «виртуальная» биометрия, это такая, которую можно сменить без пересканирования. Собирается она из трёх деталей:

  1. парковая проекция. Она выдаётся не на пользователя, а на весь парк, под номером T. Маска M_T = PRF(MK, «mask» || T) и правило выбора I_T. По этим параметрам, дверной терминал может спроецировать реальный вектор вен ладони. Он пользуется текущей проекцией, а сверка идёт по вспомогательным данным, сохранённым под ней. Все параметры проекции выводятся из MK. Подделать или подменить проекцию нельзя, а вычислить любую из них может каждый терминал парка.

  2. Проекция вектора с потерей данных, выполняемая на терминале. Правило выбора I_T публично доступно, но маска M_T — нет. Без паркового ключа каждая проекция выдает случайную последовательность битов, не связанный ни с исходным вектором, ни с другими проекциями.

  3. Процедура отзыва. Она работает как смена пароля, но у неё два независимых пути реализации.

Первый путь — перевыпуск виртуального ключа. Он действует на пользователя, мгновенно и без пересканирования.

  • Триггер: утечка базы, подозрение на компрометацию ключа либо плановое обновление.

  • Сервер в HSM выводит новую версию w_{k+1} и перекодирует вспомогательные данные: c' = c ⊕ E(w_k) ⊕ E(w_{k+1}). Оригинальный вектор не участвует, его негде взять и незачем.

  • Новая строка подписывается и уходит дельтой в дверные кэши. Старый c перестаёт действовать везде.

Второй путь — ротация проекции. Она действует на весь парк, без пересканирования, но не мгновенно.

  • Триггер: защита от накопления копий либо проекция, под которой парк живёт слишком долго.

  • Сервер подписывает объявление T+1, и набор «живых» номеров проекций становится {T, T+1} — терминалам он доходит той же дельтой. В течение окна (дни) дверные терминалы присылают по проекции на каждый живой номер, и каждый успешный проход мигрирует строку пользователя на новую. Личность уже установлена совпадением, а свежий вектор принёс сам сотрудник.

  • Пока под T есть хотя бы одна не мигрированная строка, она остаётся в живом наборе: не успевшие пройти (отпуск, больничный) проверяются онлайн под старой проекцией — её параметры выводятся из MK в любой момент — и мигрируют при следующем проходе. Когда последняя строка переехала, T выкидывается из набора.

Оба пути вместе закрывают конкретную атаку: накопление. Инсайдер, который раз в полгода снимает копию базы, через год держит вспомогательные данные сотрудников под разными проекциями и версиями ключей и пытается сложить их в систему уравнений на исходный вектор. И система уравнений у него не решается. Вектор c — это сумма двух секретов, выведенных из MK: кодового слова E(w_{u,k}) и маски M_T = PRF(MK, «mask» || T), наложенной на исходный биометрический вектор b. Для инсайдера без MK оба слагаемых выглядят как случайные 511-битные последовательности, причём каждая работает одноразовым шифроблокнотом над защищаемым фрагментом. То есть, отдельная последовательность не несёт об исходном векторе никакой информации. А последовательности c из разных снимков, не связаны. У них разные маски, разные кодовые последовательности или и то и другое сразу. При допущении стойкости PRF это доказуемое свойство: сколько бы копий базы инсайдер ни накопил, каждая — независимый замаскированный слепок, и количество информации об исходном векторе с числом копий не увеличивается. А перевыпуск ключей и ротация проекции, к тому же состаривают старые снимки. Наша схема не маскирует накопление, а делает его бесполезным.

Выбор кода коррекции ошибок

Для таких хранимых артефактов классический выбор — код Рида–Соломона. Но мы не используем его, и вот почему. Коды Рида–Соломона исправляют целые символы (байты), а наш шум живёт на уровне битов. Один ошибочный бит портит для декодера целый байт, а код, исправляющий t символьных ошибок, требует как минимум 2t символов избыточности.

Посчитаем. Наши вспомогательные данные — порядка 64 байт. Типовой шум после медианы рассыпается примерно по двадцати разным байтам, граничный — уже по тридцати с лишним. Код Рида–Соломона длины 64, исправляющий 20 символов, оставляет на сообщение всего 24 байта, и это без всякого запаса. Для граничного случая такого кода в 64-байтном блоке попросту не существует! Избыточность оказалась бы больше всего сообщения. Наш шум разреженный и независимый на уровне битов, поэтому берём двоичный блок-код с исправлением ошибок (БЧХ), но с той же математикой.

По теории кодирования, встречаем первое жёсткое ограничение. Длина двоичного кода БЧХ всегда нечётная: n = 2^m − 1, то есть 255, 511, 1023 и так далее. Кодов длины 512 или 1000 просто нет. На ближайшей допустимой длине 511, код, исправляющий 64 ошибки, существует, но ёмкость сообщения всего 76 бит. Это меньше половины от наших 128 бит виртуального ключа. Избыточность съедает 435 из 511 бит, то есть 85%.

Выберем рабочую точку кривой характеристик кода. Наша длина n = 511 (m = 9), исправляющая способность t = 40, проектируемое расстояние d ≥ 81. По разложению на циклотомические косеты степень генерирующего многочлена выходит ровно 300. Ёмкость сообщения 211 бит. Наш w занимает 128 из них. Остальные заполняются константой.

Проверим, покрывает ли t = 40 наш шум. Проекция оставляет 511 координат из 1024, то есть половину, и вдвое уменьшает в среднем вес межсчитывающей разности. Типовой шум после медианы — 4–5% полного вектора, то есть 41–51 бит; на наших 511 координатах это порядка 20–25 бит, под t = 40 с запасом в несколько сигм. Граничный случай, 9–10% полного вектора (то есть 46–50 бит), оказывается за пределами исправляющей способности: декодер просто не сойдется. Это не дефект. Это так спроектировано. Потому как в сомнительных случаях система должна переходить в отказ, а не в допуск. Сотрудник, у которого считывание попало в граничный случай, просто должен показать свою ладонь еще раз. Но нарушитель с муляжем пограничного качества получает болт.

Из чего состоит y_T

Теперь сама проекция. На ней стоит вся схема защиты. y_T строится двумя дешевыми операциями. Первая операция — маскирование секретом проекции: b ⊕ M_T, где M_T = PRF(MK, «mask» || T). Это 1024-битная маска, выведенная псевдослучайной функцией (PRF) от паркового мастер-ключа MK и номера проекции. Вторая операция — публичная выборка координат с необратимой потерей данных. Из замаскированного вектора берём 511 позиций из 1024 по правилу I_T, известному всем (и серверу, и терминалам). Правило зависит только от номера проекции.

Итого, закрываются сразу два требования. Выборка безвозвратно выбрасывает 513 бита, и восстановить их не из чего, их просто нет в c. А маска делает оставшийся фрагмент случайным без MK. В результате терминал покидает замаскированный фрагмент, 511 бит (64 байта) вместо сырого вектора 1024 бит.

Регистрация и проверка в псевдокоде

Напишем код регистрации, проверки и обоих видов отзыва.

// Операции над битовыми векторами показаны так,
// будто это целые числа. Размеры: b — 1024 бита; M_T — 1024 бита;
// y_T, y_live — 511 бит (64 байта); w — 128 бит; c — 64 байта;
// key_hash — 32 байта.

// Регистрация
// Терминал регистрации, изолированная зона (TEE/SE).

fn enroll(user_id: UserRef, t: Transform) -> EnrollmentPacket {
    // Вектор - медиана трёх кадров в окне захвата ~120 мс.
    // Кадры живут в ОЗУ ~200 мс (захват плюс вычисление дескриптора),
    // сам b — до формирования проекции y_T. Никуда не записывается,
    // ни на диск, ни в кэш, ни на сервер.
    let b = sensor.read_vein_vector();

    // Парковая проекция: маска и выборка выводятся внутри кристалла из MK
    // (слот SE, чтение запрещено). Сам MK в память процессора не светит
    let m_t = se.prf("mask", t);        // 1024 бита
    let y_t = project(b ^ m_t, t);      // 511 бит, публичное правило I_T

    b.wipe();                           // оригинал больше нигде не существует

    // Виртуальный ключ, версия 1: секрет привязки личности, 128 бит.
    // Не ключ шифрования — проверяется по хешу; перевыпускается без
    // пересканирования (см. rekey ниже).
    let w = se.prf("key", user_id, 1);                 // 128 бит

    // Вспомогательные данные (AD): 64 байта. Хранятся на сервере
    // открыто — без MK для нарушителя это шум. Почему — ниже.
    let c = bch_encode(w) ^ y_t;                       // BCH(511, 211), t = 40

    // Пакет уходит на сервер подписанным; сервер в HSM дописывает
    // key_hash = SHA-256(w) из своей копии MK (w модуль не покидает)
    // и сохраняет строку {user_id, c, T=t, k=1, key_hash}.
    EnrollmentPacket { user_id, commitment: c, transform: t }
}

// Проверка онлайн (сервер, 1-to-N)

/// замаскированные живые проекции с дверного терминала под
/// mTLS и ключом полезной нагрузки на проход (как он выводится — раздел 5).
/// По одной на каждый «живой» номер проекции, объявленный сервером дельтой:
/// штатно один, в окне ротации (T -> T+1) два; старый остаётся живым,
/// пока под ним есть не мигрированные строки.
fn verify_online(projections: &[MaskedProjection]) -> Decision {
    for row in db.templates() {                        // {c, T_u, k_u, key_hash}
        let Some(y_live) = projections.iter().find(|p| p.t == row.t_u) else { continue };

        let w_prime = bch_decode(row.c ^ y_live);      // декод кода БЧХ, t = 40
        if sha256(&w_prime) != row.key_hash { continue }

        // Совпадение — личность установлена. Если строка ещё под старой
        // проекцией, мигрируем её в том же проходе: кодовое слово
        // пересчитывается внутри HSM, w модуль не покидает, оригинальный
        // вектор не участвует — его нет.
        if row.t_u < current_t() {
            let y_new = projections.iter().find(|p| p.t == current_t()).unwrap();
            // c' = E(w) ⊕ y_{T+1}(live)
            row.c = hsm.recommit(row.user_id, row.k_u, y_new); 
            row.t_u = current_t();          // ключ и key_hash не меняются
            db.save(row);
            cache_delta.push_signed(row);   // подписанная дельта дверным кэшам
        }
        return Decision::AllowPass;         // кандидат декодировался в известный ключ
    }
    Decision::Reject                        // отказ при сомнении
}

// Проверка офлайн (дверной терминал, горячее подмножество)

fn verify_offline(b: &RawVector) -> Decision {
    // Один захват на всю сверку; b обнуляется после выхода из функции.
    for rec in door_cache.records() {     // подписанные сервером {user_id, c, T_u, k_u}
        let y_live = project(b ^ se.prf("mask", rec.t_u), rec.t_u);
        let w_prime = bch_decode(rec.c ^ y_live);

        // Дверной терминал держит MK в SE, поэтому сравнивает полный ключ
        // за постоянное время, а не хеш: на слабом CPU сравнение 128 бит
        // дешевле хеширования. Ключи в записях кэша не хранятся — выводятся
        // на лету и стираются после сверки. Различных проекций в кэше не
        // больше двух одновременно — окно ротации.
        let expected = se.prf("key", rec.user_id, rec.k_u);
        if w_prime.ct_eq(&expected) {
            return Decision::AllowPass;
        }
    }
    Decision::Reject
}

// Перевыпуск виртуального ключа (сервер + HSM)

fn rekey(user_id: UserRef) -> SignedDelta {
    // Перекодирование без биометрии: код линейный, поэтому
    // c' = c ⊕ E(w_k) ⊕ E(w_{k+1}). Оба кодовых слова и новый хеш
    // считаются внутри HSM — w модуль не покидает; оригинальный вектор
    // нигде не существует и ни во что не вмешивается.
    let row = db.get(user_id);
    (row.c, row.key_hash) = hsm.rekey_row(row.user_id, row.k_u, row.c);
    row.k_u += 1;
    db.save(row);
    cache_delta.push_signed(row)         // старый c перестаёт действовать везде
}

Три детали в этом коде заслуживают внимания.

  • Сырые данные живут в памяти только во время работы. Кадры ~200 мс (захват плюс вычисление дескриптора), сам вектор b — ещё несколько миллисекунд до формирования проекции y_T; ни на одном устройстве, включая терминал регистрации, они не записываются на диск.

  • Онлайн-проверка сравнивает по хешу, а офлайн — по полному ключу: дверной терминал держит MK в SE, вычисляет ожидаемый w сам и сравнивает за постоянное время, а ключи в записях кэша не хранятся, а выводятся на лету.

  • Два вида ротации — перевыпуск виртуального ключа для пользователя и ротация проекции для парка, живут рядом и не конфликтуют

Ещё раз про структуру данных. Вспомогательные данные c лежат на сервере открыто — и это всё, что в системе биометрического. Даже при полной утечке базы, известном алгоритме и известном правиле выбора I_T исходный вектор из c не восстанавливается без паркового ключа. Кроме c в системе не хранится никаких биометрических объектов. Ни референсных векторов, ни проекций, ни копий «про запас».

Мастер-ключ: почему один на парк

Это хороший вопрос который возникает к этому моменту. От него зависит вся архитектура распределённой системы. Конечно, существует альтернатива. Это сделать терминалы «глупыми» и перенести вычисления (проекцию) целиком на сервер. Этот путь я даже не рассматриваю, потому что он устраняет офлайн-режим и сильно понижает защищенность, так как выводит полные 1024-битные векторы в канал.

В нашей системе MK на весь парк, то есть на все терминалы одного объекта. Почему не давать каждому терминалу собственный уникальный мастер-ключ? Потому что маскированные вектора на терминале регистрации и на двери различались бы. Их маски независимы, а расстояние Хэмминга между проекциями было бы велико. В результате сверка рассыпается, и требование «одна биометрия на все двери» нарушается на первом же многодверном развёртывании. Общий ключ даёт совместимость без обмена секретами по сети.

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

А если с MK случится худшее, то есть ключ утечёт? Это единственное событие, от которого наша система не восстанавливается без пересканирования. Рассмотри подробно. Это не одна атака, а небольшой набор атак. Ключи парка находятся в двух организациях, у изготовителя (который собирает терминалы) и у заказчика (который эксплуатирует СКУД), поэтому сразу разделим, где что происходит.

  1. Первый сценарий — физический взлом кристалла SE на объекте заказчика. Сюда входит снятие крышки, фокусированный ионный пучок, внедрение отказов в момент работы PRF. Защищённый элемент строится так, чтобы это было экономически нецелесообразно, но «нецелесообразно» — не значит «невозможно», и доказать обратное не сможет ни один вендор.

  2. Второй сценарий — сборочный контур изготовителя. MK вписывается в чипы на заводской линии, так что скомпрометированная линия (станция прошивки, производственная сеть, инсайдер с доступом к ней) перехватит ключ до ввода системы в эксплуатацию. Этот сценарий атаки потенциальный только до церемонии передачи ключей и для данного парка после неё закрывается.

  3. Третий сценарий — HSM сервера у заказчика. Сюда входит кража самого модуля, поиск уязвимость его прошивки либо злоупотребление служебным интерфейсом.

Все три пути редки, но не исключаются наверняка. Поэтому система строится не на допущении «MK никогда не утечёт», а на том, что цена утечки заранее известна. Это перерегистрация, а не украденная база биометрии!

Теперь последствия. Все виртуальные ключи и все проекции выводятся из MK. Получивший его нарушитель вычисляет w каждого сотрудника, «отпирает» c до замаскированных проекций и по известным маскам читает половину координат каждого вектора в открытом виде. А со временем, по мере ротации проекций, соберёт и остальное. Поэтому, архитектура и система защиты сразу имеет ответ на такого рода утечку. Это процедура из шести шагов.

  1. Установить, через какой из трёх путей ушёл ключ — кристалл, производственная линия или HSM. Пока идёт расследование, парк работает штатно. Одного MK для открытия турникета недостаточно, нарушителю всё ещё нужен живой вектор. Но каждый день под скомпрометированным ключом — это время на постройку синтетической ладони, поэтому процедура стартует сразу. Решения принимаем за часы, а саму замену ключей во всём парке ведём параллельно операционным мерам.

  2. Заменить ключ внутри чипов, не меняя сами чипы. На первый взгляд тут противоречие. MK вписан на заводе в слот, который нельзя ни читать, ни перезаписывать. Но у кристалла есть специальный режим именно для этого случая, он встроен в чип, а не придуман нами. Вот как он работает.
    На производстве изготовителя вместе с MK в каждый чип вписывается второй ключ, KIK (key injection key). Его роль одна: разрешать замену MK. После этого слот MK принимает запись только в особом виде: зашифрованный пакет, к которому приложена контрольная подпись, вычисленная от KIK. Чип проверяет подпись сам. И если совпала, то выполняет запись. А собрать такой пакет без знания KIK нельзя.
    Самое главное, это кто и где хранит эти ключи. Изготовитель и заказчик — разные организации в разных местах. Во время производства копии MK и KIK есть в HSM сборочной линии изготовителя. Именно оттуда создаются пакеты, которыми ключи вписываются в чипы. При вводе системы в эксплуатацию, выполняется церемония передачи ключей. Под двойным контролем (например, два представителя заказчика в присутствии представителя изготовителя) MK и KIK вносятся в HSM серверной заказчика, после чего изготовитель уничтожает свои копии MK этого парка.
    Может быть исключение, при котором изготовитель оставляет себе копию KIK, но только для подготовки запасных носителей, о которых ниже. С этого момента все операции с ключами парка происходят исключительно у заказчика, и ни одна копия MK нигде больше не существует, кроме HSM заказчика и самих чипов.
    В программном обеспечении терминалов KIK нет вообще. Из этого следует главное. Взломанный дверной терминал свой ключ сам себе заменить не может, потому что у него нет KIK, которым можно подписать пакет. Прочитать MK зловред тоже не может - слот не читается. Только разрушающий анализ чипа или ионная пушка.
    На практике, замена ключей по парку выглядит так. Служба безопасности заказчика под двойным контролем и с записью каждого действия в аудит генерирует новый MK в своём HSM. Затем каждому терминалу по mTLS, по его уникальному сертификату, доходит зашифрованный пакет с новым ключом. Пакет подписан копией KIK, которая лежит в том же HSM. Прошивка терминала даже не смотрит внутрь пакета - она просто передаёт его чипу, а чип сверяет подпись со своим KIK и принимает новый МК. У каждого чипа есть ограниченное число таких замен (аппаратный счётчик). Если бюджет счетчика исчерпан, то слот запирается навсегда.

    Железо меняют только там, где чип вскрывали физически или он самоуничтожился. Дополнительно, такой носитель обнуляют специальной подписанной командой и выводят из эксплуатации. Запасные чипы заказывают у изготовителя. Они приходят с уже вписанным KIK этого парка, а текущий MK в них дописывает сам заказчик, на месте, той же процедурой. MK есть в каждом терминале парка, включая дверные, поэтому переключение идёт только по своему  парку. Другие развёртывания со своими ключами МК не затрагиваются.

  3. Если вектором атаки была сборочная линия изготовителя (сценарий до ввода в эксплуатацию), сменить также ключи подписи прошивок и пакетов кэша, которые использовали МК. Изготовитель выпускает новые образцы, заказчик перезаписывает терминалы. Иначе зловред сможет подписать собственный «легитимный» кэш или прошивку.

  4. Новый MK попадает в два места, оба у заказчика. В HSM сервера (для HSM замена ключа на месте — штатная операция) и во все чипы терминалов механизмом из шага 2. Обе процедуры идут под двойным контролем службы безопасности заказчика, каждое действие сохраняется в аудит-лог. Новый ключ означает новую семью проекций (T = 1) и виртуальные ключи с версии 1.

  5. Кампания перерегистрации. Каждый сотрудник один раз подносит ладонь на терминале регистрации, как при приёме на работу. Новая запись заменяет старую, а подписанная дельта вычищает старые записи из дверных кэшей. Далее, все работает штатно. Остальные сотрудники в переходный период проходят по короткоживущим билетам и через дежурный пост. Двери, чей ключ ещё не обновлен, на время работают только по билетам.

  6. Проверочные процедуры, SIEM (система анализа событий безопасности) в режиме повышенной чувствительности на время перехода, ведет запись инцидента в неизменяемом журнале (масштаб, затронутые устройства, действия). Происходит закрытие того самого канала, через который ключ был скомпрометирован. Новый MK дыру не чинит, он лишь делает старый ключ бессмысленным. Параллельно идёт административный шаг. Об утечке биометрических персональных данных уведомляется надзорный орган по 152-ФЗ.

Остаётся один вопрос. После всего сказанного KIK стал верхним секретом управления ключами, и живёт он в двух копиях: в HSM серверной заказчика (для эксплуатации) и у изготовителя (только для подготовки запасных носителей). Что, если утечёт и он? Одна копия сама по себе бесполезна. Пакет инъекции доходит до чипа только через локальное ПО терминала, а оно действует по команде HSM заказчика — со стороны изготовителя его отправить нельзя. Поменяли ключ на одной двери, она тут же перестала узнавать сотрудников! Это видно и по аудиту, и по статистике проходов. Чтобы заставить весь парк работать с известным атакующему ключом, нужен физический доступ ко многим (всем) терминалам в пределах бюджета счётчиков. Механизм переносит «замену всего парка» из случая утечки MK в случай, когда одновременно скомпрометированы и управление ключами, и физический периметр.

Это осознанная цена того, что оригинальная биометрия нигде не хранится. В самом безнадёжном случае, мы платим одноразовым пересканированием. Вероятность события ограничена тем, что защищает MK: слоты без чтения и HSM в охраняемой серверной. А чтобы реакция была быстрой в случае возникновения инцидента, всё должно быть готово заранее. Все должно быть в эксплуатационном плане заказчика: KIK в его HSM, отрепетированная процедура замены ключей и то, сколько замен каждый чип ещё выдержит. Склад запасных носителей при этом держат только под физически повреждённые устройства. Заказывают их у изготовителя.

Итоговая таблица событий:

Событие

Ответ системы

Перерегистрация сотрудников?

Утечка базы, снимки инсайдера

Перевыпуск виртуальных ключей (на пользователя) и/или ротация проекций

Нет
перекодирование считается из MK в HSM, миграция — из живых захватов при легитимных проходах

Увольнение сотрудника

Удаление строки, подписанная дельта в дверные кэши

Нет

Старый парк, защита в глубину

Ротация проекций T -> T+1

Нет
строки мигрируют сами по мере того, как сотрудники проходят турникеты

Утечка MK: кристалл у заказчика, линия изготовителя или HSM

Процедура из шести шагов (выше): всё выполняется у заказчика — замена ключа во всех чипах через KIK из его HSM, смена ключей подписи, если была затронута линия изготовителя, перерегистрация парка; железо меняют только вскрывшиеся чипы

Да — и это единственный такой случай. Одноразовая кампания: каждый по одному разу подносит ладонь, как при приёме на работу

Полная пересъёмка парка существует ровно в одном сценарии за всю жизнь системы: в том, где становятся публичны все секреты, выведенные из MK. Все остальные сценарии не требуют пересканирования.

Исходный вектор нигде не хранится

Начнём с вопроса, который напрашивается: если оригинал нигде не лежит, на чём тогда держится отзыв? Оказывается, ни на чём. Перевыпуск ключа — это перекодирование вспомогательных данных, а оба виртуальных ключа выводятся из MK прямо в HSM. Ротация проекции пересчитывается из живых захватов во время легитимных проходов: каждый сотрудник каждый день подносит ладонь к турникету, и это самый свежий референсный вектор, который только можно попросить у системы. Хранить копию «про запас» не нужно, она лишь добавила бы объект атаки без какой либо полезности.

Значит, где исходный вектор вообще существует? В ОЗУ того терминала, который его снял. При регистрации — на терминале в кадровом отделе. Из памяти строится c, и всё сразу стирается. Не записывается ни в NVM этого терминала, ни куда-либо ещё. При проходе на дверном терминале, та же история, а в сеть уходит только замаскированный 511-битный фрагмент. Сервер в любой момент жизни системы видит не больше этого фрагмента: ни сырого изображения, ни полного вектора, ни незамаскированной проекции. Миграция при ротации тоже пересчитывается из живого фрагмента, который терминал прислал в этом же проходе.

Соответствие ISO/IEC 24745:2022

Необратимость нашей конструкции вычислительная. Её основа — секретность паркового мастер-ключа МК. Все публичные параметры (номера проекций, правило выбора I_T, код) известны. Но без MK ни одна строка {c, T, k} не переводится в исходные вектор. Потому что неизвестны ни маска M_T, ни кодовое слово E(w_k). Вторая опора — безвозвратная выборка координат. Даже перехваченная проекция даёт только 511 замаскированных координат исходного вектора. Восстановит её нельзя. Каждое использование привязано к одноразовой nonce.

Требование стандарта о стойкости при «компрометации алгоритма» закрывается так — алгоритм открыт, закрыт ключ. А сам исходный вектор защищён сильнейшей из доступных мер :) он не хранится вовсе! Категории данных стандарта ложатся на нашу конструкцию, за исключением одной категории BR (Biometric Reference) — референсный вектор b. В нашей системе он не существует в какой-либо форме. Биометрических данных нет ни в БД, ни где-либо ещё. Он появляется как объект обработки в ОЗУ TEE, в момент работы сенсора и стирается. Вся биометрическая математика выполняется на терминалах, а сервер держит только защищённые шаблоны.

Стандарт требует защищать BR, но мы пошли дальше. Мы вообще его не храним нигде. AD (Auxiliary Data) — вспомогательные данные: c вместе с номером проекции T и версией ключа k. BDB (Biometric Data Block) — блок биометрических данных, то есть защищённый шаблон, который реально лежит в базе: кортеж {c, T, k, key_hash}. Требование стандарта «хранить защищённый шаблон, а не сырые биометрические данные» выполняется с запасом. Однако необратимость не гарантирует невозможности построения синтетического входа, который пройдёт проверку. Поэтому перевыпуск ключей и ротация проекций — неотъемлемая часть схемы.

Вывод. Сравнение биометрического вектора заменено вспомогательными данными с двумя независимыми ротациями: перевыпуск виртуального ключа без пересканирования и ротация проекций из текущих сканирований.

Терминал в руках врага

Что именно получит Н2, если украл терминал? Чтобы ответить, сначала разберёмся, каким узлам платы мы доверяем и где на ней лежат секреты. Затем пройдём по ним слой за слоем.

Доверяем без всяких проверок только изолированной зоне. В терминале она реализована как защищённый элемент (Secure Element, SE) или доверенная среда выполнения (Trusted Execution Environment, TEE), в зависимости от конфигурации платформы. Для нашего примера возьмём SE кристалл ATECC608B (говорят, что существуют российские аналоги, линейки МИКРОН и ПКК «Миландр». Но лично я с ними не знаком. Возможно, они и существуют...). Внутри SE кристалла дверного терминала лежат три секрета: парковый мастер-ключ, ключ дешифрования локального кэша и закрытые ключи для связи с сервером. Вектор b не существует в персистентной форме, он живёт лишь в ОЗУ терминала, который его считал. В варианте с TEE вектор не покидает защищённую подсистему вовсе. На шину и в сеть попадает только замаскированный фрагмент — 511 бит вместо 1024.

Локальный кэш — главная цель Н2. Именно кэш определяет, кого пропускать, когда терминал работает автономно. Терминал не хранит копию всей базы, он знает только о своей двери. Матрица прав «пользователь-дверь» ведётся на сервере, а каждому терминалу отправляется лишь то подмножество, которое относится к нему. В этом подмножестве два типа записей. Для сотрудников, которые постоянно проходят через эту дверь и потому образуют горячее подмножество (в кэше лежат записи со вспомогательными данными. Офлайн-сверка идёт по ним). Остальным допущенным пользователям выдаются короткоживущие подписанные билеты. Как это сочетание работает на практике, покажем на сквозном проходе. Сейчас важно лишь понять, что физически лежит во флеш-памяти.

Во флеш-памяти кэш хранится только в зашифрованном виде: AES-256 или, в гостированной конфигурации, «Кузнечик» (ГОСТ 34.12-2015) в режиме «Магмы» (ГОСТ Р 34.13-2015). Ключ дешифрования генерируется внутри защищённого SE кристалла. На каждой записи, будь то вспомогательные данные или билет, стоит цифровая подпись сервера.

Ключи связи с сервером подчиняются тому же правилу: закрытая часть каждого хранится только в защищённой памяти SE, сертификат лежит в слоте, и наружу не выходит ни то, ни другое. Ключи полезной нагрузки биометрического канала выводятся аппаратно. Для этого у SE есть команда вывода ключа (KDF, opcode 0x56), которая работает в трёх режимах: PRF для TLS 1.2, HKDF на HMAC-SHA256 для TLS 1.3 и AES-KDF.

Отдельно скажу про выбор финальной платформы. Кандидатов тут три: исполнение «на голом железе» без операционной системы на микроконтроллере, RTOS или встроенный Linux. Язык ядра (C/C++ против Rust) тоже надо выбрать. От этого решения зависят доступные крипто- библиотеки и то, как будет устроен контроль целостности памяти. При этом один пункт решён независимо от исхода этого выбора: криптографическое ядро вокруг SE в любом случае пишется на no_std Rust.

Secure Boot защищает прошивку от изменений со стороны Н2. Цепочка короткая и без исключений: аппаратный корень в ROM, затем неизменяемый загрузчик, который при включении проверяет цифровую подпись образа. У нас это ECDSA. Режима восстановления нет.

Защита от вскрытия устроена так. Триггеры — герконы на задней крышке, сетка микровыключателей в швах корпуса и контроль аномалий напряжения через контроллер питания. Их сигнал заведён напрямую на аппаратное прерывание процессора с наивысшим приоритетом. Стоит какому-нибудь датчику сработать, как терминал немедленно стирает ключи дешифрования кэша из SRAM, переходит в полный режим блокировки и шлёт срочную тревогу на сервер СКУД. В журнале аудита она появится как событие вскрытия. Если нарушитель успел перебить канал, событие записывается в локальный неизменяемый журнал и уйдёт при следующей синхронизации. Но в любом случае, устройство заблокировано.

Что реально получит Н2, украв терминал? Сырые ИК-кадры задерживаются в общей памяти порядка 200 миллисекунд (сколько длится захват трёх кадров и вычисление дескриптора). Сам дескриптор продержится ещё около десяти миллисекунд, пока не сделаны маскирование и выборка координат. После этого и оригинальное изображение и его вектор уничтожаются.

В релизной прошивке линии JTAG, UART и SWD отключены. Если Н2 пойдёт в лоб и вскроет корпус, герконы и контроллер питания сработают раньше, чем он дойдёт до платы. Ключи в SRAM уже стёрты, устройство заблокировано, на сервере уже лежит строка тревоги в журнале.

Внутренняя структура терминала

Внутренняя структура терминала

Вывод. Извлечь секреты не получится физически: кэш зашифрован, ключи из кристалла не извлекаются, отладочные линии отключены, а при вскрытии терминал стирает ключи, блокируется и шлёт тревогу на сервер. В доверенной зоне остались корень в ROM, ядро SE.

Путь от сенсора до замка (сквозной протокол)

Итак, человек подносит ладонь к считывателю, а мы с вами следим за каждой остановкой.

Этап 0. Синхронизация кэша. Это приём на работу, увольнение, перевыпуск виртуального ключа, миграция при ротации проекций, изменение прав доступа, переход пользователя между горячим и холодным подмножествами, объявление нового номера проекции. Поддерживаются два типа записей: вспомогательные данные для горячего подмножества и короткоживущий подписанный билет {user_id, terminal_id, срок действия}, выдаваемый остальным допущенным после каждого успешного онлайн-прохода. Терминал не принимает обновление кэша, пока сервер не докажет свою подлинность и пакет не пройдёт проверку подписи.

Этап 1. Регистрация. Начинается с разделения ролей. Оператор отдела кадров инициирует сессию. Вход в режим регистрации только по квалифицированному токену. ИК-снимок превращается в дескриптор локально, в ОЗУ его терминала. На сервер уходят только вспомогательные данные, построенные под замаскированной проекцией. Для оператора это выглядит так: после сканирования сотрудника, на экране появляется «предьевите токен для подтверждения личности», затем «шаблон принят, подпись сервера получена».

Этап 2. Транспорт и прикладной механизм «запрос — ответ». Это базовый уровень mTLS. У сервера и у каждого терминала свой уникальный криптографический сертификат. Для коммерческого объекта в МУиН обосновывают AES-256 и TLS 1.3 (RFC 8446) и стандартный стек. Для госорганов и КИИ стандартного OpenSSL с AES и RSA не хватает: нужны сертифицированные ФСБ средства (КриптоПро, ИнфоТеКС, примеры иллюстративные) классами стойкости КС1–КС2 по Приказу ФСБ № 378 и TLS на алгоритмах ГОСТ, «Кузнечик» по ГОСТ Р 34.12-2015. Замена алгоритмов не требует перепроектирования, схема алгоритмически независима.

Защита от повторения запросов строится так. Сервер на каждый запрос прохода выдаёт одноразовое nonce и временную метку. Терминал включает их в контекст выведения ключа полезной нагрузки. Сам сервер ведёт учёт выданных nonce и проверяет окно времени — повторённый пакет отклоняется, его nonce уже известен. Два слоя шифрования тут необходимы. mTLS защищает канал, и только канал. Администратор или ведение журнала на TLS-контуре видит открытые байты. Поэтому биометрическая полезная нагрузка дополнительно шифруется ключом на проход, известным лишь SE терминала и HSM сервера (серверная половина того же выведения выполняется в HSM от той же копии паркового ключа). Зловред или инсайдер внутри туннеля видит только шифротекст. Каждый перехваченный фрагмент замаскирован маской проекции, а чтобы превратить поток в права на проход, нужна активная позиция внутри mTLS с валидным сертификатом терминала. Как следствие, всё перехваченное обесценивается следующей ротацией проекций. Ключ полезной нагрузки мы выводим аппаратно, а не программным стеком на микроконтроллере: no_std Rust-драйвер SE, команда KDF (opcode 0x56):

/// Поля байта mode команды KDF (opcode 0x56).
/// Раскладка битов — по даташиту ATECC608B, поля не пересекаются.
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum KdfSource {
    TempKey = 0x00, // биты 1-0: исходный ключ — из регистра TempKey
    Slot    = 0x02, // биты 1-0: исходный ключ — из защищённого слота EEPROM
}

#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum KdfTarget {
    TempKey      = 0x00, // биты 4-2: результат — в регистр TempKey
    Slot         = 0x08, // биты 4-2: результат — в слот EEPROM
    OutputBuffer = 0x10, // биты 4-2: результат — наружу по шине (output buffer)
}

#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum KdfAlgorithm {
    Prf  = 0x00, // биты 6-5: PRF для TLS 1.2
    Aes  = 0x20, // биты 6-5: AES-KDF, фиксированное 16-байтное сообщение
    Hkdf = 0x40, // биты 6-5: HKDF-Extract/Expand на HMAC-SHA256 (RFC 5869) для TLS 1.3
}

/// Details — 4 байта в little-endian. Для HKDF: биты 1-0 — место сообщения
/// (0 = слот, 1 = TempKey, 2 = входной параметр), старший байт — длина сообщения.
const fn hkdf_details(msg_location: u8, msg_len: usize) -> u32 {
    ((msg_location & 0x03) as u32) | ((msg_len as u32) << 24)
}

impl<I2C, DELAY, E> Atecc608b<I2C, DELAY>
where
    I2C: i2c::I2c<Error = E>,
    DELAY: delay::DelayNs,
{
    /// Аппаратное выведение ключа (команда KDF, opcode 0x56).
    /// Исходный ключ не покидает кристалл: выведение выполняется внутри кремния.
    pub fn derive_key(
        &mut self,
        algorithm: KdfAlgorithm,
        source: KdfSource,
        target: KdfTarget,
        source_key_slot: u8,  // 0..15, если источник — слот
        details: u32,         // 4 байта; для HKDF — hkdf_details(...)
        message: &[u8],       // до 128 байт; у нас 16 (nonce + timestamp)
        out_buffer: &mut [u8] // только для OutputBuffer; 32 байта
    ) -> Result<usize, Error<E>> {
        let mode = (algorithm as u8) | (source as u8) | (target as u8);
        let key_id = source_key_slot as u16; // младший байт — слот источника, цель не в EEPROM
        let mut tx = [0u8; 4 + 128];         // details + сообщение
        tx[..4].copy_from_slice(&details.to_le_bytes());
        tx[4..4 + message.len()].copy_from_slice(message);

        let mut rx_buf = [0u8; 35]; // байт счётчика + до 32 байт ключа + 2 байта CRC
        let resp_len = self.send_command(
            CommandCode::Kdf, mode, key_id, &tx[..4 + message.len()], &mut rx_buf,
        )?;

        if KdfTarget::OutputBuffer == target {
            let data_len = resp_len - 3; // минус байт счётчика и 2 байта CRC
            if out_buffer.len() < data_len {
                return Err(Error::BadResponseLength);
            }
            out_buffer[..data_len].copy_from_slice(&rx_buf[1..1 + data_len]);
            Ok(data_len)
        } else {
            Ok(0) // ключ остался внутри чипа (TempKey или слот)
        }
    }
}

Рассмотрим пример в контексте прохода.

Мастер-ключ лежит в слоте №2 и остаётся в режиме без чтения. HKDF-Extract/Expand на HMAC-SHA256 (RFC 5869) выполняется внутри кристалла. Корнем выступает ключ из слота, а 16-байтный контекст (nonce и метка времени сервера) передаётся входным параметром команды.

Серверная половина того же выведения — в HSM от его копии паркового ключа. На выходе — 256-битный ключ полезной нагрузки в ОЗУ микроконтроллера, для AES-GCM над биометрическим фрагментом.

let mut ctx = [0u8; 16];
ctx[..8].copy_from_slice(&nonce_from_server); // nonce запроса прохода
ctx[8..].copy_from_slice(&timestamp_be);      // метка времени сервера
let mut payload_key = [0u8; 32];

crypto_auth.derive_key(
    KdfAlgorithm::Hkdf,           // HKDF на HMAC-SHA256
    KdfSource::Slot,              // мастер-ключ — в SE, не покидает кристалл
    KdfTarget::OutputBuffer,      // результат — в ОЗУ для AES-GCM
    2,                            // слот №2 (key_id: младший байт)
    hkdf_details(2, ctx.len()),   // сообщение — входной параметр, 16 байт
    &ctx,
    &mut payload_key,
)?;

SE кристал выполняет выведение внутри. В ОЗУ процессора появляется только результат выведения — новое значение. Ключ полезной нагрузки живёт в памяти лишь до конца прохода и стирается. В гостированной конфигурации та же механика выполняется на сертифицированных средствах (ГОСТ TLS-стек и «Кузнечик/Магма» для полезной нагрузки). Управление ключами не меняется, меняются лишь алгоритмы криптографии.

Этап 3. Проверка. Онлайн режим: терминал шлёт по mTLS под ключом полезной нагрузки на проход только замаскированные живые проекции — 511 бит вместо 1024, по одной на каждый «живой» номер проекции, объявленный сервером в дельте. SE здесь выводит ключ полезной нагрузки и выполняет подписи. В пакет попадает только замаскированный фрагмент (в конфигурации с TEE строже, см. раздел 4).

Сопоставление «один ко многим» (1:N) идёт на сервере: по каждому кандидату его вспомогательные данные «отпираются» живой проекцией того же номера и декодируются, а результат сверяется с хешем виртуального ключа, а не с самим ключом. Если совпавшая строка ещё под старой проекцией, сервер мигрирует её в том же проходе. То-есть, пересчитывает c из живой проекции в присланную вместе с запросом, без пересканирования и без участия терминала регистрации. В СУБД лежит только кортеж, никаких других векторов:

-- таблица protected_templates
user_id     INT PRIMARY KEY,   -- идентификатор сотрудника
helper_data BINARY(64),        -- вспомогательные данные c (AD)
transform   SMALLINT,          -- T_u: номер проекции, под которую построено c
key_ver     SMALLINT,          -- k_u: версия виртуального ключа (перевыпускается 
                               -- без пересканирования)
key_hash    BINARY(32),        -- SHA-256(w_{k_u}): хеш виртуального ключа, 
                               -- сам ключ в БД не лежит

Сама СУБД лежит под шифрованием с ключами в HSM сервера. Достаточно минимального уровеня защиты поверх используемой необратимости. Скомпрометировавший файловый слой получает не вспомогательные данные, а зашифрованный шум.

Офлайн режим работает так: терминал решает по подписанному кэшу, теперь в двух ветках. Для горячего подмножества — это сверка по вспомогательным данным, но лишь в горячем множестве. Ожидаемый виртуальный ключ терминал выводит сам из паркового мастер-ключа в SE, по номеру проекции T_u и версии ключа k_u, записанным в самой записи. Именно поэтому офлайн-задержка не растёт с численностью компании. Она растёт только с горячим подмножеством собственной двери.

Для остальных допущенных — проверка офлайн-билета: он должен быть привязан к этому терминалу по terminal_id, подписан сервером и быть свежим. Но свежесть проверяется не по локальным часам терминала, которые нарушитель может отменить. А по монотонному якорю времени: каждый подписанный пакет дельты несёт метку времени сервера с его подписью. Терминал держит в SE значение, обновляемое от якорей. Если якорь меньше сохранённого — это событие вскрытия, а не синхронизация. Если билета нет, то во время сетевого сбоя пропуска не будет. Другими словами, новичок, чьё первое сканирование пришлось на обрыв сети, или сотрудник, который несколько дней не проходил, до восстановления канала не пройдёт. Для критичных дверей это компенсируется политикой горячего подмножества (параметры конфигурации кэша на терминале), а при необходимости - дежурным постом.

События автономных проходов логируются на терминале и синхронизируются с сервером по восстановлению связи. В журнале они помечаются как события автономного режима.

Этап 4. Исполнение открытия. Команда открытия идёт по OSDP v2.2 с шифрованием на контроллер, где физически размещено реле, внутри охраняемого периметра. Исполнительные цепи изолированы от корпуса публичного терминала. Время срабатывания реле ~90 мс.

Складываем задержки по проходу в онлайн-режиме.

Захват 120 мс (три кадра, медиана), дескриптор 85 мс, маскирование, выборка и KDF ~15 мс, сеть 4–9 мс при возобновлении сессии mTLS, декод на сервере — микросекунды (полное согласование только при старте смены), реле 90 мс. Турникет открывается через ~320 миллисекунд от показывания ладони.

Диаграмма последовательности

Диаграмма последовательности

Итоги по этапам

  • Этап 0: подписанная дельта идёт по mTLS, доверяем корню CA.

  • Этап 1: на сервер уходят только вспомогательные данные под замаскированной проекцией.

  • Этап 2: аппаратный KDF плюс mTLS, доверие среде, вписавшей сертификаты в слоты.

  • Этап 3: на сервере только необратимые вспомогательные данные и хеши виртуальных ключей.

  • Этап 4: шифрованный OSDP внутри периметра.

Техника защищена полностью. Осталось самое ненадёжное звено — люди :)

Слабое звено

Люди это тоже доверенные объекты. Посмотрим, что Н3 с легитимными учётками реально может сделать в каждой роли (администратор или кадровик).

Сначала администратор СКУД. Он видит события прохода, пользователей и конфигурацию. Но подделать проход он не может. Сам сервер отрезан от внешней сети. RDP и веб-интерфейс администрирования доступны только из выделенного служебного сегмента, каждый вход по MFA. Критические команды («открыть все двери», «удалить шаблон») уходят в SIEM как тревога. Резервная копия БД — изолированное холодное хранилище, на которое у любого администратора нет прав записи или удаления. Теперь кадровик. Выгрузить базу шаблонов он не может. Доступа к БД сервера нет, а доступен только интерфейс регистрации, и тот с MFA токеном.

Допустим, базу всё-таки выгрузили полностью. Например, туда попали таблица protected_templates, кортеж ~110 байт, 10 000 сотрудников — около 1,1 МБ. Зловреду это не даёт ничего. Перед ним необратимые вспомогательные данные под масками проекций и хеши виртуальных ключей, а не биометрия.

Остаётся сценарий из модели угроз - вредоносная заплатка в логике авторизации. Закрывает его триада. Контроль целостности: релиз серверного ПО подписывается ключом. Неподписанный бинарник сервер не примет. Конвейер сборки у производителя: SAST/DAST на каждой версии. Сегментация: даже скомпрометированный сервер СКУД не пересекается с корпоративной сетью. Разделение полномочий доходит и до ИТ-ролей: администратор СУБД, сетевая инфраструктура и сервер СКУД — это разные люди, ни один из которых в одиночку не может выполнить цикл «заплатка - бэкап - сеть».

Против «двойника» при регистрации работают два механизма: сверка личности по документу и двойной контроль. Привилегированные операции (регистрация, перевыпуск ключей, ротация проекций) подтверждаются вторым оператором. Двухфакторная аутентификация здесь жёсткая: режим регистрации открывается только по квалифицированному токену (Рутокен, JaCarta или аналоги). Общих учётных данных не существует в принципе, его просто нет.

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

Вывод. «Доверие оператору» мы заменили MFA токенами, правилом двух лиц и неизменяемым аудитом.

Точка

Зашифровано / подписано

Чему доверяем

Почему приемлемо

Сенсор

Проверка liveness (модуль компьютерного зрения, вне криптоцепочки)

Модулю liveness

Остаточный риск, объявлен в МУиН

Изолированная зона терминала

Парковый мастер-ключ (чтение запрещено), ключи кэша и транспорта — только в NVM SE/TEE; биометрических данных в персистентной памяти нет ни в одном терминале парка

Ядру SE и корню ROM (TCB)

Аппаратно неизвлекаемо; OTP-блокировка отладочных линий; реакция на вскрытие; исходный вектор живёт в ОЗУ TEE только время захвата и вычисления дескриптора

Кэш терминала

Область — одна дверь: вспомогательные данные горячего подмножества + короткоживущие билеты; AES-256 / «Кузнечик», подпись сервера на запись

SE (ключ) и серверу (подпись)

Без ключа из SE флеш — это шум; украдено подмножество одной двери, а не база компании

Канал

mTLS, уникальные сертификаты, nonce+ts, подписанные дельты

Корню CA на сервере

Сертификаты вписаны при развертывании

БД сервера

Кортеж {user_id, c, T, k, key_hash}; оригинального вектора в нём нет; хранилище зашифровано, ключи в HSM

Серверной комнате и админу БД

Вспомогательные данные необратимы без MK, файл БД — шум; копия сервера без физических HSM и чипов ничего не даёт

Софт сервера

Подписанный релиз из сборочной среды; SAST/DAST в CI

Сборочной среде (ключ подписи) и процессу сборки

Неподписанная «заплатка» не развернётся; сегментация не даёт ей уйти за периметр СКУД

Замок

OSDP v2.2 с шифрованием; реле в периметре

Физическому периметру

Wiegand в публичной зоне запрещён

Люди

Токены, двойной контроль, правило двух лиц, криптографические метки времени

Человеку с MFA токеном

Каждое действие — в неизменяемом аудите

Вместо заключения

У нас получилась защита «военного уровня» — звучит иронично, но удобно. Надеюсь, вам понравилось.

Главное, что весь стек — наша собственная разработка. Защита в нём закладывается в архитектуру с самого начала. Мы зашифровали всё, что можно проверить: канал — криптографией и подписями, кэш — шифрованием с неизвлекаемыми ключами, прошивку — Secure Boot, журнал — метками времени, действия людей — токенами и аудитом. Доверять пришлось только тому, что нельзя заменить: корню в ROM, ядру SE и авторизованному человеку с Рутокеном или JaCartой.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.

Шифровать нельзя доверять: как защитить биометрию вен ладони на пути от сенсора до сервера СКУД — KioskNews