パスワードの漏洩と認証情報侵害を検出する

このページでは、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 ライブラリが用意されています。
  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 を開始するために必要なパラメータが含まれています。

パスワード漏洩を検出する評価の作成

projects.assessments.create メソッドを使用します。

リクエストのデータを使用する前に、次のように置き換えます。

  • PROJECT_ID: 実際の Google Cloud プロジェクト ID
  • LOOKUP_HASH_PREFIX: ユーザー名のプレフィックス SHA-256 ハッシュ プレフィックス
  • ENCRYPTED_USER_CREDENTIALS_HASH: 暗号化されたユーザー認証情報 Scrypt ハッシュ

HTTP メソッドと URL:

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

評価から漏洩した認証情報を検証する

reEncryptedUserCredentials レスポンスからフィールド encryptedLeakMatchPrefixes と を抽出し、検証オブジェクトに渡して認証情報が漏洩しているかどうかを判断します。

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

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

コードサンプル

次のコードサンプルは、Java ライブラリと 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();
    }
  }
}

Node.js (TypeScript)

reCAPTCHA に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。詳細については、ローカル開発環境の認証の設定をご覧ください。


import {google} from '@google-cloud/recaptcha-enterprise/build/protos/protos';
import {RecaptchaEnterpriseServiceClient} from '@google-cloud/recaptcha-enterprise';
import {PasswordCheckVerification} from 'recaptcha-password-check-helpers';

// TODO(developer): Uncomment and set the following variables
// Google Cloud Project ID.
// const projectId: string = "PROJECT_ID"

// Username and password to be checked for credential breach.
// const username: string = "user123";
// const password: string = "1234@abcd";

// Create a client.
const client: RecaptchaEnterpriseServiceClient =
  new RecaptchaEnterpriseServiceClient();

/*
* 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 Typescript.
* See, https://github.com/GoogleCloudPlatform/typescript-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.
*/
async function checkPasswordLeak(
  projectId: string,
  username: string,
  password: string
) {
  // Instantiate the recaptcha-password-check-helpers library to perform the
  // cryptographic functions.
  // Create the request to obtain the hash prefix and encrypted credentials.
  const verification: PasswordCheckVerification =
    await PasswordCheckVerification.create(username, password);

  const lookupHashPrefix: string = Buffer.from(
    verification.getLookupHashPrefix()
  ).toString('base64');
  const encryptedUserCredentialsHash: string = Buffer.from(
    verification.getEncryptedUserCredentialsHash()
  ).toString('base64');
  console.log('Hashes created.');

  // Pass the credentials to the createPasswordLeakAssessment() to get back
  // the matching database entry for the hash prefix.
  const credentials: google.cloud.recaptchaenterprise.v1.IPrivatePasswordLeakVerification =
    await createPasswordLeakAssessment(
      projectId,
      lookupHashPrefix,
      encryptedUserCredentialsHash
    );

  // Convert to appropriate input format.
  const reencryptedUserCredentialsHash: Uint8Array = Buffer.from(
    credentials.reencryptedUserCredentialsHash!.toString(),
    'base64'
  );
  const encryptedLeakMatchPrefixes: Uint8Array[] =
    credentials.encryptedLeakMatchPrefixes!.map(prefix => {
      return Buffer.from(prefix.toString(), 'base64');
    });

  // Verify if the encrypted credentials are present in the obtained
  // match list to check if the credential is leaked.
  const isLeaked: boolean = verification
    .verify(reencryptedUserCredentialsHash, encryptedLeakMatchPrefixes)
    .areCredentialsLeaked();

  console.log(`Is Credential leaked: ${isLeaked}`);
}

// Create a reCAPTCHA Enterprise assessment.
// Returns: PrivatePasswordLeakVerification which contains
// reencryptedUserCredentialsHash and credential breach database whose prefix
// matches the lookupHashPrefix.
async function createPasswordLeakAssessment(
  projectId: string,
  lookupHashPrefix: string,
  encryptedUserCredentialsHash: string
): Promise<google.cloud.recaptchaenterprise.v1.IPrivatePasswordLeakVerification> {
  // Build the assessment request.
  const createAssessmentRequest: google.cloud.recaptchaenterprise.v1.ICreateAssessmentRequest =
    {
      parent: `projects/${projectId}`,
      assessment: {
        // Set the hashprefix and credentials hash.
        // Setting this will trigger the Password leak protection.
        privatePasswordLeakVerification: {
          lookupHashPrefix: lookupHashPrefix,
          encryptedUserCredentialsHash: encryptedUserCredentialsHash,
        },
      },
    };

  // Send the create assessment request.
  const [response] = await client.createAssessment(createAssessmentRequest);

  // Get the reCAPTCHA Enterprise score.
  console.log(`The reCAPTCHA score is: ${response.riskAnalysis?.score}`);
  // Get the assessment name (id). Use this to annotate the assessment.
  console.log(`Assessment name: ${response.name}`);

  if (!response?.privatePasswordLeakVerification) {
    throw `Error in obtaining response from Private Password Leak Verification ${response}`;
  }

  return response.privatePasswordLeakVerification;
}

checkPasswordLeak(projectId, username, password).catch(err => {
  console.error(err.message);
  process.exitCode = 1;
});

Docker コンテナ

認証情報が漏洩しているかどうかを確認するには、localhost 接続を使用するか、コンテナに HTTPS を設定して、ユーザー名とパスワードの認証情報ペアをコンテナに安全に送信します。コンテナは、reCAPTCHA への API リクエストを行う前にこれらの認証情報を暗号化し、再暗号化された結果をローカルで検証します。

Docker コンテナにリクエストを送信する手順は次のとおりです。

  1. Docker を設定する
  2. Docker コンテナの環境を準備する
  3. コンテナをビルドして実行する
  4. コンテナに HTTP リクエストを送信する
  5. 判定を解釈して対処する

Docker コンテナの実行を準備する

  1. 認証戦略を選択します。

    コンテナは、アプリケーションのデフォルト認証情報の設定をサポートしています。また、認証に API キーを受け入れることもできます。

  2. HTTPS または localhost 専用のデモモードで実行するように PLD コンテナを構成します。

    コンテナは機密性の高いエンドユーザー認証情報(ユーザー名とパスワード)を受け入れるため、HTTPS または localhost 専用のデモモードで実行する必要があります。HTTPS 構成のガイダンスについては、GitHub の README をご覧ください。

次の手順では、API キー認証を使用して、localhost 専用のデモモードでクライアントを実行します。

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 メソッドと URL:

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 機能を使用できます。

次のステップ