安全用户认证

为了保护您在 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 开始,以了解更多有关生物识别的信息。

根据您的使用场景,您可以选择在用户显式操作的情况下或不进行显式用户操作的情况下实现对话框。为了避免欺诈,我们建议您在每次交易中都添加需要显式用户操作的生物识别对话框。我们理解添加身份验证可能会增加用户体验的摩擦,但由于银行交易中处理的信息的性质,以及生物识别身份验证比其他身份验证方法更流畅,我们认为添加这一级别的导航是必要的。

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

密钥

密钥是比密码更安全、更便捷的替代方案。密钥使用公钥加密技术,让您的用户可以使用其设备的屏幕锁定机制(例如指纹或面部识别)登录应用程序和网站。这使用户无需记住和管理密码,并提供显著提升的安全性。

密钥可以在一步内满足多因素身份验证要求,取代密码和 OTP 代码,以提供强大的防钓鱼攻击保护,并避免 SMS 或应用程序基于的一次性密码带来的用户体验问题。由于密钥是标准化的,因此一次实现就可以在所有用户的设备、浏览器和操作系统上实现无密码体验。

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

这对缓解欺诈有什么帮助

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

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

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

如果您的手机被盗,密钥可以保护您,因为小偷无法窃取您的密码并在其他设备上使用 - 密钥是设备特定的。如果您使用 Google 密码管理器并且您的手机被盗,您可以从另一台设备(例如计算机)登录您的 Google 帐户,并远程从被盗手机注销。这会使被盗手机上的 Google 密码管理器无法使用,包括任何保存的密钥。

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

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

实现

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

  1. 遵循 Credential Manager 代码实验室 来初步了解如何实现密钥。
  2. 查看 密钥用户体验设计指南。本文档展示了适用于您的用例的推荐流程。
  3. 通过遵循 指南 来学习 Credential Manager。
  4. 为您的应用程序规划 Credential Manager 和密钥实施。计划添加对 数字资产链接 的支持。

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

安全帐户重置

未经授权的攻击者访问解锁设备(例如手机被抢走)时,会尝试访问敏感应用程序,尤其是银行或现金应用程序。如果应用程序实现了生物识别验证,攻击者会尝试重置帐户以获取访问权限。对于帐户重置流程来说,不应仅仅依赖于设备上容易获取的信息,例如电子邮件或 SMS OTP 重置链接,这一点至关重要。

以下是一些您可以将应用程序的重置流程中整合的常见最佳做法

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

SMS Retriever API

SMS Retriever API 使您能够在 Android 应用程序中自动执行基于 SMS 的用户验证。这样,用户就不需要手动输入验证代码。此外,此 API 不会要求用户提供额外的、可能存在安全风险的应用程序权限,例如 RECEIVE_SMSREAD_SMS。但是,SMS 不应作为唯一的用户验证手段,以防止对设备进行未经授权的本地访问。

这对缓解欺诈有什么帮助

一些用户使用 SMS 代码作为他们唯一的身份验证因素,这为欺诈提供了简单的入口点。

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

实现

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

Android: (指南)

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

服务器: (指南)

  1. 构建验证消息。
  2. 通过 SMS 发送验证消息。
  3. 验证返回的 OTP。

最佳实践

应用程序集成完毕且用户电话号码已通过 SMS Retriever API 验证后,它会尝试获取 OTP。如果成功,则表示 SMS 已自动接收在设备上,这是一个强信号。如果失败且用户需要手动输入 OTP,则可能预示着用户可能正在遭受欺诈。

SMS 不应作为唯一的用户验证机制,因为它存在本地攻击的漏洞,例如,攻击者抢走解锁设备;或 SIM 卡克隆攻击。建议在可能的情况下使用生物识别。在没有生物识别传感器的设备上,用户身份验证应至少依赖于一个无法从当前设备轻松获取的因素。

了解更多

要进一步了解最佳实践,请查看以下资源