安全用户认证

为了保护您在 Android 中的认证系统,请考虑放弃基于密码的模型,尤其是在用户银行和电子邮件帐户等敏感帐户中。请记住,用户安装的一些应用可能并非出于善意,可能会尝试诱骗您的用户。

此外,不要假设只有授权用户才会使用该设备。手机被盗是一个常见问题,攻击者会将目标锁定在未锁定的设备上,以直接从用户数据或金融应用中获利。我们建议所有敏感应用实现合理的认证超时(15 分钟?),并使用生物识别验证,并在进行敏感操作(例如转账)之前要求额外的认证。帐户重置应需要设备上不容易获取的因素(如下面的最佳实践)。

生物识别认证对话框

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

这如何帮助降低欺诈和手机被盗的风险

一个特别有用的用例可以防止欺诈,就是在应用中进行交易之前请求生物识别认证。当您的用户想要进行金融交易时,会显示生物识别对话框以验证进行交易的确实是预期的用户。此最佳实践可以防止攻击者窃取设备,无论攻击者是否知道 LSKF,因为他们都需要证明自己是设备的所有者。

为了获得额外的安全级别,我们建议应用开发者请求 3 类生物识别认证并利用 CryptoObject 用于银行和金融交易。

实施

  1. 确保您包含 androidx.biometric 库。
  2. 在包含您希望用户进行身份验证的逻辑的活动或片段中包含生物识别登录对话框。

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 开始,以了解更多关于生物识别的信息。

根据您的用例,您可以使用或不使用显式用户操作来实现对话框。为了避免欺诈,我们建议您为每次交易添加带有显式用户操作的生物识别对话框。我们了解添加身份验证可能会导致 UX 出现摩擦,但由于银行交易中处理的信息的性质以及生物识别认证比其他身份验证方法更流畅,因此我们认为有必要添加此级别的导航。

详细了解生物识别认证.

密码密钥

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

密码密钥可以在一步内满足多因素身份验证的要求,同时替换密码和 OTP 代码,从而提供强大的防钓鱼攻击保护,并避免用户体验 SMS 或基于应用的一次性密码带来的痛苦。由于密码密钥已标准化,因此只需一次实现即可在所有用户的设备、浏览器和操作系统上提供无密码体验。

在 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
  • 安全问题
  • 知识因素(例如母亲的娘家姓、出生城市或喜欢的歌曲)
  • 身份验证

SMS Retriever API

SMS Retriever API 允许您在 Android 应用中自动执行基于短信的用户验证。这样,用户就不需要手动输入验证码。此外,此 API 不会要求用户提供额外的、可能危险的应用权限,例如 RECEIVE_SMSREAD_SMS。但是,短信不应作为防止未经授权的本地访问设备的唯一用户验证方式。

这如何帮助降低欺诈风险

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

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

实施

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

Android:(指南)

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

服务器:(指南)

  1. 构建验证消息。
  2. 通过短信发送验证消息。
  3. 在 OTP 返回时进行验证。

最佳实践

一旦应用集成并且用户的电话号码正在使用 SMS Retriever API 进行验证,它就会尝试获取 OTP。如果成功,则表明短信已自动接收在设备上。如果失败并且用户需要手动输入 OTP,则可能预示着用户可能正在遭受欺诈。

短信不应作为唯一的用户验证机制,因为它会为本地攻击留下空间,例如抢走未锁定设备的攻击者;或 SIM 卡克隆攻击。建议尽可能使用生物识别技术。在没有生物识别传感器的设备上,用户身份验证应依赖于至少一个无法从当前设备轻松获取的因素。

了解更多

有关最佳实践的进一步阅读,请查看以下资源