检测密码泄露和凭据盗用

本页介绍了如何使用 reCAPTCHA 的密码泄露检测功能来检测密码泄露和凭据盗用,以防范账号盗用 (ATO) 和凭据填充攻击。借助 reCAPTCHA,您可以在进行任何评估过程中对用户凭据(密码)进行定期审核,以确保它们没有被泄露或盗用。为了执行这些评估,Google 会使用密码安全检查功能。

准备工作

  1. 为 reCAPTCHA 准备环境

  2. 设置 reCAPTCHA

  3. Make sure that billing is enabled for your Google Cloud project.

    reCAPTCHA 要求在项目上关联并启用结算功能才能使用密码泄露检测功能。您可以使用信用卡或现有的 Google Cloud 项目结算 ID 来启用结算功能。如果您需要结算方面的帮助,请与 Cloud Billing 支持团队联系。

检查凭据是否被盗用和/或泄露

您可以使用加密函数或 Docker 容器来检查一组凭据是否已泄露。

Docker 容器是一个开源客户端,用于实现安全多方计算,以保护最终用户隐私并安全地查找密码泄露。如需了解详情,请参阅 GitHub 代码库。 Docker 容器抽象化了实现加密算法的复杂性,并简化了安装过程。您还可以使用它在基础架构中托管容器应用。

加密函数

如需检查一组凭据是否已遭泄露,请在为登录、密码更改和密码重置等操作创建评估时使用密码泄露检测。

如需检查是否存在密码泄露和被盗凭据,请完成以下步骤:

  1. 生成请求参数
  2. 创建用于检测密码泄露的评估
  3. 验证评估中泄露的凭据
  4. 解读判定结果并采取行动

生成请求参数

  1. 使用高隐私保护协议所需的加密函数计算必要的请求参数。reCAPTCHA 提供了 Java 和 TypeScript 库来协助生成这些字段:

  2. 如需创建密码检查验证,请创建一个 PasswordCheckVerifier 对象。

    PasswordCheckVerifier verifier = new PasswordCheckVerifier();
    
  3. 如需发起验证,请调用 PasswordCheckVerifier#createVerification。此方法使用用户名和密码计算参数以执行密码检查。

    PasswordCheckVerification verification = verifier.createVerification("username", "password").get();
    
  4. 使用验证参数创建评估

    byte[] lookupHashPrefix = verification.getLookupHashPrefix();
    byte[] encryptedUserCredentialsHash = verification.getEncryptedUserCredentialsHash();
    

    字节数组 lookupHashPrefixencryptedUserCredentialsHash 包含发起密码检查 Assessment 所需的参数。

创建评估以检测密码泄露

使用 projects.assessments.create 方法。

在使用任何请求数据之前,请先进行以下替换:

  • PROJECT_ID:您的 Google Cloud 项目 ID
  • LOOKUP_HASH_PREFIX:用户名 SHA-256 哈希前缀
  • ENCRYPTED_USER_CREDENTIALS_HASH:加密的用户凭据 Scrypt 哈希

HTTP 方法和网址:

POST https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments

请求 JSON 正文:

{
  "private_password_leak_verification": {
    "lookup_hash_prefix": "LOOKUP_HASH_PREFIX",
    "encrypted_user_credentials_hash": "ENCRYPTED_USER_CREDENTIALS_HASH"
  }
}

如需发送请求,请选择以下方式之一:

curl

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

curl -X POST \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments"

PowerShell

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method POST `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/assessments" | Select-Object -Expand Content

您应该收到类似以下内容的 JSON 响应:

{
  "name": "projects/698047609967/assessments/fb22000000000000",
  "score": 0,
  "reasons": [],
  "privatePasswordLeakVerification": {
    "lookupHashPrefix": "zoxZwA==",
    "encryptedUserCredentialsHash": "AyRihRcKaGLj/FA/r2uqQY/fzfTaDb/nEcIUMeD3Tygp",
    "reencryptedUserCredentialsHash": "Aw65yEbLM39ww1ridDEfx5VhkWo11tzn/R1B88Qqwr/+"
    "encryptedLeakMatchPrefixes": [
      "n/n5fvPD6rmQPFyb4xk=", "IVQqzXsbZenaibID6OI=", ..., "INeMMndrfnlf6osCVvs=",
      "MkIpxt2x4mtyBnRODu0=", "AqUyAUWzi+v7Kx03e6o="]
  }
}

验证评估中泄露的凭据

从评估响应中提取 reEncryptedUserCredentialsencryptedLeakMatchPrefixes 字段,并将其传递给验证器对象,以确定凭据是否泄露。

PasswordCheckResult result = verifier.verify(verification,
result.getReEncryptedUserCredentials(),
result.getEncryptedLeakMatchPrefixes()
).get();

System.out.println("Credentials leaked: " + result.areCredentialsLeaked());

代码示例

如需了解如何使用 TypeScript 实现密码泄露检测,请参阅 GitHub 上的 TypeScript 代码示例

以下代码示例展示了如何使用 Java 实现密码泄露检测:

Java

如需向 reCAPTCHA 进行身份验证,请设置应用默认凭据。 如需了解详情,请参阅为本地开发环境设置身份验证


import com.google.cloud.recaptcha.passwordcheck.PasswordCheckResult;
import com.google.cloud.recaptcha.passwordcheck.PasswordCheckVerification;
import com.google.cloud.recaptcha.passwordcheck.PasswordCheckVerifier;
import com.google.cloud.recaptchaenterprise.v1.RecaptchaEnterpriseServiceClient;
import com.google.protobuf.ByteString;
import com.google.recaptchaenterprise.v1.Assessment;
import com.google.recaptchaenterprise.v1.CreateAssessmentRequest;
import com.google.recaptchaenterprise.v1.PrivatePasswordLeakVerification;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.bouncycastle.util.encoders.Base64;

public class CreatePasswordLeakAssessment {

  public static void main(String[] args)
      throws IOException, ExecutionException, InterruptedException {
    // TODO(developer): Replace these variables before running the sample.
    // Google Cloud Project ID.
    String projectID = "project-id";

    // Username and password to be checked for credential breach.
    String username = "username";
    String password = "password";

    checkPasswordLeak(projectID, username, password);
  }

  /*
   * Detect password leaks and breached credentials to prevent account takeovers
   * (ATOs) and credential stuffing attacks.
   * For more information, see:
   * https://cloud.google.com/recaptcha-enterprise/docs/check-passwords and
   * https://security.googleblog.com/2019/02/protect-your-accounts-from-data.html

   * Steps:
   * 1. Use the 'create' method to hash and Encrypt the hashed username and
   * password.
   * 2. Send the hash prefix (26-bit) and the encrypted credentials to create
   * the assessment.(Hash prefix is used to partition the database.)
   * 3. Password leak assessment returns a list of encrypted credential hashes to
   * be compared with the decryption of the returned re-encrypted credentials.
   * Create Assessment also sends back re-encrypted credentials.
   * 4. The re-encrypted credential is then locally verified to see if there is
   * a match in the database.
   *
   * To perform hashing, encryption and verification (steps 1, 2 and 4),
   * reCAPTCHA Enterprise provides a helper library in Java.
   * See, https://github.com/GoogleCloudPlatform/java-recaptcha-password-check-helpers

   * If you want to extend this behavior to your own implementation/ languages,
   * make sure to perform the following steps:
   * 1. Hash the credentials (First 26 bits of the result is the
   * 'lookupHashPrefix')
   * 2. Encrypt the hash (result = 'encryptedUserCredentialsHash')
   * 3. Get back the PasswordLeak information from
   * reCAPTCHA Enterprise Create Assessment.
   * 4. Decrypt the obtained 'credentials.getReencryptedUserCredentialsHash()'
   * with the same key you used for encryption.
   * 5. Check if the decrypted credentials are present in
   * 'credentials.getEncryptedLeakMatchPrefixesList()'.
   * 6. If there is a match, that indicates a credential breach.
   */
  public static void checkPasswordLeak(
      String projectID, String username, String password)
      throws ExecutionException, InterruptedException, IOException {

    // Instantiate the java-password-leak-helper library to perform the cryptographic functions.
    PasswordCheckVerifier passwordLeak = new PasswordCheckVerifier();

    // Create the request to obtain the hash prefix and encrypted credentials.
    PasswordCheckVerification verification =
        passwordLeak.createVerification(username, password).get();

    byte[] lookupHashPrefix = Base64.encode(verification.getLookupHashPrefix());
    byte[] encryptedUserCredentialsHash = Base64.encode(
        verification.getEncryptedUserCredentialsHash());

    // Pass the credentials to the createPasswordLeakAssessment() to get back
    // the matching database entry for the hash prefix.
    PrivatePasswordLeakVerification credentials =
        createPasswordLeakAssessment(
            projectID,
            lookupHashPrefix,
            encryptedUserCredentialsHash);

    // Convert to appropriate input format.
    List<byte[]> leakMatchPrefixes =
        credentials.getEncryptedLeakMatchPrefixesList().stream()
            .map(x -> Base64.decode(x.toByteArray()))
            .collect(Collectors.toList());

    // Verify if the encrypted credentials are present in the obtained match list.
    PasswordCheckResult result =
        passwordLeak
            .verify(
                verification,
                Base64.decode(credentials.getReencryptedUserCredentialsHash().toByteArray()),
                leakMatchPrefixes)
            .get();

    // Check if the credential is leaked.
    boolean isLeaked = result.areCredentialsLeaked();
    System.out.printf("Is Credential leaked: %s", isLeaked);
  }

  // Create a reCAPTCHA Enterprise assessment.
  // Returns:  PrivatePasswordLeakVerification which contains
  // reencryptedUserCredentialsHash and credential breach database
  // whose prefix matches the lookupHashPrefix.
  private static PrivatePasswordLeakVerification createPasswordLeakAssessment(
      String projectID,
      byte[] lookupHashPrefix,
      byte[] encryptedUserCredentialsHash)
      throws IOException {
    try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) {

      // Set the hashprefix and credentials hash.
      // Setting this will trigger the Password leak protection.
      PrivatePasswordLeakVerification passwordLeakVerification =
          PrivatePasswordLeakVerification.newBuilder()
              .setLookupHashPrefix(ByteString.copyFrom(lookupHashPrefix))
              .setEncryptedUserCredentialsHash(ByteString.copyFrom(encryptedUserCredentialsHash))
              .build();

      // Build the assessment request.
      CreateAssessmentRequest createAssessmentRequest =
          CreateAssessmentRequest.newBuilder()
              .setParent(String.format("projects/%s", projectID))
              .setAssessment(
                  Assessment.newBuilder()
                      // Set request for Password leak verification.
                      .setPrivatePasswordLeakVerification(passwordLeakVerification)
                      .build())
              .build();

      // Send the create assessment request.
      Assessment response = client.createAssessment(createAssessmentRequest);

      // Get the reCAPTCHA Enterprise score.
      float recaptchaScore = response.getRiskAnalysis().getScore();
      System.out.println("The reCAPTCHA score is: " + recaptchaScore);

      // Get the assessment name (id). Use this to annotate the assessment.
      String assessmentName = response.getName();
      System.out.println(
          "Assessment name: " + assessmentName.substring(assessmentName.lastIndexOf("/") + 1));

      return response.getPrivatePasswordLeakVerification();
    }
  }
}

Docker 容器

如需检查凭据是否泄露,请使用 localhost 连接或在容器上设置 HTTPS,将用户名和密码凭据对安全地发送到容器。然后,容器会先对这些凭据进行加密,然后再向 reCAPTCHA 发出 API 请求,并在本地验证重新加密的结果。

如需向 Docker 容器发送请求,请完成以下步骤:

  1. 设置 Docker
  2. 为 Docker 容器准备环境
  3. 构建并运行容器
  4. 向容器发送 HTTP 请求
  5. 解读判定结果并采取行动

准备运行 Docker 容器

  1. 选择身份验证策略。

    该容器支持设置应用默认凭据,或可以接受 API 密钥进行身份验证。

  2. 将 PLD 容器配置为使用 HTTPS 或在仅限本地主机的演示模式下运行。

    由于该容器接受敏感的最终用户凭据(用户名和密码),因此必须使用 HTTPS 或在仅限本地主机的演示模式下运行。如需有关 HTTPS 配置的指导,请参阅 GitHub 上的 README

以下步骤使用 API 密钥身份验证,并在仅限本地主机的演示模式下运行客户端。

构建和运行 Docker 容器

  1. 克隆代码库:

    git clone github.com/GoogleCloudPlatform/reCAPTCHA-PLD
    
  2. 构建容器。

    docker build . -t pld-local
    
  3. 启动容器:

    docker run --network host \
    -e RECAPTCHA_PROJECT_ID=PROJECT_ID \
    -e GOOGLE_CLOUD_API_KEY=API_KEY \
    pld-local
    

容器随即启动,并开始在 localhost 的端口 8080 上处理请求。

发送 localhost 请求

在使用任何请求数据之前,请先进行以下替换:

  • LEAKED_USERNAME:泄露的凭据对的用户名。
  • LEAKED_PASSWORD:泄露的凭据对的密码。

HTTP 方法和网址:

POST http://localhost:8080/createAssessment/

请求 JSON 正文:

{
    "username":"LEAKED_USERNAME",
    "password":"LEAKED_PASSWORD"
}

如需发送请求,请选择以下方式之一:

curl

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

curl -X POST \
-H "Content-Type: application/json; charset=utf-8" \
-d @request.json \
"http://localhost:8080/createAssessment/"

PowerShell

将请求正文保存在名为 request.json 的文件中,然后执行以下命令:

$headers = @{  }

Invoke-WebRequest `
-Method POST `
-Headers $headers `
-ContentType: "application/json; charset=utf-8" `
-InFile request.json `
-Uri "http://localhost:8080/createAssessment/" | Select-Object -Expand Content

您应该收到类似以下内容的 JSON 响应:


  { "leakedStatus":"LEAKED" }


 OR


  { "leakedStatus":"NO_STATUS" }

解读判定结果并采取行动

评估响应会显示凭据是否泄露,并为您提供可用于采取适当措施来保护用户的信息。

下表列出了在检测到泄露的密码时建议执行的操作:

检测到密码泄露 保护用户的措施
登录期间
  • 拒绝并要求客户选择其他密码,并尽可能触发 MFA 质询。
  • 提醒用户为各个账号使用不同的密码。
  • 要求在您的网站上使用安全系数高的密码。
  • 如果用户未使用多重身份验证 (MFA),则提示用户启用多重身份验证。
在创建账号或重置密码时
  • 要求用户更改密码。
  • 触发多重身份验证 (MFA) 流程。

如果您尚未在网站上使用多重身份验证 (MFA) 提供程序,则可以使用 reCAPTCHA 的多重身份验证功能

后续步骤