بناء أنظمة التحقق البيومتري لإندونيسيا: البنية وأنماط Rust
Engineering Team
بناء الخادم الخلفي للتحقق البيومتري من SIM في إندونيسيا
هذا المقال هو الجزء الثالث من سلسلة تفويض التحقق البيومتري لبطاقات SIM في إندونيسيا، ويركز على القرارات المعمارية العملية وتنفيذ الكود اللازم لبناء نظام تحقق بيومتري بمستوى الإنتاج. سنتعمق في تصميم النظام وتدفقات البيانات وممارسات التشفير والخدمات الخلفية القابلة للتوسع المبنية بـ Rust وAxum.
نظرة عامة على بنية النظام
يتكون نظام التحقق البيومتري بمستوى الإنتاج من عدة مكونات مترابطة، لكل منها مسؤوليات محددة:
المكونات الأساسية
- خدمة الالتقاط (SDK للهاتف/الويب) — مكون الواجهة الأمامية الذي يتعامل مع التقاط صورة الوجه وكشف الحيوية على الجهاز
- بوابة API — إدارة مركزية للمصادقة وتحديد معدل الطلبات وتوجيه الطلبات وإنهاء TLS
- محرك المعالجة البيومترية — خدمة تستخرج قوالب السمات البيومترية من صور الوجه
- خدمة كشف الحيوية — تشغل نماذج كشف الحيوية السلبية والنشطة
- خدمة التكامل مع IKD — تتعامل مع اتصالات التحقق 1:1 مع منصة IKD الإندونيسية
- خدمة التشفير — إدارة مفاتيح AES-256، تشفير/فك تشفير القوالب
- خدمة سجل التدقيق — تسجل جميع معاملات التحقق للامتثال التنظيمي
- المراقبة والتنبيهات — صحة النظام ومقاييس الأداء وكشف الشذوذ
بنية تدفق البيانات
SDK العميل → بوابة API → المحرك البيومتري → منصة IKD
↓ ↓ ↓
تحديد المعدل خدمة التشفير سجل التدقيق
↓ ↓ ↓
ذاكرة المصادقة إدارة المفاتيح تخزين الامتثال
تنفيذ الخادم الخلفي بـ Rust
هيكل المشروع
biometric-service/
├── Cargo.toml
├── src/
│ ├── main.rs # نقطة الدخول وإعداد الخادم
│ ├── config.rs # إدارة التكوين
│ ├── routes/
│ │ ├── mod.rs
│ │ ├── verify.rs # نقاط نهاية التحقق
│ │ ├── health.rs # فحص الصحة
│ │ └── admin.rs # نقاط نهاية الإدارة
│ ├── services/
│ │ ├── mod.rs
│ │ ├── biometric.rs # المعالجة البيومترية
│ │ ├── liveness.rs # كشف الحيوية
│ │ ├── ikd.rs # عميل منصة IKD
│ │ ├── crypto.rs # عمليات التشفير
│ │ └── audit.rs # سجل التدقيق
│ ├── models/
│ ├── middleware/
│ └── errors.rs
├── migrations/
└── tests/
نقطة نهاية التحقق الأساسية
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();
// 1. التحقق من الطلب
req.validate()?;
// 2. كشف الحيوية
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. استخراج القالب البيومتري
let template = state.biometric_engine
.extract(&req.facial_image)
.await?;
// 4. تشفير القالب للنقل
let encrypted = state.crypto_service
.encrypt_template(&template)
.await?;
// 5. التحقق 1:1 مع IKD
let ikd_result = state.ikd_client
.verify(&req.nik, &encrypted)
.await?;
// 6. تسجيل سجل التدقيق
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,
}))
}
خدمة تشفير AES-256
وفقًا لمتطلبات UU PDP ولائحة KOMDIGI، يجب تشفير جميع القوالب البيومترية بـ 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(),
})
}
}
قابلية التوسع والأداء
تحدي الحجم الإندونيسي
مع أكثر من 270 مليون نسمة و345 مليون بطاقة SIM نشطة، فإن متطلبات التوسع لنظام التحقق البيومتري هائلة:
- تقدير حمل الذروة: بافتراض 50 مليون تسجيل جديد في أول 6 أشهر، حوالي 278,000 عملية تحقق يوميًا في المتوسط
- ساعات الذروة: بالنظر إلى أنماط ساعات العمل الإندونيسية، قد تكون الذروة 3-5 أضعاف المتوسط، أي 830,000 إلى 1,390,000 يوميًا
- الطلبات في الثانية: ذروة تقريبية 16 TPS، مع الحاجة لهامش لحركة المرور المفاجئة
استراتيجية التوسع الأفقي
#[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();
}
تنفيذ الامتثال لـ UU PDP
سياسة الاحتفاظ بالبيانات
/// مهمة مجدولة: تنظيف سجلات التدقيق المنتهية
pub async fn cleanup_expired_records(
pool: &PgPool,
) -> Result<u64, sqlx::Error> {
// متطلب UU PDP: الاحتفاظ بسجلات التحقق لمدة 5 سنوات
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(),
})
}
توصيات النشر
مراكز البيانات في إندونيسيا
وفقًا للائحة KOMDIGI ومتطلبات UU PDP، يجب أن تتم المعالجة البيومترية في مراكز بيانات في إندونيسيا. المواقع الموصى بها:
- الرئيسي: جاكرتا (قرب من غالبية المستخدمين ومنصة IKD)
- التعافي من الكوارث: سورابايا أو بالي (التكرار الجغرافي)
- CDN: عقد حافة وطنية (لتوزيع SDK والموارد الثابتة)
الخلاصة
بناء نظام تحقق بيومتري متوافق مع لائحة KOMDIGI في إندونيسيا هو تحدٍ هندسي معقد لكن قابل للإدارة. النقاط الرئيسية:
- الأمان أولاً: تشفير AES-256، نقل TLS 1.3، بنية عدم الثقة
- مدفوع بالامتثال: حماية بيانات UU PDP، الاحتفاظ بسجلات التدقيق لمدة 5 سنوات، حق حذف المستخدم
- تصميم قابل للتوسع: التوسع الأفقي، التخزين المؤقت متعدد المستويات، المعالجة غير المتزامنة
- نشر محلي: مراكز بيانات إندونيسية، تحسين عرض النطاق المنخفض، دعم تنوع الأجهزة
- مراقبة شاملة: مقاييس في الوقت الفعلي، كشف الشذوذ، تقارير الامتثال
بناء هذا النظام بـ Rust وAxum يوفر ضمانات أداء وأمان ممتازة مع تلبية المتطلبات التنظيمية الصارمة في إندونيسيا.