בעת פיתוח ארנק קריפטו למובייל, אחת הבקשות הנפוצות ביותר היא הגנה ביומטרית על עסקאות. אך אינטגרציה שטחית של Face ID או טביעת אצבע יוצרת אשליית ביטחון. ראינו פרויקטים שבהם, לאחר דחייה ביומטרית, העסקה עדיין עברה דרך עקיפה. אפליקציה כזו לא תעבור ביקורת אבטחה ועלולה להוביל לאובדן כספים. במאמר זה נסביר כיצד ליישם כראוי ביומטריה עם קישור קריפטוגרפי למפתח הפרטי — הדרך האמינה היחידה להגן על עסקאות.
הגנה ביומטרית על עסקאות: מדוע ממשק UI-gate אינו מאובטח
ממשק UI-gate פירושו שביומטריה משמשת רק לפתיחת כפתור "אישור", בעוד המפתח הפרטי נמצא ב-Keychain ללא הגנה ביומטרית. תוקף יכול לעקוף את הבדיקה באמצעות כלי hooking כמו Frida או Objection, על ידי תיקון המתודה evaluatePolicy להחזיר true. כתוצאה מכך, העסקה מאושרת ללא אימות אמיתי. עבור ארנקי קריפטו עם נכסים אמיתיים, זו פגיעות קריטית.
כיצד קישור קריפטוגרפי פותר את הבעיה — יישום הגנה ביומטרית
המפתח הפרטי (או מפתח ההצפנה) מאוחסן ב-Keychain/KeyStore עם הדגל SecAccessControl.biometryCurrentSet (iOS) או setUserAuthenticationRequired(true) (Android). פעולות קריפטוגרפיות בלתי אפשריות ללא ביומטריה מוצלחת — מובטח על ידי מערכת ההפעלה ברמת Secure Enclave (iOS) או TEE (Android). גם אם תוקף משיג גישת root, המפתח אינו נגיש פיזית ללא ביומטריה. גישה זו מונעת גניבת מפתחות גם אם מערכת ההפעלה נפרצת.
iOS: SecAccessControl
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],
nil
)!ניסיון להשתמש במפתח זה ללא ביומטריה יגרום ל-let accessControl = SecAccessControlCreateWithFlags( nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, [.privateKeyUsage, .biometryCurrentSet], nil )! או errSecUserCanceled. האפליקציה לא יכולה לעקוף זאת תוכנתית. ניתן להעביר את ההקשר במפורש עבור ממשק משתמש מותאם אישית:
let context = LAContext()
context.localizedReason = "Подтвердите транзакцию на \(amount) ETH"
context.localizedCancelTitle = "Отмена"
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationLabel as String: "wallet-key",
kSecUseAuthenticationContext as String: context,
kSecReturnRef as String: true
]הטקסט ב-errSecAuthFailed צריך להכיל פרטי עסקה — כתובת הנמען, סכום. המשתמש חייב לראות מה הוא מאשר.
Android: BiometricPrompt עם CryptoObject
val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply {
init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv))
}
val cryptoObject = BiometricPrompt.CryptoObject(cipher)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle("Подтвердить транзакцию")
.setSubtitle("Отправить ${amount} ETH на ${shortAddress}")
.setNegativeButtonText("Отмена")
.setAllowedAuthenticators(BIOMETRIC_STRONG)
.build()
biometricPrompt.authenticate(promptInfo, cryptoObject)let context = LAContext() context.localizedReason = "Подтвердите транзакцию на \(amount) ETH" context.localizedCancelTitle = "Отмена" let query: [String: Any] = [ kSecClass as String: kSecClassKey, kSecAttrApplicationLabel as String: "wallet-key", kSecUseAuthenticationContext as String: context, kSecReturnRef as String: true ] קושר את הפעולה הקריפטוגרפית לביומטריה. localizedReason שולל ביומטריה חלשה (זיהוי פנים במכשירים ללא חיישן עומק). לאחר הצלחה, val cipher = Cipher.getInstance("AES/GCM/NoPadding").apply { init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, iv)) } val cryptoObject = BiometricPrompt.CryptoObject(cipher) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Подтвердить транзакцию") .setSubtitle("Отправить ${amount} ETH на ${shortAddress}") .setNegativeButtonText("Отмена") .setAllowedAuthenticators(BIOMETRIC_STRONG) .build() biometricPrompt.authenticate(promptInfo, cryptoObject) מכיל את ה-Cipher שנפתח — רק אז ניתן לפענח ולהשתמש במפתח.
השוואת גישות: UI-gate לעומת קישור קריפטוגרפי
| פרמטר | UI-gate | קישור קריפטוגרפי |
|---|---|---|
| רמת הגנה | תוכנתית (ניתנת לעקיפה דרך Frida) | חומרתית (SE/TEE) |
| אפשרות עקיפה | כן | לא |
| קישור למפתח | לא | כן |
| דורש ביומטריה לכל פעולה | כן (ניתן לזיוף) | כן (בלתי אפשרי לזיוף) |
| המלצה | אין להשתמש | חובה עבור ארנקים |
קישור קריפטוגרפי אמין פי 10 מ-UI-gate — אושר על ידי בדיקות על מכשירים אמיתיים עם כלי pentesting. רק גישה זו מבטיחה שבלעדי ביומטריה, העסקה לא תאושר.
מה כולל היישום?
אנו מציעים מחזור עבודה מלא:
- ביקורת של ארכיטקטורת אחסון המפתחות וההרשאות הנוכחית.
- אינטגרציה של ביומטריה עם קישור קריפטוגרפי: Keychain/KeyStore עם דגלים נכונים.
- הגדרת פסקי זמן לאימות חוזר (יצירת
CryptoObjectחדש לכל עסקה). - יישום גיבוי ל-PIN של המכשיר.
- בדיקת מקרי קצה: נעילת ביומטריה לאחר ניסיונות כושלים, שינויי ביומטריה בהגדרות.
- תיעוד וסקירת קוד.
- תמיכה לאחר השקה למשך שבוע.
תהליך ולוחות זמנים
- אנליטיקה — לימוד הקוד הנוכחי, זיהוי פגיעויות.
- עיצוב — בחירת הגישה האופטימלית לפלטפורמה שלך.
- יישום — פריסת ביומטריה עם קישור קריפטוגרפי.
- בדיקות — אימות על מכשירים אמיתיים, כולל ניסיונות פריצה.
- השקה — פרסום ב-App Store / Google Play עם תיאור שימוש ביומטרי נכון.
| פלטפורמה | לוח זמנים בסיסי | לוח זמנים עם ביקורת |
|---|---|---|
| iOS | יומיים | 4 ימים |
| Android | 3 ימים | 5 ימים |
לצוות שלנו ניסיון של 5+ שנים באבטחת מובייל, עם למעלה מ-30 פרויקטים המשתמשים בהגנה ביומטרית. הזמינו ביקורת אבטחה לארנק שלכם עוד היום.
גיבוי ל-PIN
ב-iOS, השתמשו בסוג האימות BIOMETRIC_STRONG, הכולל ביומטריה ו-PIN של המכשיר. ב-Android, הגדירו authenticationResult.cryptoObject?.cipher. זה מאפשר למשתמש לאשר את העסקה עם PIN אם ביומטריה אינה זמינה או נעולה. ודאו שלאחר הזנת PIN מוצלחת, המפתח הקריפטוגרפי נשלף רק אם המערכת מאשרת את האימות.
חיסכון בעלויות ביקורת אבטחה
יישום קישור קריפטוגרפי מההתחלה מפחית עלויות ביקורת עתידיות: תיקון פגיעות UI-gate עולה פי 2–3 יותר מיישום נכון מההתחלה. זה גם מפחית את הסיכון לדחיית אפליקציה עקב הנחיות App Store (סעיף 5.1) או מדיניות האבטחה של Play Store. צרו קשר לייעוץ על הפרויקט שלכם — נעריך את הארכיטקטורה הנוכחית שלכם ונציע פתרון אופטימלי.







