비밀번호 유출 및 유출된 사용자 인증 정보 감지

이 페이지에서는 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="]
  }
}

평가에서 유출된 사용자 인증 정보 확인

평가 응답에서 reEncryptedUserCredentialsencryptedLeakMatchPrefixes 필드를 추출하고 확인자 객체에 전달하여 사용자 인증 정보 유출 여부를 확인합니다.

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 기능을 사용할 수 있습니다.

다음 단계