Verification d'empreintes digitales en ASP.NET Core avec SourceAFIS (correspondance 1:1)
Tôt ou tard, un backend doit répondre à une question difficile : cette empreinte digitale est-elle bien celle de la personne enregistrée précédemment ? Un système de pointage, un contrôle KYC, un agent qui vérifie l'identité d'un client, une connexion à haute sécurité. On a l'impression d'entrer en territoire réservé aux éditeurs, le genre de chose pour laquelle on achète un SDK hors de prix. Pour le cas de base, la vérification 1:1, ce n'est pas le cas. Vous pouvez avoir un prototype fonctionnel en une après-midi avec une bibliothèque open source.
Cet article détaille ce prototype avec ASP.NET Core et SourceAFIS : comment une empreinte digitale devient un template, comment deux templates sont comparés, comment choisir un seuil, et comment envelopper tout ça dans une API minimaliste. C'est volontairement simpliste. À la fin, je serai honnête sur tout ce qu'un système réel et scalable exige en plus.
Vérification 1:1 vs identification 1:N
Commençons par délimiter le sujet, car ce sont deux problèmes très différents.
- La vérification 1:1 répond à : cette empreinte est-elle bien celle du doigt enregistré pour cet utilisateur précis ? Une empreinte candidate, un template stocké. C'est ce que fait une connexion par empreinte digitale.
- L'identification 1:N répond à : à qui appartient cette empreinte, parmi tous les individus de la base ? Une empreinte candidate comparée à des millions de templates. Un problème différent, et bien plus difficile.
Cet article ne traite que du 1:1. On reviendra sur le 1:N à la fin.
Le modèle mental
Tout se résume à quatre étapes :
fingerprint image -> template -> match score -> threshold -> match / no match
- Un template est l'ensemble compact de caractéristiques (minuties) extraites d'une image d'empreinte digitale. On stocke le template, jamais l'image brute.
- La comparaison de deux templates produit un score de similarité (un nombre).
- Ce score est comparé à un seuil pour décider s'il y a correspondance ou non.
C'est tout. SourceAFIS s'occupe des calculs complexes pour les trois premières étapes ; le seuil et la plomberie restent à votre charge.
Mise en place
Créez un projet API et ajoutez SourceAFIS :
dotnet new web -n FingerprintDemo
cd FingerprintDemo
dotnet add package SourceAFIS
SourceAFIS v3 ne décode ni le PNG ni le JPEG. Il attend des pixels bruts en niveaux de gris 8 bits. On ajoute donc une bibliothèque d'images pour décoder les fichiers reçus :
dotnet add package SixLabors.ImageSharp
Remarque : la forme exacte du constructeur SourceAFIS et de ses options a changé selon les versions majeures. Si une signature ci-dessous ne correspond pas à votre version, consultez la documentation de SourceAFIS, les concepts restent identiques.
De l'image au template
Décodez le fichier uploadé en niveaux de gris, puis construisez un template. Indiquez à SourceAFIS la résolution du scan (500 DPI est la valeur par défaut courante pour les capteurs d'empreintes), car elle influe sur l'extraction des caractéristiques.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SourceAFIS;
// Decode an uploaded PNG/JPEG into raw 8-bit grayscale pixels.
static (int width, int height, byte[] pixels) DecodeGrayscale(byte[] fileBytes)
{
using var image = Image.Load<L8>(fileBytes); // L8 = 8 bits luminance
var pixels = new byte[image.Width * image.Height];
image.CopyPixelDataTo(pixels);
return (image.Width, image.Height, pixels);
}
// Turn grayscale pixels into a SourceAFIS template.
static FingerprintTemplate Extract(int width, int height, byte[] grayscale)
{
var options = new FingerprintImageOptions { Dpi = 500 };
var image = new FingerprintImage(width, height, grayscale, options);
return new FingerprintTemplate(image);
}
Enrôlement : extraire une fois, stocker le template
Lors de l'enrôlement, extrayez le template et sérialisez-le en tableau d'octets. Stockez ces octets, associés à l'utilisateur. Ne stockez jamais l'image de l'empreinte.
FingerprintTemplate template = Extract(width, height, grayscale);
byte[] serialized = template.ToByteArray(); // store this in your DB
Pour reconstruire un template plus tard, il suffit de repasser les octets :
var template = new FingerprintTemplate(serialized);
Vérification : comparer l'empreinte candidate au template enrôlé
À la connexion, extrayez un template à partir du scan fraîchement capturé (l'empreinte candidate), construisez un matcher à partir du template enrôlé, et obtenez un score.
static double Match(byte[] enrolledSerialized, FingerprintTemplate probe)
{
var enrolled = new FingerprintTemplate(enrolledSerialized);
var matcher = new FingerprintMatcher(enrolled);
return matcher.Match(probe); // higher score = more similar
}
Choisir un seuil
Le score n'a aucun sens tant qu'il n'est pas comparé à un seuil, et ce seuil est une décision métier, pas une constante technique.
- Augmenter le seuil : moins de fausses acceptations (l'empreinte de quelqu'un d'autre reconnue à tort), mais plus de faux rejets (l'utilisateur légitime refusé). FMR plus bas, FRR plus élevé.
- Baisser le seuil : l'inverse.
SourceAFIS calibre ses scores de sorte qu'un seuil autour de 40 corresponde à un taux de fausse acceptation d'environ 0,01 %. C'est un point de départ raisonnable :
const double Threshold = 40;
bool isMatch = score >= Threshold;
Mais point de départ est l'expression clé. Ce seuil se règle sur vos propres données, vos propres capteurs, et votre propre tolérance au risque. Une application de paiement et un pointage de salle de sport n'ont pas besoin du même seuil.
L'API minimaliste
Assemblons le tout avec deux endpoints. On utilise ici un dictionnaire en mémoire à la place d'une base de données, donc ce n'est évidemment pas du code de production.
using SourceAFIS;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// userId -> serialized enrolled template. A real app uses a database.
var store = new Dictionary<string, byte[]>();
static async Task<byte[]> ReadBody(HttpRequest req)
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
return ms.ToArray();
}
app.MapPost("/enroll/{userId}", async (string userId, HttpRequest req) =>
{
var (w, h, gray) = DecodeGrayscale(await ReadBody(req));
var template = Extract(w, h, gray);
store[userId] = template.ToByteArray();
return Results.Ok(new { enrolled = true });
});
app.MapPost("/verify/{userId}", async (string userId, HttpRequest req) =>
{
if (!store.TryGetValue(userId, out var enrolled))
return Results.NotFound(new { error = "user not enrolled" });
var (w, h, gray) = DecodeGrayscale(await ReadBody(req));
var probe = new FingerprintTemplate(
new FingerprintImage(w, h, gray, new FingerprintImageOptions { Dpi = 500 }));
double score = new FingerprintMatcher(new FingerprintTemplate(enrolled)).Match(probe);
return Results.Ok(new { score, match = score >= 40 });
});
app.Run();
Envoyez une image d'empreinte à /enroll/alice, puis un autre scan à /verify/alice, et vous récupérez un score ainsi qu'une décision de correspondance. Voilà un service de vérification d'empreinte digitale 1:1 fonctionnel.
Ce que cette version simpliste ignore délibérément
Le code ci-dessus est un fil à tirer, pas un système. Voici tout ce qui sépare cette base d'une solution que vous mettriez face à de vrais utilisateurs :
- Qualité d'image et captures ratées : empreintes partielles, floues, mouillées ou mal orientées. Les systèmes en production évaluent la qualité de la capture et demandent un nouveau scan avant même de tenter la comparaison.
- Détection du vivant et anti-usurpation : le code ci-dessus accepterait sans broncher une photocopie ou un faux en gélatine. Détecter un doigt réellement vivant est un domaine à part entière.
- Stockage sécurisé : un template est une donnée biométrique, parmi les informations personnelles les plus sensibles qui soient. Il doit être chiffré au repos, soumis à un contrôle d'accès strict, et traité conformément à des textes comme l'article 9 du RGPD ou le BIPA. On ne peut pas le traiter comme un hash de mot de passe.
- Standards et interopérabilité : les déploiements réels utilisent des templates normalisés (ISO/IEC 19794-2) et des formats d'image standardisés (WSQ), afin que les enrôlements restent valides d'un capteur ou d'un éditeur à l'autre.
- Plusieurs doigts et plusieurs modalités : enrôler plusieurs doigts, et souvent fusionner empreinte digitale et reconnaissance faciale pour plus de fiabilité.
- Le 1:N à grande échelle : comparer une empreinte candidate à des millions de templates n'est pas une simple boucle
forappelantMatch(). Cela demande du filtrage de candidats, de l'indexation, et un travail d'ingénierie soigné sur le débit, sans quoi une seule identification prend des minutes.
Ce qu'il faut en retenir
Pour une fonctionnalité, une connexion par empreinte digitale pour un utilisateur connu, le fil directeur présenté ici suffit réellement. Extraire, stocker, comparer, seuiller. Vous en comprenez maintenant chaque étape.
Transformer cela en véritable système d'identification biométrique, capable de monter en charge sur de larges populations, de résister à l'usurpation, de rester conforme, et de rester rapide en 1:N, suppose d'assembler correctement toutes les pièces manquantes. C'est un tout autre niveau de travail, une discipline à part entière. Mais tout commence exactement ici, en comprenant ce qu'est un template et ce que signifie un score de correspondance.
Si construire ce genre de système est votre problème, voici la base sur laquelle bâtir.