检测密码泄露和凭据泄露

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

准备工作

  1. 选择在您的环境中设置 reCAPTCHA Enterprise 的最佳方法,并完成设置。

  2. 批准后即可访问密码泄露检测。请与我们的销售团队联系,在您的网站上启用此功能。

  3. 确保您的 Cloud 项目已启用结算功能。了解如何检查项目是否已启用结算功能

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

检查是否发生凭据泄露和/或泄露凭据

要检查一组凭据是否已遭泄露,请在对操作进行评估期间查询密码安全检查数据库,例如登录和密码更改或重置。

如需使用密码泄露检测服务并确定响应中是否存在泄漏,您必须使用高度隐私协议所需的加密函数来计算参数。为此,reCAPTCHA Enterprise 提供了一个 Java 库:java-recaptcha-password-check-helpers

生成请求参数

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

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

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

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

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

API 请求

使用 projects.assessments.create 方法。

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

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

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 application-default 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 application-default 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="]
  }
}

验证密码泄露

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

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

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

代码示例

Java


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.Event;
import com.google.recaptchaenterprise.v1.PrivatePasswordLeakVerification;
import com.google.recaptchaenterprise.v1.TokenProperties;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

public class CreatePasswordLeakAssessment {

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

    // Site key obtained by registering a domain/app to use recaptcha Enterprise.
    String recaptchaSiteKey = "recaptcha-site-key";

    // The token obtained from the client on passing the recaptchaSiteKey.
    // To get the token, integrate the recaptchaSiteKey with frontend. See,
    // https://cloud.google.com/recaptcha-enterprise/docs/instrument-web-pages#frontend_integration_score
    String token = "recaptcha-token";

    // Action name corresponding to the token.
    String recaptchaAction = "recaptcha-action";

    checkPasswordLeak(projectID, recaptchaSiteKey, token, recaptchaAction);
  }

  /*
  * 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/getting-started
  * and https://security.googleblog.com/2019/02/protect-your-accounts-from-data.html

  * Steps:
  * 1. Use the 'createVerification' method to hash and Encrypt the hashed username and password.
  * 2. Send the hash prefix (2-byte) and the encrypted credentials to create the assessment.
  * (Hash prefix is used to partition the database.)
  * 3. Password leak assessment returns a database whose prefix matches the sent hash prefix.
  * 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 2 bytes 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 recaptchaSiteKey, String token, String recaptchaAction)
      throws ExecutionException, InterruptedException, IOException {
    // Set the username and password to be checked.
    String username = "username";
    String password = "password123";

    // 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 = verification.getLookupHashPrefix();
    byte[] encryptedUserCredentialsHash = verification.getEncryptedLookupHash();

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

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

    // Verify if the encrypted credentials are present in the obtained match list.
    PasswordCheckResult result =
        passwordLeak
            .verify(
                verification,
                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,
      String recaptchaSiteKey,
      String token,
      String recaptchaAction,
      byte[] lookupHashPrefix,
      byte[] encryptedUserCredentialsHash)
      throws IOException {
    try (RecaptchaEnterpriseServiceClient client = RecaptchaEnterpriseServiceClient.create()) {

      // Set the properties of the event to be tracked.
      Event event = Event.newBuilder().setSiteKey(recaptchaSiteKey).setToken(token).build();

      // 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()
                      .setEvent(event)
                      // Set request for Password leak verification.
                      .setPrivatePasswordLeakVerification(passwordLeakVerification)
                      .build())
              .build();

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

      // Check validity and integrity of the response.
      if (!checkTokenIntegrity(response.getTokenProperties(), recaptchaAction)) {
        return passwordLeakVerification;
      }

      // 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();
    }
  }

  // Check for token validity and action integrity.
  private static boolean checkTokenIntegrity(
      TokenProperties tokenProperties, String recaptchaAction) {
    // Check if the token is valid.
    if (!tokenProperties.getValid()) {
      System.out.println(
          "The Password check call failed because the token was: "
              + tokenProperties.getInvalidReason().name());
      return false;
    }

    // Check if the expected action was executed.
    if (!tokenProperties.getAction().equals(recaptchaAction)) {
      System.out.printf(
          "The action attribute in the reCAPTCHA tag '%s' does not match "
              + "the action '%s' you are expecting to score",
          tokenProperties.getAction(), recaptchaAction);
      return false;
    }
    return true;
  }
}