๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ์ถœ ๋ฐ ์œ ์ถœ๋œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๊ฐ์ง€

์ด ํŽ˜์ด์ง€์—์„œ๋Š” 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();
    

    ๋ฐ”์ดํŠธ ๋ฐฐ์—ด lookupHashPrefix ๋ฐ encryptedUserCredentialsHash์—๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ 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());

์ฝ”๋“œ ์ƒ˜ํ”Œ

Node.js(TypeScript)

Node.js (TypeScript)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๋ฐ€๋ฒˆํ˜ธ ์œ ์ถœ ๊ฐ์ง€๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด๋ ค๋ฉด GitHub์˜ TypeScript ์ฝ”๋“œ ์ƒ˜ํ”Œ์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์ž๋ฐ”

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๋ฅผ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ์Œ์„ ์ปจํ…Œ์ด๋„ˆ์— ์•ˆ์ „ํ•˜๊ฒŒ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ปจํ…Œ์ด๋„ˆ์—์„œ ์ด๋Ÿฌํ•œ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋ฅผ ์•”ํ˜ธํ™”ํ•œ ํ›„ API ์š”์ฒญ์„ reCAPTCHA์— ๋ณด๋‚ด๊ณ  ๋กœ์ปฌ์—์„œ ๋‹ค์‹œ ์•”ํ˜ธํ™”๋œ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

์š”์ฒญ์„ Docker ์ปจํ…Œ์ด๋„ˆ์— ์ „์†กํ•˜๋ ค๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๋ฅผ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค.

  1. Docker๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  2. Docker ์ปจํ…Œ์ด๋„ˆ ํ™˜๊ฒฝ์„ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค.
  3. ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  4. HTTP ์š”์ฒญ์„ ์ปจํ…Œ์ด๋„ˆ์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  5. ๊ฒฐ๊ณผ๋ฅผ ํ•ด์„ํ•˜๊ณ  ์กฐ์น˜๋ฅผ ์ทจํ•ฉ๋‹ˆ๋‹ค.

Docker ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰ ์ค€๋น„

  1. ์ธ์ฆ ์ „๋žต์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

    ์ปจํ…Œ์ด๋„ˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ธฐ๋ณธ ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ์„ค์ •์„ ์ง€์›ํ•˜๊ฑฐ๋‚˜ ์ธ์ฆ์„ ์œ„ํ•ด API ํ‚ค๋ฅผ ์ˆ˜๋ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  2. PLD ์ปจํ…Œ์ด๋„ˆ๊ฐ€ HTTPS ๋˜๋Š” localhost ์ „์šฉ ๋ฐ๋ชจ ๋ชจ๋“œ์—์„œ ์‹คํ–‰๋˜๋„๋ก ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

    ์ปจํ…Œ์ด๋„ˆ๋Š” ๋ฏผ๊ฐํ•œ ์ตœ์ข… ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด(์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋ฐ ๋น„๋ฐ€๋ฒˆํ˜ธ)๋ฅผ ํ—ˆ์šฉํ•˜๋ฏ€๋กœ 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 ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ๋‹จ๊ณ„