Ir al contenido principal
BiometricsMar 28, 2026

Construccion de sistemas de verificacion biometrica para Indonesia: arquitectura y patrones Rust

OS
Open Soft Team

Engineering Team

Construyendo el backend de verificacion biometrica SIM para Indonesia

Este articulo es la tercera parte de la serie sobre el mandato biometrico SIM de Indonesia, y se centra en las decisiones arquitectonicas practicas y la implementacion de codigo necesarias para construir un sistema de verificacion biometrica de nivel produccion. Profundizaremos en el diseno del sistema, flujos de datos, practicas de cifrado y servicios backend escalables construidos con Rust y Axum.

Vista general de la arquitectura del sistema

Un sistema de verificacion biometrica de nivel produccion consta de multiples componentes interconectados, cada uno con responsabilidades especificas:

Componentes principales

  1. Servicio de captura (SDK movil/web) — Componente frontend que maneja la captura de imagen facial y la deteccion de vida en el dispositivo
  2. API Gateway — Gestion centralizada de autenticacion, limitacion de velocidad, enrutamiento de solicitudes y terminacion TLS
  3. Motor de procesamiento biometrico — Servicio que extrae plantillas de caracteristicas biometricas de imagenes faciales
  4. Servicio de deteccion de vida — Ejecuta modelos de deteccion de vida pasiva y activa
  5. Servicio de integracion IKD — Maneja la comunicacion de verificacion 1:1 con la plataforma IKD de Indonesia
  6. Servicio de cifrado — Gestion de claves AES-256, cifrado/descifrado de plantillas
  7. Servicio de registro de auditoria — Registra todas las transacciones de verificacion para cumplimiento regulatorio
  8. Monitoreo y alertas — Salud del sistema, metricas de rendimiento y deteccion de anomalias

Arquitectura de flujo de datos

SDK cliente → API Gateway → Motor biometrico → Plataforma IKD
                ↓              ↓            ↓
      Limitacion de velocidad  Servicio crypto  Registro de auditoria
                ↓              ↓            ↓
        Cache de auth     Gestion de claves  Almacenamiento de cumplimiento

Implementacion del backend Rust

Estructura del proyecto

biometric-service/
├── Cargo.toml
├── src/
│   ├── main.rs              # Punto de entrada y configuracion del servidor
│   ├── config.rs            # Gestion de configuracion
│   ├── routes/
│   │   ├── mod.rs
│   │   ├── verify.rs        # Endpoints de verificacion
│   │   ├── health.rs        # Verificacion de salud
│   │   └── admin.rs         # Endpoints de administracion
│   ├── services/
│   │   ├── mod.rs
│   │   ├── biometric.rs     # Procesamiento biometrico
│   │   ├── liveness.rs      # Deteccion de vida
│   │   ├── ikd.rs           # Cliente plataforma IKD
│   │   ├── crypto.rs        # Operaciones de cifrado
│   │   └── audit.rs         # Registro de auditoria
│   ├── models/
│   ├── middleware/
│   └── errors.rs
├── migrations/
└── tests/

Endpoint de verificacion principal

use axum::{extract::State, Json};
use chrono::Utc;
use uuid::Uuid;

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();

    req.validate()?;

    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);
    }

    let template = state.biometric_engine
        .extract(&req.facial_image).await?;

    let encrypted = state.crypto_service
        .encrypt_template(&template).await?;

    let ikd_result = state.ikd_client
        .verify(&req.nik, &encrypted).await?;

    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,
    }))
}

Servicio de cifrado 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(),
        })
    }
}

Escalabilidad y rendimiento

Con mas de 270 millones de habitantes y 345 millones de tarjetas SIM activas, los requisitos de escala son enormes:

  • Estimacion de carga pico: Asumiendo 50 millones de nuevos registros en los primeros 6 meses, aproximadamente 278.000 verificaciones por dia
  • Horas pico: El pico puede ser 3-5x del promedio, es decir, 830.000-1.390.000 por dia
  • Solicitudes por segundo: Pico de aproximadamente 16 TPS, con margen para trafico en rafagas

Implementacion de cumplimiento UU PDP

pub async fn cleanup_expired_records(
    pool: &PgPool,
) -> Result<u64, sqlx::Error> {
    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())
}

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(),
    })
}

Recomendaciones de despliegue

Centros de datos en Indonesia

Segun el reglamento KOMDIGI y los requisitos de UU PDP, el procesamiento biometrico debe realizarse en centros de datos en Indonesia:

  • Principal: Jakarta (cerca de la mayoria de usuarios y la plataforma IKD)
  • DR: Surabaya o Bali (redundancia geografica)
  • CDN: Nodos de borde nacionales (para distribucion de SDK y recursos estaticos)

Conclusion

La construccion de un sistema de verificacion biometrica que cumpla con el reglamento KOMDIGI de Indonesia es un desafio de ingenieria complejo pero manejable. Puntos clave:

  1. Seguridad primero: cifrado AES-256, transmision TLS 1.3, arquitectura de confianza cero
  2. Impulsado por cumplimiento: proteccion de datos UU PDP, retencion de registros de auditoria de 5 anos, derecho de eliminacion del usuario
  3. Diseno escalable: escalamiento horizontal, caching multinivel, procesamiento asincrono
  4. Despliegue localizado: centros de datos indonesios, optimizacion de bajo ancho de banda, soporte de diversidad de dispositivos
  5. Monitoreo integral: metricas en tiempo real, deteccion de anomalias, informes de cumplimiento

Construir este sistema con Rust y Axum proporciona excelentes garantias de rendimiento y seguridad mientras cumple con los estrictos requisitos regulatorios de Indonesia.