package com.rosetta.messenger.biometric import android.content.Context import android.util.Base64 import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import javax.crypto.Cipher import javax.crypto.SecretKeyFactory import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.PBEKeySpec import javax.crypto.spec.SecretKeySpec /** * Менеджер для работы с биометрической аутентификацией * Использует простую биометрию (без криптографии) для совместимости с Class 2 (Weak) биометрией */ class BiometricAuthManager(private val context: Context) { companion object { private const val ENCRYPTION_KEY = "RosettaBiometricKey2024" private const val SALT = "RosettaSalt" private const val IV_SEPARATOR = "]" } /** * Проверяет доступность биометрической аутентификации на устройстве */ fun isBiometricAvailable(): BiometricAvailability { val biometricManager = BiometricManager.from(context) // Проверяем любую биометрию (включая слабую) return when (biometricManager.canAuthenticate( BiometricManager.Authenticators.BIOMETRIC_WEAK )) { BiometricManager.BIOMETRIC_SUCCESS -> BiometricAvailability.Available BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> BiometricAvailability.NotAvailable("Биометрическое оборудование недоступно") BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> BiometricAvailability.NotAvailable("Биометрическое оборудование временно недоступно") BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricAvailability.NotEnrolled("Биометрические данные не настроены") BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> BiometricAvailability.NotAvailable("Требуется обновление безопасности") BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> BiometricAvailability.NotAvailable("Биометрическая аутентификация не поддерживается") BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> BiometricAvailability.NotAvailable("Неизвестный статус") else -> BiometricAvailability.NotAvailable("Неизвестная ошибка") } } /** * Шифрует пароль с помощью простого AES (не привязанного к биометрии) * Биометрия используется только для подтверждения личности */ fun encryptPassword( activity: FragmentActivity, password: String, onSuccess: (String) -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { // Сначала запрашиваем биометрию showBiometricPrompt( activity = activity, title = "Сохранить пароль", subtitle = "Подтвердите для сохранения пароля", onSuccess = { try { // После успешной биометрии шифруем пароль val encrypted = encryptString(password) onSuccess(encrypted) } catch (e: Exception) { onError("Ошибка шифрования: ${e.message}") } }, onError = onError, onCancel = onCancel ) } /** * Расшифровывает пароль после подтверждения биометрией */ fun decryptPassword( activity: FragmentActivity, encryptedData: String, onSuccess: (String) -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { showBiometricPrompt( activity = activity, title = "Разблокировать", subtitle = "Подтвердите для входа", onSuccess = { try { val decrypted = decryptString(encryptedData) onSuccess(decrypted) } catch (e: Exception) { onError("Ошибка расшифровки: ${e.message}") } }, onError = onError, onCancel = onCancel ) } /** * Показывает диалог биометрической аутентификации */ private fun showBiometricPrompt( activity: FragmentActivity, title: String, subtitle: String, onSuccess: () -> Unit, onError: (String) -> Unit, onCancel: () -> Unit ) { val executor = ContextCompat.getMainExecutor(context) val callback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) if (errorCode == BiometricPrompt.ERROR_USER_CANCELED || errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON || errorCode == BiometricPrompt.ERROR_CANCELED) { onCancel() } else { onError(errString.toString()) } } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) onSuccess() } override fun onAuthenticationFailed() { super.onAuthenticationFailed() // Не вызываем onError, пользователь может попробовать снова } } val biometricPrompt = BiometricPrompt(activity, executor, callback) val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle(title) .setSubtitle(subtitle) .setNegativeButtonText("Отмена") .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK) .build() biometricPrompt.authenticate(promptInfo) } /** * Простое AES шифрование строки */ private fun encryptString(data: String): String { val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val spec = PBEKeySpec(ENCRYPTION_KEY.toCharArray(), SALT.toByteArray(), 65536, 256) val tmp = factory.generateSecret(spec) val secretKey = SecretKeySpec(tmp.encoded, "AES") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, secretKey) val iv = cipher.iv val encrypted = cipher.doFinal(data.toByteArray(Charsets.UTF_8)) val ivString = Base64.encodeToString(iv, Base64.NO_WRAP) val encryptedString = Base64.encodeToString(encrypted, Base64.NO_WRAP) return "$ivString$IV_SEPARATOR$encryptedString" } /** * Расшифровка строки */ private fun decryptString(encryptedData: String): String { val parts = encryptedData.split(IV_SEPARATOR) if (parts.size != 2) { throw IllegalArgumentException("Invalid encrypted data format") } val iv = Base64.decode(parts[0], Base64.NO_WRAP) val encrypted = Base64.decode(parts[1], Base64.NO_WRAP) val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") val spec = PBEKeySpec(ENCRYPTION_KEY.toCharArray(), SALT.toByteArray(), 65536, 256) val tmp = factory.generateSecret(spec) val secretKey = SecretKeySpec(tmp.encoded, "AES") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv)) val decrypted = cipher.doFinal(encrypted) return String(decrypted, Charsets.UTF_8) } /** * Удаляет сохраненные биометрические данные */ fun removeBiometricData() { // Ничего не нужно удалять, так как мы не используем Keystore } } /** * Результат проверки доступности биометрии */ sealed class BiometricAvailability { object Available : BiometricAvailability() data class NotAvailable(val reason: String) : BiometricAvailability() data class NotEnrolled(val reason: String) : BiometricAvailability() }