將超在地指定目標信號解密

如果發布商將行動位置資料傳送給比郵遞區號更明確的 Authorized Buyers,Authorized Buyers 會將新的加密欄位傳送給買方的 ##9;hyperlocal'地理圍欄。BidRequest.encrypted_hyperlocal_set

時程

  1. 使用者安裝含廣告功能的行動應用程式,並同意應用程式存取裝置及與第三方分享裝置位置資訊。這個應用程式也整合了 Google Ads SDK,並將這部裝置位置傳送給 Google。
  2. Google 伺服器會產生特殊的超在地指定目標信號,代表裝置位置資訊周圍的地理圍欄,例如為了保護使用者的隱私權。
  3. Google 伺服器會使用每個買方專屬的安全金鑰,序列化及加密超本機指定目標信號。請注意,您的出價工具會使用相同的鍵解密 WINNING_PRICE 巨集
  4. 您的出價方會將超在地指定目標信號解密,並序列化為通訊協定緩衝區。這樣出價方就能分析信號,並據此出價。

依附元件

您必須使用支援 SHA-1 HMAC 的加密編譯程式庫,例如 Openssl

定義

通訊協定中預設的超在地指定目標訊號如下:

// 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;

每個超在地指定目標信號都包含一或多個多邊形和中心點。每個多邊形的超在地指定目標信號都包含:

  • 多邊形中每個角落的緯度與經度,以重複的 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 串連。

計算 count_bytes

counter_bytes 會標示 ciphertext 中每個 20 位元組區段的順序。請注意,上一節可能包含 1 到 20 位元組 (含)。如要在執行 hmac() 函式時以正確的值填入 counter_bytes,請計算 20 位元組的部分 (包括其餘部分),並使用以下參考表格:

區段編號 counter_bytes
0 天
1 ... 256 1 個位元組。這個值會從 0 依序遞增 255。
257 ... 512 2 個位元組。第一個位元組的值是 0,第二個位元組的值則是依序從 0 到 255。
513 ... 768 3 個位元組。前兩個位元組的值是 0,最後一個位元組的值會依序從 0 到 255。

我們不預期 BidRequest.encrypted_hyperlocal_set 的長度不會超過 1 KB,即使在考量方面仍有進一步的成長也是如此。然而,counter_bytes 可以視需要支援任意長度的超在地指定目標信號。

加密配置

超在地指定目標訊號的加密配置與解密價格確認所使用的配置相同。

  1. 序列化:超在地指定目標信號,是 ProtoSet 中定義的超本機物件物件,會先透過 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. 驗證:完整性簽名已通過 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 個十六進位字元的字串轉換為 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};
    

偵測過時回應攻擊

如要偵測過時的回應攻擊,建議您在考量時區差異後,篩選出時間戳記與系統時間截然不同的回應。我們的伺服器已設為太平洋標準時間/PDT 時間。

如需實作詳細資料,請參閱「解密價格確認」一文中的「偵測過時的回應攻擊」一節。

Java 程式庫

您不必實作加密演算法,以將超在地指定目標信號進行編碼及解碼,您可以使用 DoubleClickCrypto.java。詳情請參閱密碼編譯