为了保护 Android 中的身份验证系统,请考虑弃用基于密码的模式,尤其是对于用户的银行账号和电子邮件账号等敏感账号。请注意,用户安装的某些应用可能并非出于善意,可能会试图对您的用户进行钓鱼式攻击。
此外,请勿假设只有已获授权的用户会使用该设备。手机盗窃是一个常见问题,攻击者会盯上已解锁的设备,以便直接从用户数据或金融应用中获利。我们建议所有敏感应用都实现合理的身份验证超时时间(15 分钟?),并使用生物识别验证,在执行资金转移等敏感操作之前要求进行额外的身份验证。
生物识别身份验证对话框
生物识别库提供了一组函数,用于显示请求进行生物识别验证(例如人脸识别或指纹识别)的提示。 不过,生物识别提示可以配置为回退到 LSKF,而 LSKF 存在已知的肩窥风险。对于敏感应用,我们建议不要让生物识别技术回退到 PIN 码,并且在生物识别重试次数用尽后,用户可以等待,或使用密码重新登录或重置账号。账号重置应需要设备上不易获取的凭据(最佳实践如下)。
这有助于防范欺诈和手机盗窃行为
在交易之前在应用内请求进行生物识别身份验证,有助于防止欺诈。当用户想要进行金融交易时,系统会显示生物识别对话框,以验证进行交易的用户是否确实是预期用户。无论攻击者是否知道 LSKF,此最佳实践都能防止攻击者窃取设备,因为攻击者需要探测自己是否是设备的所有者。
为了提高安全性,我们建议应用开发者请求使用 3 类生物识别身份验证,并针对银行和金融交易使用 CryptoObject
。
实现
- 确保您添加了 androidx.biometric 库。
- 在包含您希望用户通过身份验证的逻辑的 activity 或 fragment 中,添加生物识别登录对话框。
Kotlin
private var executor: Executor? = null private var biometricPrompt: BiometricPrompt? = null private var promptInfo: BiometricPrompt.PromptInfo? = null fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) executor = ContextCompat.getMainExecutor(this) biometricPrompt = BiometricPrompt(this@MainActivity, executor, object : AuthenticationCallback() { fun onAuthenticationError( errorCode: Int, @NonNull errString: CharSequence ) { super.onAuthenticationError(errorCode, errString) Toast.makeText( getApplicationContext(), "Authentication error: $errString", Toast.LENGTH_SHORT ) .show() } fun onAuthenticationSucceeded( @NonNull result: BiometricPrompt.AuthenticationResult? ) { super.onAuthenticationSucceeded(result) Toast.makeText( getApplicationContext(), "Authentication succeeded!", Toast.LENGTH_SHORT ).show() } fun onAuthenticationFailed() { super.onAuthenticationFailed() Toast.makeText( getApplicationContext(), "Authentication failed", Toast.LENGTH_SHORT ) .show() } }) promptInfo = Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build() // Prompt appears when user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. val biometricLoginButton: Button = findViewById(R.id.biometric_login) biometricLoginButton.setOnClickListener { view -> biometricPrompt.authenticate( promptInfo ) } }
Java
private Executor executor; private BiometricPrompt biometricPrompt; private BiometricPrompt.PromptInfo promptInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); executor = ContextCompat.getMainExecutor(this); biometricPrompt = new BiometricPrompt(MainActivity.this, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { super.onAuthenticationError(errorCode, errString); Toast.makeText(getApplicationContext(), "Authentication error: " + errString, Toast.LENGTH_SHORT) .show(); } @Override public void onAuthenticationSucceeded( @NonNull BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); Toast.makeText(getApplicationContext(), "Authentication succeeded!", Toast.LENGTH_SHORT).show(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); Toast.makeText(getApplicationContext(), "Authentication failed", Toast.LENGTH_SHORT) .show(); } }); promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build(); // Prompt appears when the user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. Button biometricLoginButton = findViewById(R.id.biometric_login); biometricLoginButton.setOnClickListener(view -> { biometricPrompt.authenticate(promptInfo); }); }
最佳做法
建议您先完成此 Codelab,详细了解生物识别技术。
您可以根据自己的使用情形,实现需要或不需要用户明确操作的对话框。为避免欺诈,我们建议您为每笔交易添加生物识别对话框,并要求用户明确执行操作。我们知道,添加身份验证可能会给用户体验带来一些不便,但考虑到银行交易中处理的信息的性质,以及生物识别身份验证比其他身份验证方法更顺畅,我们认为有必要添加此级别的导航。
通行密钥
通行密钥是一种更安全、更便捷的替代密码的方法。通行密钥使用公钥加密技术,让用户能够使用设备的屏幕锁定机制(例如指纹或人脸识别)登录应用和网站。这样一来,用户无需记住和管理密码,安全性也显著提高。
通行密钥只需一步即可满足多重身份验证要求,可取代密码和 OTP 代码,从而提供强大的保护来防范钓鱼式攻击,并避免用户在短信或基于应用的动态密码方面遇到不便。由于通行密钥是标准化的,因此只需一次实现,即可在用户的所有设备、浏览器和操作系统中实现无密码体验。
在 Android 上,通行密钥通过 Credential Manager Jetpack 库提供支持,该库整合了主要的身份验证方法,包括通行密钥、密码和联合登录(例如“使用 Google 账号登录”)。
这有助于减少欺诈行为
通行密钥仅在您注册的应用和网站上有效,因此可保护您免遭钓鱼式攻击。
通行密钥的核心组件是加密私钥。通常,此私钥仅存在于您的设备(例如笔记本电脑或手机)上,并通过凭据提供方(也称为密码管理工具,例如 Google 密码管理工具)在这些设备之间同步。创建通行密钥时,在线服务只会保存相应的公钥。在登录期间,服务使用私钥对来自公钥的质询进行签名。此请求只能来自您的某台设备。此外,要实现此目的,您必须解锁设备或凭据存储区,以防止未经授权的登录(例如,通过被盗的手机登录)。
为防止在设备被盗且处于解锁状态时发生未经授权的访问,通行密钥必须与合理的身份验证超时窗口搭配使用。窃取设备后,攻击者不应仅因之前有用户登录过该设备就能够使用应用。相反,凭据应定期过期(例如每 15 分钟),并且用户应通过重新验证屏幕锁定来验证自己的身份。
如果手机被盗,通行密钥可以保护您,因为窃贼无法窃取您的密码以在其他设备上使用,通行密钥是特定于设备的。如果您使用 Google 密码管理工具,但手机被盗,则可以在其他设备(例如计算机)上登录自己的 Google 账号,然后远程从被盗手机中退出账号。这样一来,被盗手机上的 Google 密码管理工具(包括所有已保存的通行密钥)将无法使用。
在最糟糕的情况下,如果被盗设备无法找回,凭据提供方会将创建并同步通行密钥的通行密钥同步回新设备。例如,用户可能选择了 Google 密码管理工具来创建通行密钥,并且可以通过重新登录 Google 账号并提供之前设备的屏幕锁定方式,在新设备上访问通行密钥。
如需了解详情,请参阅Google 密码管理工具中通行密钥的安全性一文。
实现
搭载 Android 9(API 级别 28)或更高版本的设备支持通行密钥。 从 Android 4.4 开始支持密码和“使用 Google 账号登录”功能。如需开始使用通行密钥,请按以下步骤操作:
- 按照 Credential Manager Codelab 操作,初步了解如何实现通行密钥。
- 查看通行密钥用户体验设计指南。本文档将向您展示针对您的使用情形推荐的工作流程。
- 按照指南学习 Credential Manager。
- 为您的应用规划凭据管理器和通行密钥实现。规划添加对 Digital Asset Links 的支持。
如需详细了解如何创建、注册和使用通行密钥进行身份验证,请参阅我们的开发者文档。
安全重置账号
如果未经授权的攻击者可以访问已解锁的设备(例如手机被抢),则会尝试访问敏感应用,尤其是银行应用或现金应用。如果应用实现了生物识别验证,攻击者会尝试重置账号以进入。账号重置流程不应仅依赖于设备上易于访问的信息,例如电子邮件或短信 OTP 重置链接。
以下是一些常见最佳实践,您可以将其纳入应用的重置流程中:
- 人脸识别(除了一次性密码)
- 安全问题
- 知识因素(例如母亲的婚前姓名、出生城市或喜爱的歌曲)
- 身份证件验证
SMS Retriever API
借助 SMS Retriever API,您可以在 Android 应用中自动以短信方式执行用户验证。这样一来,用户就不需要手动输入验证码。此外,此 API 不会要求用户授予额外的、可能存在危险的应用权限,例如 RECEIVE_SMS
或 READ_SMS
。不过,不应仅使用短信作为用户验证方式,以防未经授权的本地访问。
这有助于减少欺诈行为
部分用户仅使用短信验证码作为身份验证因素,这为欺诈行为提供了可乘之机。
借助 SMS Retriever API,应用可以直接检索短信验证码,而无需用户互动,并且可以提供一定程度的欺诈防护。
实现
实现 SMS Retriever API 分为两个部分:Android 和服务器。
Android:(指南)
- 获取用户的电话号码。
- 启动短信检索器客户端。
- 将电话号码发送到您的服务器。
- 接收验证消息。
- 将 OTP 发送到您的服务器。
服务器:(指南)
- 构造验证消息。
- 通过短信发送验证消息。
- 验证返回的 OTP。
最佳做法
应用集成完毕后,系统会使用 SMS Retriever API 验证用户的电话号码,并尝试获取动态密码。如果成功,则表明设备已自动收到短信。如果自动填充失败,并且用户需要手动输入 OTP,则可能表明用户正遭受欺诈。
短信不应作为唯一的用户验证机制,因为这会给本地攻击(例如攻击者抢劫已解锁的设备)或 SIM 卡克隆攻击留下可乘之机。建议尽可能使用生物识别技术。在没有生物识别传感器的设备上,用户身份验证应至少依赖于一种不易从当前设备获取的因素。
了解详情
如需进一步了解最佳实践,请参阅以下资源: