Construction de systemes de verification biometrique pour l'Indonesie : architecture et patterns Rust
Engineering Team
Construire le backend de verification biometrique SIM pour l’Indonesie
Cet article est la troisieme partie de la serie sur le mandat biometrique SIM en Indonesie, et se concentre sur les decisions architecturales pratiques et l’implementation de code necessaires pour construire un systeme de verification biometrique de niveau production. Nous plongerons dans la conception du systeme, les flux de donnees, les pratiques de chiffrement et les services backend evolutifs construits avec Rust et Axum.
Vue d’ensemble de l’architecture systeme
Un systeme de verification biometrique de niveau production est compose de plusieurs composants interconnectes, chacun ayant des responsabilites specifiques :
Composants principaux
- Service de capture (SDK mobile/web) — Composant frontend gerant la capture d’image faciale et la detection de vivacite sur appareil
- Passerelle API — Gestion centralisee de l’authentification, limitation de debit, routage des requetes et terminaison TLS
- Moteur de traitement biometrique — Service extrayant les modeles de caracteristiques biometriques des images faciales
- Service de detection de vivacite — Execute les modeles de detection de vivacite passive et active
- Service d’integration IKD — Gere la communication de verification 1:1 avec la plateforme IKD indonesienne
- Service de chiffrement — Gestion des cles AES-256, chiffrement/dechiffrement des modeles
- Service de journal d’audit — Enregistre toutes les transactions de verification pour la conformite reglementaire
- Surveillance et alertes — Sante du systeme, metriques de performance et detection d’anomalies
Architecture de flux de donnees
SDK client → Passerelle API → Moteur biometrique → Plateforme IKD
↓ ↓ ↓
Limitation de debit Service crypto Journal d'audit
↓ ↓ ↓
Cache d'auth Gestion de cles Stockage conformite
Implementation du backend Rust
Structure du projet
Nous recommandons la structure de projet Rust suivante pour le service de verification biometrique :
biometric-service/
├── Cargo.toml
├── src/
│ ├── main.rs # Point d'entree et configuration du serveur
│ ├── config.rs # Gestion de la configuration
│ ├── routes/
│ │ ├── mod.rs
│ │ ├── verify.rs # Points de terminaison de verification
│ │ ├── health.rs # Verification de sante
│ │ └── admin.rs # Points de terminaison d'administration
│ ├── services/
│ │ ├── mod.rs
│ │ ├── biometric.rs # Traitement biometrique
│ │ ├── liveness.rs # Detection de vivacite
│ │ ├── ikd.rs # Client plateforme IKD
│ │ ├── crypto.rs # Operations de chiffrement
│ │ └── audit.rs # Journal d'audit
│ ├── models/
│ │ ├── mod.rs
│ │ ├── verification.rs # Requete/reponse de verification
│ │ └── audit.rs # Enregistrement d'audit
│ ├── middleware/
│ │ ├── mod.rs
│ │ ├── auth.rs # Authentification
│ │ └── rate_limit.rs # Limitation de debit
│ └── errors.rs # Types d'erreur
├── migrations/
└── tests/
Point de terminaison de verification principal
use axum::{extract::State, Json};
use chrono::Utc;
use uuid::Uuid;
/// Point de terminaison de verification principal — gere le flux complet de verification biometrique
pub async fn verify_biometric(
State(state): State<AppState>,
Json(req): Json<VerificationRequest>,
) -> Result<Json<VerificationResponse>, AppError> {
let transaction_id = Uuid::new_v4();
let started_at = Utc::now();
// 1. Validation de la requete
req.validate()?;
// 2. Detection de vivacite
let liveness = state.liveness_service
.detect(&req.capture_data)
.await
.map_err(|e| {
state.audit.log_failure(
transaction_id, "liveness_failed", &e
);
e
})?;
if !liveness.is_live {
return Err(AppError::LivenessCheckFailed);
}
// 3. Extraction du modele biometrique
let template = state.biometric_engine
.extract(&req.facial_image)
.await?;
// 4. Chiffrement du modele pour la transmission
let encrypted = state.crypto_service
.encrypt_template(&template)
.await?;
// 5. Verification IKD 1:1
let ikd_result = state.ikd_client
.verify(&req.nik, &encrypted)
.await?;
// 6. Enregistrement du journal d'audit
let elapsed = Utc::now() - started_at;
state.audit.log_verification(AuditRecord {
transaction_id,
nik_hash: hash_nik(&req.nik),
liveness_score: liveness.confidence,
match_score: ikd_result.score,
verified: ikd_result.matched,
duration_ms: elapsed.num_milliseconds(),
timestamp: started_at,
}).await?;
Ok(Json(VerificationResponse {
transaction_id,
verified: ikd_result.matched,
confidence: ikd_result.score,
}))
}
Service de chiffrement AES-256
Conformement aux exigences de la UU PDP et du reglement KOMDIGI, tous les modeles biometriques doivent etre chiffres avec AES-256 :
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
use aes_gcm::aead::Aead;
use rand::RngCore;
pub struct CryptoService {
cipher: Aes256Gcm,
}
impl CryptoService {
pub fn new(key: &[u8; 32]) -> Self {
let cipher = Aes256Gcm::new_from_slice(key)
.expect("AES-256 key must be 32 bytes");
Self { cipher }
}
pub async fn encrypt_template(
&self,
template: &BiometricTemplate,
) -> Result<EncryptedTemplate, CryptoError> {
let mut nonce_bytes = [0u8; 12];
rand::thread_rng().fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
let plaintext = bincode::serialize(template)?;
let ciphertext = self.cipher
.encrypt(nonce, plaintext.as_ref())
.map_err(|_| CryptoError::EncryptionFailed)?;
Ok(EncryptedTemplate {
ciphertext,
nonce: nonce_bytes.to_vec(),
algorithm: "AES-256-GCM".into(),
})
}
}
Scalabilite et performance
Le defi de l’echelle indonesienne
Avec plus de 270 millions d’habitants et 345 millions de cartes SIM actives, les besoins de scalabilite du systeme de verification biometrique sont enormes :
- Estimation de charge de pointe : En supposant 50 millions de nouvelles inscriptions dans les 6 premiers mois, soit environ 278 000 verifications par jour en moyenne
- Heures de pointe : En tenant compte des horaires de travail indonesiens, le pic peut etre 3 a 5 fois la moyenne, soit 830 000 a 1 390 000 par jour
- Requetes par seconde : Environ 16 TPS en pointe, mais il faut prevoir une marge pour les pics de trafic
Strategie de scalabilite horizontale
#[tokio::main]
async fn main() {
let config = Config::from_env();
let pool = PgPoolOptions::new()
.max_connections(config.db_max_connections)
.min_connections(config.db_min_connections)
.acquire_timeout(Duration::from_secs(3))
.connect(&config.database_url)
.await
.expect("Failed to create pool");
let app = Router::new()
.route("/api/v1/verify", post(verify_biometric))
.route("/health", get(health_check))
.layer(RateLimitLayer::new(config.rate_limit))
.layer(TimeoutLayer::new(Duration::from_secs(10)))
.with_state(AppState::new(pool, config));
let listener = TcpListener::bind(&config.bind_addr)
.await
.expect("Failed to bind");
axum::serve(listener, app).await.unwrap();
}
Implementation de la conformite UU PDP
Politique de conservation des donnees
/// Tache planifiee : nettoyage des enregistrements d'audit expires
pub async fn cleanup_expired_records(
pool: &PgPool,
) -> Result<u64, sqlx::Error> {
// Exigence UU PDP : conservation des journaux de verification pendant 5 ans
let cutoff = Utc::now() - chrono::Duration::days(5 * 365);
let result = sqlx::query(
"DELETE FROM audit_logs WHERE created_at < $1"
)
.bind(cutoff)
.execute(pool)
.await?;
Ok(result.rows_affected())
}
/// Demande de suppression de donnees utilisateur (droit a l'oubli)
pub async fn handle_deletion_request(
pool: &PgPool,
nik_hash: &str,
) -> Result<DeletionReport, AppError> {
let mut tx = pool.begin().await?;
let templates_deleted = sqlx::query(
"DELETE FROM biometric_templates WHERE nik_hash = $1"
)
.bind(nik_hash)
.execute(&mut *tx)
.await?
.rows_affected();
let logs_anonymized = sqlx::query(
"UPDATE audit_logs SET nik_hash = 'anonymized' WHERE nik_hash = $1"
)
.bind(nik_hash)
.execute(&mut *tx)
.await?
.rows_affected();
tx.commit().await?;
Ok(DeletionReport {
templates_deleted,
logs_anonymized,
completed_at: Utc::now(),
})
}
Recommandations de deploiement
Centres de donnees en Indonesie
Conformement au reglement KOMDIGI et aux exigences UU PDP, le traitement biometrique doit etre effectue dans des centres de donnees en Indonesie. Emplacements recommandes :
- Principal : Jakarta (proximitaire de la majorite des utilisateurs et de la plateforme IKD)
- DR : Surabaya ou Bali (redondance geographique)
- CDN : Noeuds de bordure nationaux (pour la distribution du SDK et les ressources statiques)
Conclusion
La construction d’un systeme de verification biometrique conforme au reglement KOMDIGI de l’Indonesie est un defi d’ingenierie complexe mais gerable. Points cles :
- Securite d’abord : chiffrement AES-256, transmission TLS 1.3, architecture zero confiance
- Pilote par la conformite : protection des donnees UU PDP, conservation des journaux d’audit sur 5 ans, droit de suppression des utilisateurs
- Conception scalable : scalabilite horizontale, mise en cache multi-niveaux, traitement asynchrone
- Deploiement localise : centres de donnees indonesiens, optimisation basse bande passante, support de la diversite des appareils
- Surveillance complete : metriques en temps reel, detection d’anomalies, rapports de conformite
Construire ce systeme avec Rust et Axum offre d’excellentes garanties de performance et de securite tout en repondant aux exigences reglementaires strictes de l’Indonesie.