安全的用户身份验证

为了保护 Android 中的身份验证系统,请考虑弃用基于密码的模式,尤其是对于用户的银行账号和电子邮件账号等敏感账号。请注意,用户安装的某些应用可能并非出于善意,可能会试图对您的用户进行钓鱼式攻击。

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

生物识别身份验证对话框

生物识别库提供了一组函数,用于显示请求进行生物识别验证(例如人脸识别或指纹识别)的提示。 不过,生物识别提示可以配置为回退到 LSKF,而 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,详细了解生物识别技术。

您可以根据自己的使用情形,实现需要或不需要用户明确操作的对话框。为避免欺诈,我们建议您为每笔交易添加生物识别对话框,并要求用户明确执行操作。我们知道,添加身份验证可能会给用户体验带来一些不便,但考虑到银行交易中处理的信息的性质,以及生物识别身份验证比其他身份验证方法更顺畅,我们认为有必要添加此级别的导航。

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

通行密钥

通行密钥是一种更安全、更便捷的替代密码的方法。通行密钥使用公钥加密技术,让用户能够使用设备的屏幕锁定机制(例如指纹或人脸识别)登录应用和网站。这样一来,用户无需记住和管理密码,安全性也显著提高。

通行密钥只需一步即可满足多重身份验证要求,可取代密码和 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 Codelab 操作,初步了解如何实现通行密钥。
  2. 查看通行密钥用户体验设计指南。本文档将向您展示针对您的使用情形推荐的工作流程。
  3. 按照指南学习 Credential Manager。
  4. 为您的应用规划凭据管理器和通行密钥实现。规划添加对 Digital Asset Links 的支持。

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

安全重置账号

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

以下是一些常见最佳实践,您可以将其纳入应用的重置流程中:

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

SMS Retriever API

借助 SMS Retriever API,您可以在 Android 应用中自动以短信方式执行用户验证。这样一来,用户就不需要手动输入验证码。此外,此 API 不会要求用户授予额外的、可能存在危险的应用权限,例如 RECEIVE_SMSREAD_SMS。不过,不应仅使用短信作为用户验证方式,以防未经授权的本地访问。

这有助于减少欺诈行为

部分用户仅使用短信验证码作为身份验证因素,这为欺诈行为提供了可乘之机。

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

实现

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

Android:(指南

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

服务器:(指南

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

最佳做法

应用集成完毕后,系统会使用 SMS Retriever API 验证用户的电话号码,并尝试获取动态密码。如果成功,则表明设备已自动收到短信。如果自动填充失败,并且用户需要手动输入 OTP,则可能表明用户正遭受欺诈。

短信不应作为唯一的用户验证机制,因为这会给本地攻击(例如攻击者抢劫已解锁的设备)或 SIM 卡克隆攻击留下可乘之机。建议尽可能使用生物识别技术。在没有生物识别传感器的设备上,用户身份验证应至少依赖于一种不易从当前设备获取的因素。

了解详情

如需进一步了解最佳实践,请参阅以下资源: