安全的用户身份验证

为了保护 Android 中的身份验证系统,请考虑弃用基于密码的模型,尤其是敏感帐号(例如用户的银行账户和电子邮件帐号)时。请注意,用户安装的某些应用可能并非出于好意,可能会试图骗取用户。

此外,请勿假设只有获得授权的用户才能使用设备。手机盗窃是一个常见问题,攻击者的目标是解锁设备,以便直接通过用户数据或金融应用获利。我们建议所有敏感应用都通过生物识别验证实现合理的身份验证超时(15 分钟?),并要求在执行转账等敏感操作之前进行额外的身份验证。

生物识别身份验证对话框

Biometrics 库提供了一组函数,用于显示请求生物识别身份验证(例如人脸识别或指纹识别)的提示。不过,生物识别提示可以配置为回退到具有已知的路边冲浪风险的 LSKF。对于敏感应用,我们建议不要让生物识别技术回退到 PIN 码,并且在用完生物识别验证次数后,用户可以等待、使用密码重新登录或重置帐号。帐号重置应要求设备不易访问的因素(下面的最佳实践)。

这些信息如何帮助防范欺诈和手机盗窃

有助于防范欺诈的一个特定用例是在交易前在应用中请求生物识别身份验证。当用户想要进行金融交易时,系统会显示生物识别对话框,以验证交易者是否确实是期望的用户。无论攻击者是否知道 LSKF,这种最佳做法都可以防止攻击者窃取设备,因为他们需要探测自己是否是设备的所有者。

为了提高安全性,我们建议应用开发者申请 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);
    });
}

最佳实践

我们建议您从 Codelab 开始,详细了解生物识别。

根据您的用例,您可以实现有无显式用户操作对话框。为了避免欺诈,我们建议您为每笔交易添加包含明确用户操作的生物识别对话框。我们知道,添加身份验证可能会给用户体验带来不便,但由于银行交易中要处理的信息的性质,生物识别身份验证比其他身份验证方法更顺畅,因此我们认为有必要添加这种级别的导航。

详细了解生物识别身份验证

通行密钥

通行密钥是一种更安全、更简便的替代密码。通行密钥使用公钥加密,使您的用户可以使用其设备的屏幕锁定机制(如指纹或人脸识别)登录应用和网站。这让用户不必记住和管理密码,并且可以显著提高安全性。

通行密钥只需一步即可满足多重身份验证要求,既可替换密码,又可替换动态密码,以提供针对钓鱼式攻击的强大保护,并避免因使用短信或基于应用的动态密码而给用户带来麻烦。由于通行密钥是标准化的,因此只需实现一次,即可在所有用户的设备、浏览器和操作系统上实现无密码体验。

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

这些信息如何帮助防范欺诈行为

通行密钥可以保护您免受钓鱼式攻击,因为它们仅适用于您注册的应用和网站。

通行密钥的核心组件是加密私钥。通常,此私钥仅位于您的设备(如笔记本电脑或手机)上,并由 Google 密码管理工具等凭据提供程序(也称为密码管理工具)在设备间同步。创建通行密钥时,在线服务只会保存相应的公钥。登录期间,服务会使用私钥对来自公钥的质询进行签名。这只能来自您的某部设备。此外,为此,您必须解锁设备或凭据存储区,以防止未经授权的登录(例如手机被盗)。

为防止在设备被盗时遭到未经授权的访问,通行密钥必须与合理的身份验证超时期限结合使用。窃取设备的攻击者不能仅仅因为之前用户已经登录就无法使用应用。相反,凭据应定期(例如每 15 分钟)过期,并且用户应要求用户通过重新进行屏幕锁定身份验证来验证身份。

如果您的手机被盗,通行密钥可以为您保驾护航,因为盗窃者无法窃取您在其他设备上使用的密码,通行密钥是设备专用的。如果您使用 Google 密码管理工具,并且手机被盗,您可以从其他设备(例如计算机)登录您的 Google 帐号,并从被盗的手机中远程退出帐号。这样一来,被盗手机上的 Google 密码管理工具(包括任何已保存的通行密钥)将无法使用。

在最糟糕的情况下,如果被盗设备未恢复,则由创建和同步通行密钥的凭据 provider 将通行密钥同步回新设备。例如,用户可能已选择 Google 密码管理工具来创建通行密钥,并且他们可以在新设备上访问其通行密钥,方法是重新登录其 Google 账号并提供先前所用设备的屏锁。

如需了解详情,请参阅 Google 密码管理工具中通行密钥的安全性一文。

实现

搭载 Android 9(API 级别 28)或更高版本的设备支持通行密钥。Android 4.4 及更高版本支持密码和“使用 Google 账号登录”功能。若要开始使用通行密钥,请按以下步骤操作:

  1. 学习 Credential Manager Codelab,初步了解如何实现通行密钥。
  2. 查看通行密钥用户体验设计指南。本文档介绍了针对您的使用场景推荐的流程。
  3. 按照指南学习 Credential Manager。
  4. 规划应用的 Credential Manager 和通行密钥实现。计划添加对 Digital Asset Links 的支持。

如需详细了解如何创建、注册通行密钥并使用通行密钥进行身份验证,请参阅我们的开发者文档。

安全账号重置

如果未经授权的攻击者能够访问已解锁的设备(例如手机被盗时),则会尝试访问敏感应用,尤其是银行应用或现金应用。如果应用实现了生物识别验证,攻击者会尝试重置帐号以进入帐号。让帐号重置流程不仅仅依赖于设备上可轻松访问的信息,例如电子邮件或短信动态密码重置链接。

以下是可以整合到应用的重置流程中的常见最佳实践:

  • 除动态密码外的人脸识别
  • 安全问题
  • 知识因素(例如母亲的婚前姓、出生城市或喜爱的歌曲)
  • 身份证件验证

SMS Retriever API

利用 SMS Retriever API,您可以在 Android 应用中自动执行基于短信的用户验证。这样,用户就不需要手动输入验证码。此外,此 API 不会要求用户提供额外的可能危险的应用权限,如 RECEIVE_SMSREAD_SMS。不过,为了防止设备在本地遭到未经授权的访问,不应将短信作为唯一的用户验证手段。

这些信息如何帮助防范欺诈行为

有些用户使用短信验证码作为唯一的身份验证手段,这为欺诈提供了方便的入口点。

SMS Retriever API 允许应用直接检索短信验证码,而无需用户互动,并且可以提供一定程度的欺诈防护。

实现

实现 SMS Retriever API 的过程分为两个部分:Android 和 Server。

Android:(指南

  1. 获取用户的电话号码。
  2. 启动短信检索器客户端。
  3. 将电话号码发送到您的服务器。
  4. 接收验证邮件。
  5. 将动态密码发送至您的服务器。

服务器:(指南

  1. 构建验证消息。
  2. 通过短信发送验证消息。
  3. 验证系统返回的动态密码。

最佳实践

应用集成并且使用 SMS Retriever API 验证用户的电话号码后,它会尝试获取动态密码。如果成功,这是一个强烈信号,表示设备自动收到了短信。如果登录失败,并且用户需要手动输入动态密码,这可能是一个警告信号,表明用户可能存在欺诈行为。

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

了解详情

如需详细了解最佳实践,请查看以下资源: