Распил SDK сканера отпечатков пальцев Bio Link fs80
@webwareПредыстория
Безопасность данных всегда была, есть, и остается больной темой для всех людей и айтишников. Говорить по данной тематике можно бесконечно, иначе говоря от слов к делу. В недалёком 2014 году, когда учился на 5 курсе речь зашла о дипломе и практических навыках, полученных за всё обучение. Почему остановился на разработке, да ещё и связанной с биометрией, ответ прост – были наработки. Целью работы являлось – реализовать ПО для получения биометрических данных, иначе говоря – конечная задача свелась к получению картинки с специальными точками, по которым производится идентификация.
Что бы реализовать ПО выделил для себя основные моменты:
- минимизировать число необходимых процедур и функций, для увеличения быстродействия и удобства дальнейшего использования;
- вкратце описать модули разработчика, потому как лезть в дизассемблер очень затяжное дело, и скорее всего не получилось бы;
- разработать блок схему для общего представления о функционале;
- описать типовые решения применения.
Как видно из вышеперечисленного перечня, фронт работ обозначен. Для реализации у меня был оптический сканер fs80 и диск с коммерческим ПО BIO Link. В долгих поисках адекватно работающего SDK пришлось разочароваться, потому как столкнулся с кучей багов и ограничений разработчиков BIO Link и других вендоров. Время поджимало и остановился на официальном, бесплатно распространяемом Free Fingerprint Verification SDK. Данный комплект средств разработки поддерживает взаимодействие с множеством оптических сканеров и имеет ограничение на хранение до десяти пользователей в стандартной базе данных. Функционал SDK организован на основе алгоритма упрощенной версии VeriFinger Fingerprint Identification SDK, которая производит верификацию с эталонным качеством 1:1. SDK взаимодействует с: платформами Microsoft Windows 2000, XP, Vista, а также 32-разрядной архитектурой процессора (честно заработало и на 64- разрядном процессоре с windows 8).
После непродолжительных танцев с бубном и трассировкой Delphi – вуаля, сканы пальцев со спец точками корректно создавались. Реализация свелась к любимой всеми теории, где основными моментами стали:
Анализ биометрических данных
основываясь на законодательстве в области обработки ПДн, а именно Федеральном законе РФ "О персональных данных" (152-ФЗ), глава 2, статья 11, следует, что биометрические персональные данные – это:
- 1. Сведения, которые характеризуют физиологические и биологические особенности человека, на основании которых можно установить его личность (биометрические персональные данные) и которые используются оператором для установления личности субъекта персональных данных, могут обрабатываться только при наличии согласия в письменной форме субъекта персональных данных, за исключением случаев, предусмотренных частью 2 настоящей статьи.
- 2. Обработка биометрических персональных данных может осуществляться без согласия субъекта персональных данных в связи с реализацией международных договоров Российской Федерации о реадмиссии, в связи с осуществлением правосудия и исполнением судебных актов, а также в случаях, предусмотренных законодательством Российской Федерации об обороне, о безопасности, о противодействии терроризму, о транспортной безопасности, о противодействии коррупции, об оперативно - розыскной деятельности, о государственной службе, уголовно – исполнительным законодательством Российской Федерации, законодательством Российской Федерации о порядке выезда из Российской Федерации и въезда в Российскую Федерацию.
Исходя из официальной статистики применения биометрических методов идентификации: отпечатки пальцев – 59%; геометрия лица – 17%; радужная оболочка – 7%; геометрия руки – 7%; рисунок вен – 7%; голос – 5%; почерк – 1%; все остальное – 1%.
В качестве анализа, также можно привести экспертные оценки свойств биометрических характеристик человека:

Основные биометрические данные человека рассматриваются по 4-м критериям, а именно: Универсальность - это применение в различных задачах (контроль доступа, идентификация, аутентификация). Уникальность – свойство, зависящее от источника получаемой информации с возможностью идентификации личности 1:1. Стабильность — это способность источника данных функционировать, не меняя своих структурных свойств. Собираемость – это возможность получения биометрической характеристики от каждого индивидуума в реальном времени.
В практическом применении метода биометрических характеристик главным параметром является универсальность и уникальность той или иной технологии.
Анализ алгоритмов получения биометрических данных и методов идентификации при помощи особых точек на отпечатках пальцев;
Отпечаток пальца состоит из хребтов и впадин, которые образуют папиллярный рисунок (рисунок 1). Отпечаток имеет два вида признаков: глобальные и локальные. Глобальные признаки описывают общее положение папиллярных линий.

Локальные признаки отпечатков пальцев описывают расположение линий в окрестности минуций. Минуция - такая точка отпечатка пальца, где папиллярная линия обрывается или разделяется на две. Из этих двух типов могут быть составлены более сложные виды минуций (рисунок 2).

Особые точки на отпечатках пальца относятся к глобальным признакам и бывают двух видов: точка ядра и точка дельты. Ядро - точка отпечатка пальца, которую огибает максимальное количество папиллярных линий (рисунок 3).

Дельта - точка отпечатка пальца, вокруг которой папиллярные линии расходятся в трех разных направлениях (рисунок 4).

Локальные признаки, в отличие от глобальных, у каждого человека являются индивидуальными. Поэтому для идентификации пользуются методом распознавания по минуциям. Т.е. информация, полученная в результате съема отпечатка со сканнера, сравнивается с информацией в базе данных. Данный метод имеет ряд недостатков, таких как трудоемкость, чувствительность к локальному растяжению и плохая масштабируемость.
Также на положении особых точек основывается классификация отпечатков пальцев. Тип отпечатка пальца - еще один вид глобальных признаков. Отпечатки разделяются на три основных типа: спираль, петля и дуга (рис. 5).

Для определения особых точек на отпечатке пальца существует четыре основных подхода к определению особых точек: метод индекса Пуанкаре; метод комплексных фильтров 1 степени; метод комплексных фильтров 2 степени; метод индекса Пуанкаре с преобразованием Хафа.
Метод индекса Пуанкаре и метод комплексных фильтров 1 степени, реализованы евклидовым расстоянием между особыми точками, что является наиболее распространёнными методами. Углубляться в мат часть не вижу смысла, получится copy/past.
Адекватно описать функционал получения биометрических данных пользователя собственного ПО;
Для решения поставленных целей, будет использоваться часть комплекта разработчика, связанная с языком программирования Object Pascal: Nffv.pas; NffvUser.pas; UserList.pas; Util.pas
Рассмотрим модуль Nffv.pas, который является контейнером методов и классов для использования бесплатной версии SDK.
Вначале остановимся на типе данных для отображения статуса сканера (листинг 1):
Листинг 1.
type
TNffvStatus = ( nfesNone = 0, nfesTemplateCreated = 1,
{ временные данные сканера(подключение базы/инициализация драйвера). }
nfesNoScanner = 2, { наличие сканнера }
nfesScannerTimeout = 3, { сканер находится в ожидании}
nfesUserCanceled = 4, { отменение действий пользователем }
nfesQualityCheckFailed = 100 { наличие ошибок}
);
Базовым классом для отображения процедур и функций, с которыми взаимодействует программа, является TNffv = class (листинг 2):
Листинг 2.
type
TNffv = class
private
public
Constructor Create(databaseName: string; password: string); overload;
{ Создание нового экземпляра обьекта TNffv(параметры базы данных, пароля и модулей сканера). }
Constructor Create(databaseName: string; password: string; scannerModules: string); overload;
{конструктор, инициализирующий сканер, от параметров //обьекта TNffv}
Destructor Destroy; override;
{деструктор объекта}
procedure RemoveUsers;
{очистка стандартной базы данных}
procedure RemoveUser(index: LongInt);
{удаление пользователя по индексу в базе}
function GetUserCount: LongInt;
{Получение номера пользователей контейнера Nffv}
function GetUserByIndex(index: LongInt): TNffvUser;
{возвращает индекс выбранного пользователя}
function GetUserById(id: LongInt): TNffvUser;
{возвращает идентификатор выбранного пользователя}
function GetUserIndexById(id: LongInt): LongInt;
{возвращает индекс параметром идентификатора}
procedure Cancel;
{Отменна сканирования отпечатков}
function Enroll(timeout: LongWord; var engineStatus: TNffvStatus): TNffvUser;
{Получение отпечатка }
function Verify(user: TNffvUser; timeout: LongWord; var engineStatus: TNffvStatus): LongInt;
{Сравнение пользователей}
function GetQualityThreshold: Byte;
{Порог качества изображения}
procedure SetQualityThreshold(threshold: Byte);
{Выставляем порог качества}
function GetMatchingThreshold: LongInt;
{Устанавливаем минимальное значение соответствия схожести}
procedure SetMatchingThreshold(threshold: LongInt);
{Минимальное значение схожести отпечатков}
end;
function GetAvailableScannerModules(): String;
{Обьявление модулей сканера}
function NffvGetInfo(): TNLibraryInfo;
{обьявление Nffv библиотеки}
function EngineStatusString(status: TNffvStatus): string;
{Строковое сообщение о TNffv статусе аппарата}
procedure NffvFreeMemory(point: PChar); stdcall; external dllName;
{высвобождаем память созданную в работе движка}
Рассмотрим модуль NffvUser.pas, содержащий класс TNffvUser для работы с пользователем (листинг 3):
Листинг 3.
type
TNffvUser = class
private
_handle: Pointer;
public
Constructor Create(handle: Pointer);
{Создаём новый экземпляр TNffvUser}
property Handle: Pointer read _handle;
{метод хэндла}
function GetId(): LongInt;
{Получаем идентификатор пользователя}
function GetImage(): TBitmap;
{Получаем изображение отпечатка пальца}
Рассмотрим модуль UserList.pas. Основное назначение модуля взаимодействия со стандартной базой данных (листинг 4):
Листинг 4.
type
TUser = class
{класс для работы с пользователями}
Public
UserName: string; {имя пользователя}
UserID: LongInt; {идентификатор пользователя}
constructor Create(const Name : String;
{конструктор класса}
const ID : Integer);
{идентификатор пользователя}
end;
Рассмотрим класс, отображающий процедуры и функции, для работы с пользователями является TUser = class (листинг 5):
Листинг 5.
TUserDatabase = class(TList)
{класс для работы со стандартной базой данных}
private
_databaseName: String;{привязка ini файла к базе данных}
public
procedure Save;
{процедура сохранения ланных пользователя}
function GetById(id: LongInt): TUser;
{получение идентификатора}
Constructor Create(databaseName: string);
{конструктор класса}
Destructor Destroy; override;
{деструктор класса}
end;
В модуле Util.pas, рассматривается: исполнение программы, возникновение ошибки, определение рабочей платформы из под которой происходит запуск программы и взаимодействие с минуцией (листинг 6):
Листинг 6.
type
TNLibraryInfo = record
{запись о разработчиках SDK}
Title: array[0..63] of Char;{экспорт названия}
Product: array[0..63] of Char;{продукт}
Company: array[0..63] of Char;{компания}
Copyright: array[0..63] of Char;{авторское право}
VersionMajor: Integer;{главная версия}
VersionMinor: Integer;{порядковая версия}
VersionBuild: Integer;{версия сборки}
VersionRevision: Integer;{версия доработки}
DistributorId: Integer;{идентификатор дистрибьютора }
SerialNumber: Integer;{серийный номер}
end;
function Succeeded(Res: Integer): Boolean;
{функция вызываемая при успешном выполнении работы программы}
function Failed(Res: Integer): Boolean;
{функция вызываемая при выполнении //неудачного сканирования}
procedure RaiseError(msg: String; Err: Integer); overload;
{процедура исполняемая при возникновении ошибки}
procedure RaiseError(msg: String); overload;
{процедура исполняемая при возникновении ошибки}
function GetOS: String;
{функция получения рабочей платформы}
function MatchingThresholdToString(value: Integer): string;
{перевод значения минуции в строковый параметр}
function MatchingThresholdFromString(value: string): Integer;
{вызов параметра минуции из строки}
Рассмотрим основной функционал, который будет использован в собственном программном обеспечении. Переменная Engine, класса TNffv, заключает в себе работу базового класса отображения процедур и функций, с которыми взаимодействует программа (листинг 7):
Функция Failed выдаёт ошибку, в случае если Res < 0
Листинг 7.
function Failed(Res: Integer): Boolean; begin Result := Res < 0; end;
Функция GetAvailableScannerModules, класса TNffv, получает доступ к модулям сканера(листинг 8):
Листинг 8.
function GetAvailableScannerModules(): String;
var res: Integer;{}
str: PChar;
str2: String;
resultString: String;
begin
res := NffvGetAvailableScannerModulesA(str);
{в переменную res записываем строковый параметр срабатывания сканера}
str2 := string(str);
{переводим значение указателя в строковый параметр}
resultString := str2;
{в результирующую строку записываем строку с значением параметра}
if (Failed(res)) then
begin
RaiseError('невозможно получить доступ к модулям сканера.', res);
end;
{обработка ошибки}
Result := resultString;
NffvFreeMemory(str);
end;
В случае возникновения ошибки при обращении к данной функции выводится сообщение об ошибке «невозможно получить доступ к модулям сканера».
Для того чтобы подключить сканнер Futronic Fs80, прописываем код(листинг 9):
Листинг 9.
Engine := TNffv.Create('fingers.db', '', 'Futronic');
Создается новый экземпляр класса TNffv от названия базы без пароля с выбранным сканером.
Engine.RemoveUsers чистка стандартной базы данных приведена процедурой RemoveUsers(листинг 10):
Листинг 10.
procedure TNffv.RemoveUsers;
var res: Integer;
begin
res := NffvClearUsers();
{переменной присваиваем обнуление базы данных}
if (Failed(res)) then
begin
RaiseError('удаление пользователей невозможно.', res);
end;
{при возникновении ошибки выводится сообщение «удаление пользователей невозможно»}
end;
NffvClearUsers – функция очистки базы данных импортируемая из библиотеки разработчика SDK.Сканирование отпечатка произведено функцией добавления пользователя с параметром ожидания сканера в 2 секунды и статусом добавления пользователя(листинг 11):
Листинг 11.
_user := Engine.Enroll(2000, _engineStatus);
Функция добавления пользователя TNffv.Enroll(листинг 12):
Листинг 12.
function TNffv.Enroll(timeout: Cardinal; var engineStatus: TNffvStatus): TNffvUser;
var res: Integer;
engStatus: Integer;
handle: Pointer;
begin
res := NffvEnroll(timeout, engStatus, handle);
{значение переменной рассчитано из времени, значения статуса сканирования, и значения хэндла}
if (Failed(res)) then
begin
RaiseError(' добавление пользователя невозможно.', res);
end;
{при возникновении ошибки выводится сообщение «добавление пользователя невозможно»}
engineStatus := TNffvStatus(engStatus);
{значение статуса = числовому значению состояния сканирования}
if (engineStatus = nfesTemplateCreated) then
{если числовое значение статуса = значению созданного шаблона }
begin
Result := TNffvUser.Create(handle);
{создаём пользователя по его хэндлу}
end
else
Result := nil;
{иначе в результат записывается нулевое значение}
end;
_user.GetImage получение картинки отпечатка пальца функцией GetImage(листинг 13):
Листинг 13.
function TNffvUser.GetImage: TBitmap;
var res: Integer;
nBitmap: TBitmap;
pBitmap: HBitmap;
begin
res := NffvUserGetHBitmap(_handle, pBitmap);
{получаем числовое значение картинки от _handle и pBitmap}
if (Failed(res)) then
begin
RaiseError('получить изображение невозможно.', res);
{при возникновении ошибки выводится сообщение «получить изображение невозможно»}
end;
nBitmap := TBitmap.Create;
nBitmap.Handle := pBitmap;
Result := nBitmap;
end;
Картинка получается из функции NffvUserGetHBitmap, экспортируемой из библиотеки разработчика. В приведенных выше листингах отражен основной функционал корректной работы со сканером fs80. Софтина получилась довольно таки простая, использовал кнопку(t.Button) - для получения скана отпечатка и поле ввода текста(t.Edit) – для ввоода названия скана.
И непосредственно сам обработчик.
Листинг 14.
procedure TForm2.Button1Click(Sender: TObject);
begin
Engine := nil;
_allModuleString := GetAvailableScannerModules;
try
Engine := TNffv.Create('fingers.db', '', 'Futronic'); //Подключаем наш //сканер
Engine.RemoveUsers; //Чистим базу
_user := Engine.Enroll(2000, _engineStatus); //Сканируем //отпечаток
if (_engineStatus = nfesTemplateCreated) then //Если //сосканировался делаем дальше
begin
Bitmap := _user.GetImage; //Получаем картинку и пишем ее в //имадж
randomize;
bitmap.SaveToFile(Edit1.Text); //сохраняем ее в папке с проэктом с //именем пользователя
Engine.RemoveUsers;
FreeAndNil(Engine); //очищаем память движка
ShowMessage(‘… Done’);
end
else {какой error} ;
except
MessageDlg(‘невозможно запустить сканер или создать/загрузить базу данных.’ + #13#10 +
‘пожалуйста проверьте:’ + #13#10 + ‘ – правильно ли введён пароль;’ + #13#10 + ‘ – правильность названия базы данных;’ + #13#10 +‘ – правильно ли подключен сканер.’, mtError, [mbOK], 0);
FreeAndNil(Engine);
Exit;
end;
end;
С моей точки зрения софтина проста и от части уникальна потому, что получение информации производится перехватом получаемого изображения на стадии обработки данных стандартного функционала программы. Судите строго или матом ругайтесь –ваше право, потому как заниматься разработкой с 0 – не было времени, но костыль решил проблему. К сожалению сверку с полученным сканом не предоставлю, потому как инфа на другом винте, который сдох.
Рассмотреть типовые решения применения сканера отпечатков пальцев;
Типовыми решениями могут быть: реализация многофакторной аутентификации, доступ к информации\ресурсу, физический доступ, интеграция со сторонней б\д для поиска информации о человеке, и тд. Забыл добавить, драйвера для корректной работы fs80 с помощью самописной софтины либо SDK подходят не все, надо экспериментировать, какие-то находил рабочие.
Если у кого будут вопросы консультационного характера, с радостью отвечу на все вопросы.