クライアント署名

概要

クライアント署名により、リスク分析を改善するための追加情報をユーザーが reCAPTCHA に送信できます。情報は暗号化されるため、ユーザーや潜在的な攻撃者は、このデータの読み取りまたは改ざんができません。

ペイロード

ペイロードは、次のフィールドが含まれる JSON オブジェクトです。

  • ts_ms: エポックからのミリ秒数。これは現在の時刻に設定します。リプレイ攻撃の防止に使用されます。数分前の ClientSignature が見つかった場合は、EXPIRED を返します。UNIX エポックとも呼ばれます。
  • session_id: ソリューションを識別してこのセッションにバインドする一意の ID。ID ごとに有効にできる recaptcha ソリューションは 1 つだけです。string として渡します。
  • url_hash: 完全な URL の 8 文字のハッシュ プレフィックス(スキーム/パス/パラメータを含む)。
  • ua_hash: ユーザー エージェントの 8 文字のハッシュ プレフィックス。
  • callback_hash: JavaScript コールバックの 10 文字のハッシュ プレフィックス(空白は削除)。コールバックがない場合は、コールバックを追加し、getResponse() や フォーム フィールドから読み取るのではなく、そのコールバックを使用してください。

たとえば、コールバックが次の場合:

function(response) {
  document.location = "//example.com/recaptcha?session=ef969321&recaptcha=" + response;
}

ハッシュ化するテキストは次のようになります。

document.location="//example.com/recaptcha?session=ef969321&recaptcha="+response;

これにより、9B39FBB667 で始まるハッシュが生成されます。

  • ip: エンドユーザーの IP アドレス。IPv4 または IPv6 を使用できます。

暗号化

AES-GCM でオブジェクトを暗号化する

  • JSON オブジェクトをバイトに変換(UTF-8)します。
  • reCAPTCHA 共有シークレットをハッシュ(SHA256)し、バイト(UTF-8)を取得して、暗号化鍵を準備します。
  • 12 バイトの IV と 16 バイトの認証タグを使用し、最終結果の前に iv を追加して、JSON ペイロードを暗号化します。
  • base64 としてエンコードされた iv +暗号化された blob を返します。

Python の例:

import base64
import hashlib
import json
import os

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def GetEncryptedClientSignature(timestamp_ms, session_id):
  iv = os.urandom(12)
  aesgcm = AESGCM(hashlib.sha256(SHARED_SECRET.encode('utf-8')).digest())
  signature = json.dumps({
      'session_id': session_id,
      'ts_ms': timestamp_ms
  })
  return base64.urlsafe_b64encode(
      iv + aesgcm.encrypt(iv, signature.encode('utf-8'), None)).decode().rstrip('=')

Shared Secret

ペイロードの暗号化に使用される共有シークレットは、のリソースのプロパティです。クライアント署名は試験運用版の機能であるため、共有シークレットは Cloud Console や gcloud CLI では表示されません。代わりに、REST API を使用する必要があります。共有シークレットは鍵ごとに保持されます。

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

  • PROJECT_ID: 実際の Google Cloud プロジェクト ID
  • KEY_NAME: 探索する鍵の名前

HTTP メソッドと URL:

GET https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/keys/key-name

リクエストを送信するには、次のいずれかのオプションを選択します。

curl

次のコマンドを実行します。

curl -X GET \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/keys/key-name"

PowerShell

次のコマンドを実行します。

$cred = gcloud auth print-access-token
$headers = @{ "Authorization" = "Bearer $cred" }

Invoke-WebRequest `
-Method GET `
-Headers $headers `
-Uri "https://recaptchaenterprise.googleapis.com/v1/projects/PROJECT_ID/keys/key-name" | Select-Object -Expand Content

次のような JSON レスポンスが返されます。

{
  "name": "projects/PROJECT_ID/keys/KEY_NAME",
  "shared_secret": "6Ldqgs0UAAAAAIn4k7YxEB-LwEh5S9-Gv6IIWB8m"
}

Hashes

ハッシュ プレフィックスは SHA256 HEX エンコード文字列である必要があります。完全なハッシュの代わりに接頭辞を送信できます。そうすると暗号化されたペイロードのサイズが小さくなります。8 バイト長の接頭辞を使用することをおすすめします。

Python の例:

import hashlib
import re

# Replace this with your JS function as it will show up in production.
f = """
function onSuccess(token) { a = token; form.submit(); }
"""

whitespace_pattern = re.compile(r'\s+');
function_pattern = re.compile(r'[^\{]*\{([\s\S]*)\};?$')

f = re.sub(whitespace_pattern, '', f)
f = re.search(function_pattern, f)
if not f:
  print("function didn't seem like a function, did you include the {} ?")
else:
  print(hashlib.sha256(f.groups()[0].encode('utf-8')).hexdigest())

配信

reCAPTCHA をリクエストする場合、インラインの場合は結果を data-s として、明示的にレンダリングする場合は s として含めます。

<div
 data-sitekey="6LehsLkSAAAAAPF9gNzrbSN_vxQLgEFZW4-ZzSJa"
 data-callback="onSuccess"
 data-s="ZSYCxsDyhoYOSKBQkcFsx0N7b3XGSTanV0ESfgO5f5yxeklC29VWwSB8Wa1VZtkswPZCPCuxadxfHXRxXsdP4f5lL3knjxwAAYB1aydtt2Ae0KXo2tb10q72JKb0AxF_BXT4FUEj3URw4CAtFOTgSHoF_WhNY3yrjpm5KWmhl8pigfSv50Tv7h7WCYRA8cT3BsBzH3TZ">
</div>

次に execute() の使用例を示します。

grecaptcha.enterprise.execute(SITE_KEY, {action: "button", s: clientSignature}).then(
  function(token) {
    sendToBackendAndVerify(SITE_KEY, token);
  });

検証

ClientSignature に関連する情報は、Assessmenttoken_properties.client_signature フィールドに返されます。

// Customers must confirm that this ID matches the ID they expect for the
// current event.
string session_id;
bool valid;
enum InvalidReason {
  INVALID_REASON_UNSPECIFIED
  INVALID_ENCRYPTION
  INVALID_JSON
  EXPIRED
}
// Errors in parsing or validating the ClientSignature. We share errors
// related to the signature through backend calls rather than client errors
// such as 400s because we want to limit the amount of information we share
// related to client signatures with possible attackers.
InvalidReason invalid_reason;

// Enum that represents a feature potentially useful for risk analysis.
enum Feature {
  // Default unspecified type.
  FEATURE_UNSPECIFIED;
  // The IP provided in the signature does not match the IP address reCAPTCHA
  // observed for this event. This can happen for legitimate reasons, so
  // cautious should be exercised before taking action.
  IP_MISMATCH;
  // At least one feature of the environment does not match reCAPTCHA's point
  // of view (URL, UserAgent, Callback). All of these environment features
  // must be provided in the encrypted ClientSignature payload for this
  // feature to be enabled.
  UNEXPECTED_ENVIRONMENT;
}
// Features related to the signature which may be useful for risk analysis.
repeated Feature features;

テスト用に機能をトリガーする

いくつかの機能のトリガーを試して、統合をテストできます。

IP_MISMATCH

IP_MISMATCH 機能をトリガーするには、ip フィールドにランダムな IP アドレスを指定します。テスト環境に応じて、IPv4 または IPv6 のアドレスを使用することが重要です。

UNEXPECTED_ENVIRONMENT

ua_hashurl_hash、および callback_hash のすべてを指定する必要があります。この機能をトリガーするには、callback_hashffffff に変更してみてください。