ハイパーローカル ターゲティング シグナルの復号

パブリッシャーが郵便番号よりも具体的な地域にモバイルの位置情報を渡すと、認定バイヤーは新しい暗号化フィールド「BidRequest.encrypted_hyperlocal_set」で購入者にジオフェンスを送信します。

スケジュール

  1. ユーザーが広告対応のモバイルアプリをインストールし、アプリがデバイスの位置情報にアクセスしてサードパーティと共有することに同意します。このアプリは Google Ads SDK とも統合されていて、このデバイスの位置情報が Google に送信されます。
  2. Google サーバーは、ユーザーのプライバシーを保護するためなど、デバイスの位置情報を囲むジオフェンスを表す特別なハイパーローカル ターゲティング シグナルを生成します。
  3. Google のサーバーは、各購入者に固有のセキュリティ キーを使用して、ハイパーローカル ターゲティング シグナルをシリアル化および暗号化します。入札者は同じ鍵を使用して WINNING_PRICE マクロを復号します。
  4. ビッダーはハイパーローカル ターゲティング シグナルを復号してシリアル化解除し、プロトコル バッファにします。ビッダーはシグナルを分析して、それに応じて入札できます。

依存関係

SHA-1 HMAC をサポートする暗号ライブラリ(Openssl など)が必要になります。

定義

ハイパーローカル ターゲティング シグナルは、proto では次のように定義されます。

// A hyperlocal targeting location when available.
//
message Hyperlocal {
  // A location on the Earth's surface.
  //
  message Point {
    optional float latitude = 1;
    optional float longitude = 2;
  }

  // The mobile device can be at any point inside the geofence polygon defined
  // by a list of corners.  Currently, the polygon is always a parallelogram
  // with 4 corners.
  repeated Point corners = 1;
}

message HyperlocalSet {
  // This field currently contains at most one hyperlocal polygon.
  repeated Hyperlocal hyperlocal = 1;

  // The approximate geometric center of the geofence area.  It is calculated
  // exclusively based on the geometric shape of the geofence area and in no
  // way indicates the mobile device's actual location within the geofence
  // area. If multiple hyperlocal polygons are specified above then
  // center_point is the geometric center of all hyperlocal polygons.
  optional Hyperlocal.Point center_point = 2;
}

// Hyperlocal targeting signal when available, encrypted as described at
// https://developers.google.com/authorized-buyers/rtb/response-guide/decrypt-hyperlocal
optional bytes encrypted_hyperlocal_set = 40;

各ハイパーローカル ターゲティング シグナルには、1 つ以上のポリゴンと中心点が含まれています。各ポリゴンについて、ハイパーローカル ターゲティング シグナルには次の情報が含まれます。

  • ポリゴンの各隅の緯度と経度を順番に corners フィールドとして渡します。
  • オプションの center_point フィールドで渡される、ジオフェンス領域のおおよそのジオメトリの中心。

ターゲティング シグナルの構造

BidRequest.encrypted_hyperlocal_set に含まれる暗号化されたハイパーローカル ターゲティング シグナルには、次の 3 つのセクションがあります。

  • initialization_vector: 16 バイト。
  • ciphertext: 一連の 20 バイトのセクション。
  • integrity_signature: 4 バイト。
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

ciphertext バイト配列は複数の 20 バイト セクションに分割されます。ただし、最後のセクションには 1 ~ 20 バイトが含まれる場合があります。元の byte_array の各セクションに対して、対応する 20 バイトの ciphertext が次のように生成されます。

<byte_array <xor> HMAC(encryption_key, initialization_vector || counter_bytes)>

ここで、|| は連結です。

定義

変数 詳細
initialization_vector 16 バイト - インプレッションに固有の。
encryption_key 32 バイト - アカウント設定時に提供。
integrity_key 32 バイト - アカウント設定時に提供。
byte_array シリアル化された HyperlocalSet オブジェクト(20 バイト セクション)。
counter_bytes セクションの序数を示すバイト値(下記をご覧ください)。
final_message BidRequest.encrypted_hyperlocal_set フィールドを介して送信されたバイト配列。
演算子 詳細
hmac(key, data) SHA-1 HMAC、key を使用して data を暗号化する。
a || b 文字列 a と文字列 b を連結した文字列。

カウンタ バイト数を計算

counter_bytes は、ciphertext の各 20 バイト セクションの順序をマークします。最後のセクションには、1 ~ 20 バイトが含まれる場合があります。hmac() 関数の実行時に counter_bytes に正しい値を入力するには、20 バイトのセクション(残りの部分を含む)をカウントし、次の参照テーブルを使用します。

セクション番号 counter_bytes
撮影していない なし
1 ~ 256 1 バイトです。値は 0 から 255 まで増えます。
257 ... 512 2 バイトです。最初のバイトの値は 0 で、2 番目のバイトの値は 0 から 255 まで順次増分されます。
513 ... 768 3 バイト最初の 2 バイトの値は 0 で、最後のバイトの値は 0 から 255 まで順次増分されます。

BidRequest.encrypted_hyperlocal_set の長さが 1 KB を超えることはないと仮定し、さらに拡張を加えた場合も考慮します。ただし、counter_bytes は、任意の長さのハイパーローカル ターゲティング シグナルをサポートするために必要な長さにすることができます。

暗号化スキーム

ハイパーローカル ターゲティング シグナルの暗号化スキームは、価格確認の復号に使用されるスキームと同じです。

  1. シリアル化: proto で定義されている HyperlocalSet オブジェクトのインスタンスである、ハイパーローカル ターゲティング シグナルは、まず SerializeAsString() によってバイト配列にシリアル化されます。

  2. 暗号化: バイト配列は、適切なセキュリティを確保しながらサイズのオーバーヘッドを最小限に抑えるように設計されたカスタム暗号化スキームを使用して、暗号化されます。暗号化スキームでは、キー付きの HMAC アルゴリズムを使用して、インプレッション イベントに固有の initialization_vector に基づいてシークレット パッドを生成します。

暗号化擬似コード

byte_array = SerializeAsString(HyperlocalSet object)
pad = hmac(encryption_key, initialization_vector || counter_bytes )  // for each 20-byte section of byte_array
ciphertext = pad <xor> byte_array // for each 20-byte section of byte_array
integrity_signature = hmac(integrity_key, byte_array || initialization_vector)  // first 4 bytes
final_message = initialization_vector || ciphertext || integrity_signature

復号スキーム

復号コードは、1)暗号鍵を使用してハイパーローカル ターゲティング シグナルを復号し、2)完全性キーで完全性ビットを検証する必要があります。キーは、アカウントの設定中に提供されます。実装の構造に制限はありません。ほとんどの場合、サンプルコードを取得し、必要に応じて適応させることができます。

  1. パッドを生成する: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: この結果と、暗号テキストを含む <xor> を取得して、暗号化を逆行します。
  3. Verify: 完全性署名が 4 バイトの HMAC(integrity_key, byte_array || initialization_vector) を渡します。

復号擬似コード

(initialization_vector, ciphertext, integrity_signature) = final_message // split up according to length rules
pad = hmac(encryption_key, initialization_vector || counter_bytes)  // for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad // for each 20-byte section of ciphertext
confirmation_signature = hmac(integrity_key, byte_array || initialization_vector)
success = (confirmation_signature == integrity_signature)

C++ のサンプルコード

ここには、完全な復号サンプルコードの主要な関数が含まれています。

bool DecryptByteArray(
    const string& ciphertext, const string& encryption_key,
    const string& integrity_key, string* cleartext) {
  // Step 1. find the length of initialization vector and clear text.
  const int cleartext_length =
      ciphertext.size() - kInitializationVectorSize - kSignatureSize;
  if (cleartext_length < 0) {
    // The length cannot be correct.
    return false;
  }

  string iv(ciphertext, 0, kInitializationVectorSize);

  // Step 2. recover clear text
  cleartext->resize(cleartext_length, '\0');
  const char* ciphertext_begin = string_as_array(ciphertext) + iv.size();
  const char* const ciphertext_end = ciphertext_begin + cleartext->size();
  string::iterator cleartext_begin = cleartext->begin();

  bool add_iv_counter_byte = true;
  while (ciphertext_begin < ciphertext_end) {
    uint32 pad_size = kHashOutputSize;
    uchar encryption_pad[kHashOutputSize];

    if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
              encryption_key.length(), (uchar*)string_as_array(iv),
              iv.size(), encryption_pad, &pad_size)) {
      printf("Error: encryption HMAC failed.\n");
      return false;
    }

    for (int i = 0;
         i < kBlockSize && ciphertext_begin < ciphertext_end;
         ++i, ++cleartext_begin, ++ciphertext_begin) {
      *cleartext_begin = *ciphertext_begin ^ encryption_pad[i];
    }

    if (!add_iv_counter_byte) {
      char& last_byte = *iv.rbegin();
      ++last_byte;
      if (last_byte == '\0') {
        add_iv_counter_byte = true;
      }
    }

    if (add_iv_counter_byte) {
      add_iv_counter_byte = false;
      iv.push_back('\0');
    }
  }
}

ハイパーローカル シグナルとキーの例

コードをテストして検証する手順は次のとおりです。

  1. 308 桁の 16 進数を含む文字列を 154 バイトの配列に変換します。たとえば、次の文字列があるとします。
    E2014EA201246E6F6E636520736F7572636501414243C0ADF6B9B6AC17DA218FB50331EDB376701309CAAA01246E6F6E636520736F7572636501414243C09ED4ECF2DB7143A9341FDEFD125D96844E25C3C202466E6F6E636520736F7572636502414243517C16BAFADCFAB841DE3A8C617B2F20A1FB7F9EA3A3600256D68151C093C793B0116DB3D0B8BE9709304134EC9235A026844F276797
    
    次のように 154 バイトの配列に変換します。
    const char serialized_result[154] = { 0xE2, 0x01, 0x4E, ... };
    
  2. BidRequest.ParsePartialFromString() メソッドを呼び出して、154 バイト配列を BidRequest プロトコル バッファにシリアル化解除します。
    BidRequest bid_req;
    bid_req.ParsePartialFromString(serialzed_result);
    
  3. BidRequest のフィールドが 3 つのみであることを確認します。
    • encrypted_hyperlocal_set
      BidReqeust メッセージで宣言されている。
    • encrypted_advertising_id
      BidReqeust.Mobile メッセージで宣言されている。
    • encrypted_hashed_idfa
      BidReqeust.Mobile メッセージで宣言されている。

    例:

    encrypted_hyperlocal_set:(
        {  100,  100 },
        {  200, -300 },
        { -400,  500 },
        { -600, -700 },)
    encrypted_advertising_id: { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }
    encrypted_hashed_idfa : { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0xF1 }
    
  4. 次の encryption_keyintegrity_key を使用して 3 つのフィールドを復号し、それらが正しく復号されていることを確認します。
    encryption_key = {0x02, 0xEE, 0xa8, 0x3c, 0x6c, 0x12, 0x11, 0xe1, 0x0b,
        0x9f, 0x88, 0x96, 0x6c, 0xee, 0xc3, 0x49, 0x08, 0xeb, 0x94, 0x6f, 0x7e,
        0xd6, 0xe4, 0x41, 0xaf, 0x42, 0xb3, 0xc0, 0xf3, 0x21, 0x81, 0x40};
    
    integrity_key = {0xbf, 0xFF, 0xec, 0x55, 0xc3, 0x01, 0x30, 0xc1, 0xd8,
        0xcd, 0x18, 0x62, 0xed, 0x2a, 0x4c, 0xd2, 0xc7, 0x6a, 0xc3, 0x3b, 0xc0,
        0xc4, 0xce, 0x8a, 0x3d, 0x3b, 0xbd, 0x3a, 0xd5, 0x68, 0x77, 0x92};
    

最新でないレスポンス攻撃を検出する

古いレスポンス攻撃を検出するために、タイムゾーンの違いを考慮したうえで、システム時刻とは大きく異なるタイムスタンプのレスポンスをフィルタすることをおすすめします。Google のサーバーは PST/PDT 時間に設定されています。

実装について詳しくは、料金確認の復号の記事で「古くなったレスポンス攻撃の検出」をご覧ください。

Java ライブラリ

ハイパーローカル ターゲティング シグナルのエンコードとデコードを行う暗号アルゴリズムを実装する代わりに、DoubleClickCrypto.java を使用できます。詳細については、暗号をご覧ください。