安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

admin 2024年4月14日01:41:53评论5 views字数 14774阅读49分14秒阅读模式

介绍

为了增强用户体验和安全性,生物识别身份验证已广受欢迎。然而,随着极大的便利,确保实施的安全性的责任也随之而来。

本文将探讨移动生物识别身份验证安全实现的重要性,并提供 3 种主要现代移动语言的详细实现,即 Android 的 Kotlin、iOS 的 Swift 和 Flutter 多平台应用程序的 Dart。它试图解决我们在审查过的大多数移动应用程序中看到的安全漏洞。

那么,什么是安全的移动生物识别身份验证?

首先,移动生物识别身份验证的安全实现保证了使用Face ID或Touch ID身份验证来访问应用程序的敏感数据的需要。

在移动应用程序中,生物识别身份验证的安全实施不仅仅是验证指纹或面部登录。它还包括使用生物识别数据的应用程序的敏感数据。encrypting

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • windows

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • windows()

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • USB()

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • ()

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • ios

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • windbg

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • ()

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

  • 安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

这种加密增加了额外的保护层,使未经授权的个人访问或使用敏感信息极具挑战性。如果未经授权的一方通过恶意软件或物理访问访问设备,使用生物识别数据进行加密变得至关重要。

如果不加密,攻击者可以操纵内存绕过生物识别检查并成功登录应用程序。但是,如果对生物识别数据使用加密,他们将无法解释或利用应用程序数据。这有助于维护应用程序敏感信息的机密性,从而保护用户数据的隐私和安全。

安全的移动生物识别身份验证

应用场景:

在本文中,我们将实现 、 和 () 的安全生物识别身份验证示例。完整的工作流应遵循以下步骤来启用安全的生物识别身份验证:KotlinSwiftFlutterDart

1-用户打开移动应用程序并显示登录屏幕。

2-用户输入他的用户名和密码。

3- 应用程序通过将提供的凭据发送到后端服务器进行身份验证来验证这些凭据。

4- 如果凭据有效,后端服务器会为用户会话生成唯一令牌。

5- 应用程序提示用户设置生物识别身份验证(例如,指纹或面部识别)以备将来登录。

6- 用户设置生物识别身份验证后,应用程序将存储后端令牌。

7- 用户成功登录并访问应用程序的特性和功能。

8- 在随后的应用程序启动中,应用程序会检查用户是否启用了生物识别身份验证。

9- 如果启用了生物识别身份验证,应用程序将使用生物识别数据对用户进行身份验证。

10-成功进行生物识别身份验证后,应用程序将从安全存储中检索后端令牌。

我们假设设备在设备上启用了生物识别(我们不会将其包含在代码中,检查简单性很重要)

我们还将假设从 1 到 5 的步骤已经完成,并且用户正在为应用程序设置生物识别,这将需要使用生物识别数据加密令牌,并在成功登录后稍后读取它。

Kotlin (Android) 中的安全移动生物识别身份验证:

在 Android 中,为了在使用生物识别数据加密后存储后端令牌,我们将实现以下实现:

1- 我们的应用程序要求 Android 提供.KeyStoreSecretKey

2- Android 密钥库在安全位置 (TEE) 创建密钥。

3- Keystore 向我们的应用程序返回一个别名以访问 .secretKey

4-我们创建一个对象来执行加密/解密(加密是在系统中完成的。CipherKeystore

5- 密钥库系统接收明文和别名,并返回称为密文的加密数据。

6- 当应用程序想要执行解密时,密钥库系统会接收密文和别名并返回解密的数据或明文。

7-我们启用生物识别身份验证,以要求系统使用身份验证绑定来保护密钥。

8- 我们使用 a 作为包装器来携带密码。CryptoObject

9- 我们使用包装在 .将作为参数传递给 。CryptoObjectCryptoObjectonAuthenticationSucceeded

10- 我们在成功登录后通过使用包装在 .CryptoObject

通过这种实现,即使设备遭到入侵并且攻击者发出请求,数据仍保持加密状态,除非攻击者可以以某种方式让用户使用其生物识别凭据进行身份验证。生物识别身份验证增加了额外的安全层 - 即使在受感染的设备上 - 因为除非用户在场,否则无法访问硬件管理的密钥库。

检查与此流关联的代码:

1- 创建一个函数来创建并从 Android KeyStore 获取 SecretKey:

// DECLARE CONSTS
val ANDROID_KEYSTORE = "AndroidKeyStore"
private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
private val KEY_SIZE: Int = 256

private fun getOrCreateSecretKey(keyName: String): SecretKey {
// return Secretkey if it was previously created for that keyName.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
// Create new SecretKey for the provided keyName
val paramsBuilder = KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE)
setEncryptionPaddings(ENCRYPTION_PADDING)
setKeySize(KEY_SIZE)
}
val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
ANDROID_KEYSTORE)
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}

2- 我们创建对象,并在调用 authenticate 方法时将其包装在CipherCryptoObject

// DECLARE CONSTS
private val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private lateinit var promptInfo: BiometricPrompt.PromptInfo

private fun authenticateToEncrypt() {
if (BiometricManager.from(applicationContext).canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS) {
val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"
val cipher = Cipher.getInstance(transformation)
val secretKey = getOrCreateSecretKey(KEY_NAME)
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val biometricPrompt = createEncryptBiometricPrompt()
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}

private fun authenticateToDecrypt() {
if (BiometricManager.from(applicationContext).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"
val cipher = Cipher.getInstance(transformation)
val secretKey = getOrCreateSecretKey(KEY_NAME)
cipher.init(Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(128, initializationVector))
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}
}

3-我们可以实现两种不同的方法,一种用于加密数据,一种用于解密数据:BiometricPrompt

private fun createEncryptBiometricPrompt(): BiometricPrompt {
val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
// Handle authentication errors
}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
encryptData(result.cryptoObject)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
// Handle authentication failure
}
}
return BiometricPrompt(this, executor, authenticationCallback)
}
private fun createDecryptBiometricPrompt(): BiometricPrompt {
val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
// Handle authentication errors
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
decryptData(result.cryptoObject)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
// Handle authentication failure
}
}
return BiometricPrompt(this, executor, authenticationCallback)

4- 最后一部分是定义 and 函数。在此示例中,敏感数据是后端令牌身份验证:encryptDatadecryptData

private fun encryptData(cipher: Cipher): EncryptedData {
val ciphertext = cipher.doFinal(backendToken.toByteArray(Charset.forName("UTF-8")))
return EncryptedData(ciphertext,cipher.iv)
}

fun decryptData(ciphertext: ByteArray, cipher: Cipher): String {
val plaintext = cipher.doFinal(ciphertext)
return String(plaintext, Charset.forName("UTF-8"))
}

安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南

如果我在应用程序中启用生物识别身份验证后添加新指纹或删除现有指纹,会发生什么情况?

与密码认证类似,我们需要在更改密码后使当前令牌会话失效,我们需要在更改生物识别数据后使对存储在 Android 密钥库中的访问失效。SecretKey

在 Android 中,这不是默认行为,这意味着添加新指纹将允许用户登录并从应用程序访问敏感数据。

要在更改后使数据失效,我们需要在为 Android 密钥库生成 secretKey 时将 to 设置为。setUserAuthenticationRequiredtrue

// DECLARE CONSTS
val ANDROID_KEYSTORE = "AndroidKeyStore"
private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
private val KEY_SIZE: Int = 256

private fun getOrCreateSecretKey(keyName: String): SecretKey {
// return Secretkey if it was previously created for that keyName.
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
keyStore.load(null) // Keystore must be loaded before it can be accessed
keyStore.getKey(keyName, null)?.let { return it as SecretKey }
// Create new SecretKey for the provided keyName
val paramsBuilder = KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
paramsBuilder.apply {
setBlockModes(ENCRYPTION_BLOCK_MODE)
setEncryptionPaddings(ENCRYPTION_PADDING)
setKeySize(KEY_SIZE)
setUserAuthenticationRequired(true) // WE ADD OUR CALL HERE
}
val keyGenParams = paramsBuilder.build()
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
ANDROID_KEYSTORE)
keyGenerator.init(keyGenParams)
return keyGenerator.generateKey()
}

根据文档,将 设置为 将使现有数据失效:setUserAuthenticationRequiredTrue

sets whether this key is authorized to be used only if the user has been authenticated.By default, the key is authorized to be used regardless of whether the user has been authenticated.When user authentication is required:- The key can only be generated if secure lock screen is set up (see KeyguardManager.isDeviceSecure()). Additionally, if the key requires that user authentication takes place for every use of the key (see setUserAuthenticationValidityDurationSeconds(int)), at least one biometric must be enrolled (see BiometricManager.canAuthenticate()).- The use of the key must be authorized by the user by authenticating to this Android device using a subset of their secure lock screen credentials such as password/PIN/pattern or biometric.- The key will become irreversibly invalidated once the secure lock screen is disabled (reconfigured to None, Swipe or other mode which does not authenticate the user) or when the secure lock screen is forcibly reset (e.g., by a Device Administrator). Additionally, if the key requires that user authentication takes place for every use of the key, it is also irreversibly invalidated once a new biometric is enrolled or once no more biometrics are enrolled, unless setInvalidatedByBiometricEnrollment(boolean) is used to allow validity after enrollment. Attempts to initialize cryptographic operations using such keys will throw KeyPermanentlyInvalidatedException.

因此,在设置并添加新指纹后,应用程序将失败,并且需要请求新的密钥:setUserAuthenticationRequiredTrue

at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: android.security.keystore.KeyPermanentlyInvalidatedException: Key permanently invalidated

Swift (iOS) 中的安全移动生物识别身份验证:

iOS 中的实现与 Android 有很大不同。我们将使用钥匙串来存储密钥,并强制使用生物识别身份验证来从钥匙串访问项目。

1- 创建受生物识别保护的钥匙串项目:

我们使用以下参数创建一个:SecAccessControlCreateWithFlagsSecAccessControl

  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly:只有在iOS设备解锁时才能读取我们的钥匙串条目。此外,它不会通过iCloud复制到其他设备,也不会添加到备份中。

  • .biometryCurrentSet:设定触控 ID 或面容 ID 认证的要求。它将您的条目与当前注册的生物识别数据严格相关联。

static func getBioSecAccessControl() -> SecAccessControl {
var access: SecAccessControl?
var error: Unmanaged<CFError>?
access = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
&error)
precondition(access != nil, "SecAccessControlCreateWithFlags failed")
return access!
}

static func createBioProtectedEntry(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String: kSecClassGenericPassword as String,
kSecAttrAccount as String: key,
kSecAttrAccessControl as String: getBioSecAccessControl(),
kSecValueData as String: data ] as CFDictionary
return SecItemAdd(query as CFDictionary, nil)
}

2- 阅读受生物统计学保护的条目:

static func loadBioProtected(key: String, context: LAContext? = nil,
prompt: String? = nil) -> Data? {
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecReturnData as String: kCFBooleanTrue,
kSecAttrAccessControl as String: getBioSecAccessControl(),
kSecMatchLimit as String: kSecMatchLimitOne ]
if let context = context {
query[kSecUseAuthenticationContext as String] = context
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUISkip
}
if let prompt = prompt {
query[kSecUseOperationPrompt as String] = prompt
}
var dataTypeRef: AnyObject? = nil
let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return (dataTypeRef! as! Data)
} else {
return nil
}
}
static func redBioProtectedEntry(entryName: String) {
let authContext = LAContext()
let accessControl = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
&error)
authContext.evaluateAccessControl(accessControl, operation: .useItem, localizedReason: "Access sample keychain entry") {
(success, error) in
var result = ""
if success, let data = loadBioProtected(key: entryName, context: authContext) {
let result = String(decoding: data, as: UTF8.self)
} else {
result = "Can't read entry, error: (error?.localizedDescription ?? "-")"
}
}
}

在此示例中,我们使用 instance 对用户进行身份验证。我们调用提示用户进行 Touch ID 或 Face ID 身份验证的方法。如果身份验证成功,我们将使用实例来读取钥匙串条目内容。LAContextauthContext.evaluateAccessControlauthContext

该实例将放入键的查询字典中。这可确保后续调用考虑先前执行的身份验证。authContextkSecUseAuthenticationContextSecItemCopyMatching

如果我在应用程序中启用生物识别身份验证后添加新指纹或删除现有指纹,会发生什么情况?

如 Android 章节中所述,我们需要在更改生物识别数据后使钥匙串对项目的访问失效。

这就是为什么建议使用该设置,该设置将在任何更改后自动删除受生物识别保护的条目。SecAccessControlCreateFlagsbiometryCurrentSet

标志 ,并将条目保留在钥匙串中,并且新的生物识别数据仍然被视为有效,因此可以从钥匙串访问。userPresencebiometryAny

Flutter 中的安全移动生物识别身份验证(Android 和 iOS):

我们将使用 Plugin biometric_storage 来实现 Flutter。该插件允许使用生物识别身份验证将加密数据写入和读取到设备。

引擎盖下的实现应用了上述原则和用途,适用于 Android,并有权使用 Touch ID 和 Face ID 限制访问。CryptoObjectSecAccessControlSecAccessControlCreateFlags

该插件有一个要应用的要求列表。

第一步是创建访问对象,我们将在生物识别认证后写入和读取数据:

/// Retrieves the given biometric storage file. Each store is completely separated and has its own encryption and biometric lock.
Future<BiometricStorageFile> _getStorageFile() async {
final authStorage = await BiometricStorage().getStorage('authenticated_storage',options:StorageFileInitOptions(
///Always call it with `authenticationRequired=true`and`authenticationValidityDurationSeconds = -1` to ensure the secure implementation of bioùetric authentication.
authenticationValidityDurationSeconds: -1,
authenticationRequired: true,
androidBiometricOnly: true,
));
return authStorage;
}

将数据写入安全存储:

Future<void> createBioProtectedEntry(context) async {
if (await _checkAuthenticate() == false) {
showAlertDialog(context,const Text("Can't use biometric auth on this device."));
return ;
}
_storageFile = await _getStorageFile();
await _storageFile?.write(_my_secret_data);
}

要读取数据,请执行以下操作:

Future<void> redBioProtectedEntry(context) async {
if (await _checkAuthenticate() == false) {
showAlertDialog(context,const Text("Can't use biometric auth on this device."));
return ;
}
if (_storageFile == null){
showAlertDialog(context,const Text("Enable authentication first."));
return ;
}
final data = await _storageFile?.read();
showAlertDialog(context,Text(data!));
}

瞧!该实现适用于 Android 和 iOS。

每次调用 or 都会提示用户进行 Touch ID 或 Face ID 身份验证,并且密钥访问受生物识别绑定保护。_storageFile.write_storageFile.read

如果我们检查 Android 实现,当我们使用非 null 值和 == -1 调用 authenticate 函数时,则会用包装我们的密码来调用 。cipherauthenticationValidityDurationSecondsBiometricPromptCryptoObject

if (cipher == null || options.authenticationValidityDurationSeconds >= 0) {
// if authenticationValidityDurationSeconds is not -1 we can't use a CryptoObject
logger.debug { "Authenticating without cipher. ${options.authenticationValidityDurationSeconds}" }
prompt.authenticate(promptBuilder.build())
} else {
prompt.authenticate(promptBuilder.build(), BiometricPrompt.CryptoObject(cipher))
}

要得到一个 non-null ,我们需要将相同的选项传递给源代码cipherauthenticationValidityDurationSeconds == -1

val cipher = if (options.authenticationValidityDurationSeconds > -1) {
null
} else try {
cipherForMode()
} catch (e: KeyPermanentlyInvalidatedException) {
// TODO should we communicate this to the caller?
logger.warn(e) { "Key was invalidated. removing previous storage and recreating." }
deleteFile()
// if deleting fails, simply throw the second time around.
cipherForMode()
}

对于 iOS 实现,安全访问控制是用 和 源代码定义的kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlybiometryCurrentSet

private func accessControl(_ result: @escaping StorageCallback) -> SecAccessControl? {
let accessControlFlags: SecAccessControlCreateFlags
    The implementation is also using LAContext to read the protected data:
private func canAuthenticate(result: @escaping StorageCallback) {
var error: NSError?
let context = LAContext()

......

if #available(iOS 11.3, *) {
accessControlFlags = .biometryCurrentSet
} else {
accessControlFlags = .touchIDCurrentSet
}
var error: Unmanaged<CFError>?
guard let access = SecAccessControlCreateWithFlags(
nil, // Use the default allocator.
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
accessControlFlags,
&error) else {
hpdebug("Error while creating access control flags. (String(describing: error))")
result(storageError("writing data", "error writing data", "(String(describing: error))"));
return nil
}
return access
}

源代码

func read(_ result: @escaping StorageCallback, _ promptInfo: IOSPromptInfo) {
guard var query = baseQuery(result) else {
return;
}
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecUseOperationPrompt as String] = promptInfo.accessTitle
query[kSecReturnAttributes as String] = true
query[kSecReturnData as String] = true
query[kSecUseAuthenticationContext as String] = context

结论

在本文中,我们介绍了三个主要框架的生物识别身份验证的安全实现。

  • 对于 Android,我们使用 来包装我们的并将其绑定到生物识别身份验证以访问 Android KeyStore 密钥。CryptoObjectcipher

  • 对于 iOS,我们使用实例创建了受保护的钥匙串项。SecAccessControl

  • 对于 Flutter,我们使用了 插件 ,它在 Android 和 iOS 上使用安全的生物识别实现来写入和读取文件的数据。biometric_storage

有关更多信息,您可以阅读官方文档:

1- 生物识别提示#authenticate

2- 使用Face ID或Touch ID访问钥匙串项目

3-biometric_storage

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月14日01:41:53
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   安全移动生物识别身份验证:Kotlin、Swift 和 Flutter 的最佳实践和实施指南https://cn-sec.com/archives/2654573.html

发表评论

匿名网友 填写信息