安全的用户身份验证

为保护您在 Android 中的身份验证系统,请考虑放弃基于密码的模型,尤其是对于用户银行和电子邮件账户等敏感账户。请记住,用户安装的某些应用可能没有最佳意图,并可能试图对您的用户进行网络钓鱼。

此外,不要假设只有授权用户才会使用设备。手机盗窃是一个常见问题,攻击者会针对未锁定的设备,直接从用户数据或金融应用中获利。我们建议所有敏感应用实施合理的身份验证超时(15 分钟?)并进行生物识别验证,并在进行诸如资金转账等敏感操作之前,要求进行额外的身份验证。

生物识别身份验证对话框

Biometrics 库提供了一组函数,用于显示请求生物识别身份验证(例如面部识别或指纹识别)的提示。但是,生物识别提示可以配置为回退到 LSKF,这存在已知的肩窥风险。对于敏感应用,我们建议不要让生物识别回退到 PIN,并且在生物识别重试次数用尽后,用户可以等待,或者使用密码重新登录或重置账户。账户重置应要求使用不易在设备上获取的因素(最佳实践如下)。

这如何有助于减轻欺诈和手机盗窃

一个有助于防止欺诈的特殊用例是在交易前在您的应用内请求生物识别身份验证。当用户想要进行金融交易时,会显示生物识别对话框,以验证确实是预期的用户在进行交易。这种最佳实践将保护设备免受攻击者盗窃,无论攻击者是否知道 LSKF,因为他们需要证明他们是设备的所有者。

为了提高安全性,我们建议应用开发者请求 Class 3 生物识别身份验证,并为银行和金融交易使用 CryptoObject

实现

  1. 确保您包含 androidx.biometric 库。
  2. 将生物识别登录对话框包含在包含您希望用户进行身份验证逻辑的 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);
    });
}

最佳实践

我们建议您从代码实验室开始,了解有关生物识别的更多信息。

根据您的用例,您可以选择是否通过显式用户操作来实施对话框。为了避免欺诈,我们建议您为每笔交易添加带有显式用户操作的生物识别对话框。我们理解添加身份验证可能会增加用户体验的摩擦,但由于银行交易中处理的信息的性质,以及生物识别身份验证比其他身份验证方法更顺畅,我们认为有必要添加此级别的导航。

了解更多关于生物识别身份验证的信息.

通行密钥

通行密钥是比密码更安全、更简单的替代方案。通行密钥使用公钥加密技术,让您的用户能够使用其设备的屏幕锁定机制(例如指纹或面部识别)登录应用和网站。这使用户无需记住和管理密码,并显著提高了安全性。

通行密钥可以一步满足多因素身份验证要求,取代密码和 OTP 代码,从而提供强大的防网络钓鱼保护,并避免短信或基于应用的单次密码带来的用户体验痛苦。由于通行密钥是标准化的,因此一次性实现即可在用户的所有设备、浏览器和操作系统上提供无密码体验。

在 Android 上,通行密钥通过 Credential Manager Jetpack 库支持,该库统一了主要的身份验证方法,包括通行密钥、密码和联合登录(例如使用 Google 登录)。

这如何有助于减轻欺诈

通行密钥可以保护您免受网络钓鱼攻击,因为它们仅在您注册的应用和网站上有效。

通行密钥的核心组件是加密私钥。通常,此私钥仅存在于您的设备(例如笔记本电脑或手机)上,并由凭据提供商(也称为密码管理器,例如 Google 密码管理器)在它们之间同步。创建通行密钥时,只有相应的公钥由在线服务保存。在登录期间,服务使用私钥签署来自公钥的质询。这只能源自您的其中一台设备。此外,要实现这一点,您必须解锁您的设备或凭据存储,这可以防止未经授权的登录(例如,来自被盗手机)。

为了防止在设备被盗、解锁的情况下未经授权的访问,通行密钥必须与合理的身份验证超时窗口结合使用。窃取设备攻击者不应该仅仅因为前一个用户已登录就能够使用应用程序。相反,凭据应定期过期(例如,每 15 分钟),并且应要求用户通过屏幕锁定重新身份验证来验证其身份。

如果您的手机被盗,通行密钥会保护您,因为小偷无法窃取您的密码在其他设备上使用——通行密钥是设备专用的。如果您使用 Google 密码管理器且手机被盗,您可以从其他设备(例如电脑)登录您的 Google 账号,然后远程从被盗手机中退出登录。这会使被盗手机上的 Google 密码管理器无法使用,包括任何保存的通行密钥。

在最坏的情况下,如果被盗设备未能找回,通行密钥将由创建和同步通行密钥的凭据提供商同步回新设备。例如,用户可能选择了 Google 密码管理器来创建通行密钥,他们可以通过重新登录其 Google 账号并提供之前设备的屏幕锁定来在新设备上访问其通行密钥。

Google 密码管理器中通行密钥的安全性一文中了解更多信息。

实现

Android 9(API 级别 28)或更高版本设备支持通行密钥。Android 4.4 起支持密码和通过 Google 登录。要开始使用通行密钥,请按以下步骤操作:

  1. 按照Credential Manager 代码实验室,初步了解如何实现通行密钥。
  2. 查阅通行密钥用户体验设计指南。本文档向您展示了推荐用于您的用例的流程。
  3. 按照指南研究 Credential Manager。
  4. 为您的应用规划 Credential Manager 和通行密钥的实现。计划添加对数字资产链接的支持。

请参阅我们的开发者文档,了解有关如何创建、注册和使用通行密钥进行身份验证的更多详细信息。

安全账户重置

未经授权的攻击者如果能访问已解锁的设备(例如手机被抢走),将试图访问敏感应用,特别是银行或现金应用。如果应用实施了生物识别验证,攻击者会尝试重置账户以进入。账户重置流程不应仅仅依赖于设备上容易获取的信息,例如电子邮件或短信 OTP 重置链接。

以下是您可以纳入应用重置流程的常见最佳实践:

  • 面部识别,除了 OTP
  • 安全问题
  • 知识因素(例如母亲的娘家姓、出生城市或最喜欢的歌曲)
  • 身份验证

短信检索 API (SMS Retriever API)

SMS Retriever API 让您可以在 Android 应用中自动执行基于短信的用户验证。通过这种方式,用户无需手动输入验证码。此外,此 API 不会向用户请求额外的、可能危险的应用权限,例如 RECEIVE_SMSREAD_SMS。但是,短信不应作为唯一的身份验证方式来防止对设备的未经授权的本地访问。

这如何有助于减轻欺诈

一些用户将短信验证码作为其唯一的身份验证因素,这为欺诈提供了便捷的入口。

SMS Retriever API 允许应用在没有用户交互的情况下直接检索短信验证码,并可以提供一定程度的防欺诈保护。

实现

实现 SMS Retriever API 包含两部分:Android 和服务器。

Android:(指南

  1. 获取用户的电话号码。
  2. 启动 SMS 检索客户端。
  3. 将电话号码发送到您的服务器。
  4. 接收验证消息。
  5. 将 OTP 发送到您的服务器。

服务器:(指南

  1. 构造验证消息。
  2. 通过短信发送验证消息。
  3. 返回 OTP 时验证它。

最佳实践

一旦应用集成完毕且用户的手机号码正在通过 SMS Retriever API 进行验证,它就会尝试获取 OTP。如果成功,则强烈表明短信已自动在设备上收到。如果未成功且用户需要手动输入 OTP,则这可能是用户可能正在经历欺诈的警告信号。

短信不应作为唯一的身份验证机制,因为它为本地攻击(例如抢劫已解锁设备的攻击者)或 SIM 卡克隆攻击留下了空间。建议尽可能使用生物识别。在无法使用生物识别传感器的设备上,用户身份验证应至少依赖一个不易从当前设备获取的因素。

了解详情

如需进一步阅读最佳实践,请查阅以下资源: