检测密码泄露和凭据盗用

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

准备工作

  1. 为 reCAPTCHA 准备环境

  2. 设置 reCAPTCHA

  3. Verify 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());

代码示例

Node.js (TypeScript)

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

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 运行或以仅限 localhost 的演示模式运行。

    由于容器接受敏感的最终用户凭据(用户名和密码),因此必须通过 HTTPS 运行,或者以仅限 localhost 的演示模式运行。如需查看有关 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 的 MFA 功能

后续步骤