為廣告聯播網解密廣告客戶 ID

如果廣告聯播網使用 JavaScript 代碼透過 Authorized Buyers 供應廣告,就可接收 Android 和 iOS 裝置的廣告客戶 ID。這些資訊是透過 Authorized Buyers 管理的 JavaScript 代碼中的 %%EXTRA_TAG_DATA%%%%ADVERTISING_IDENTIFIER%% 巨集傳送。本節的其餘部分著重於擷取 %%EXTRA_TAG_DATA%%,但請參閱「 使用廣告識別碼或廣告 ID 進行再行銷」一文,進一步瞭解 %%ADVERTISING_IDENTIFIER%% 加密 proto 緩衝區 MobileAdvertisingId 能以類似方式解密。

時間表

  1. 廣告聯播網會透過 Authorized Buyers UI 更新 JavaScript 應用程式內代碼,並加入 %%EXTRA_TAG_DATA%% 巨集,如下所示。
  2. 放送廣告時,應用程式透過 Google Mobile Ads SDK 向 Authorized Buyers 請求廣告,同時以安全的方式傳遞廣告客戶 ID。
  3. 應用程式會傳回 JavaScript 代碼,%%EXTRA_TAG_DATA%% 巨集會填入包含該 ID 的加密廣告聯播網通訊協定緩衝區。
  4. 應用程式會執行這個代碼,呼叫廣告聯播網以請求勝出的廣告。
  5. 廣告聯播網必須處理通訊協定緩衝區,才能使用這些資訊 (營利):
    1. 使用 WebSafeBase64 將 websafe 字串解碼為位元組字串。
    2. 請按照以下配置進行解密。
    3. 將 Pro 反序列化,並從 ExtraTagData.advertising_id 或 ExtraTagData.hashed_idfa 取得廣告客戶 ID。

依附元件

  1. WebSafeBase64 編碼器
  2. 支援 SHA-1 HMAC 的加密編譯程式庫,例如 Openssl
  3. Google 通訊協定緩衝區編譯器

解碼網路安全字串

由於透過 %%EXTRA_TAG_DATA%% 巨集傳送的資訊必須透過網址傳送,因此 Google 伺服器會使用安全的 Base64 (RFC 3548) 對資訊進行編碼。

因此,在嘗試解密之前,您必須先將 ASCII 字元解碼為位元組字串。下列 C++ 程式碼範例是以 OpenSSL 專案 BIO_f_base64() 為基礎,並屬於 Google 的解密程式碼範例的一部分。

string AddPadding(const string& b64_string) {
  if (b64_string.size() % 4 == 3) {
    return b64_string + "=";
  } else if (b64_string.size() % 4 == 2) {
    return b64_string + "==";
  }
  return b64_string;
}

// Adapted from http://www.openssl.org/docs/man1.1.0/crypto/BIO_f_base64.html
// Takes a web safe base64 encoded string (RFC 3548) and decodes it.
// Normally, web safe base64 strings have padding '=' replaced with '.',
// but we will not pad the ciphertext. We add padding here because
// openssl has trouble with unpadded strings.
string B64Decode(const string& encoded) {
  string padded = AddPadding(encoded);
  // convert from web safe -> normal base64.
  int32 index = -1;
  while ((index = padded.find_first_of('-', index + 1)) != string::npos) {
    padded[index] = '+';
  }
  index = -1;
  while ((index = padded.find_first_of('_', index + 1)) != string::npos) {
    padded[index] = '/';
  }

  // base64 decode using openssl library.
  const int32 kOutputBufferSize = 256;
  char output[kOutputBufferSize];

  BIO* b64 = BIO_new(BIO_f_base64());
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  BIO* bio = BIO_new_mem_buf(const_cast(padded.data()),
                             padded.length());
  bio = BIO_push(b64, bio);
  int32 out_length = BIO_read(bio, output, kOutputBufferSize);
  BIO_free_all(bio);
  return string(output, out_length);
}

加密位元組字串的結構

將 ASCII 字元解碼為位元組字串後,即可開始解密。加密的位元組字串包含 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 序列化的 ExtraTagData 物件,位於 20 位元組區段中。
counter_bytes 顯示區段序數的位元組值,如下所示。
final_message 透過 %%EXTRA_TAG_DATA%% 巨集傳送的位元組陣列總量 (減去 WebSafeBase64 編碼)。
運算子 詳細說明
hmac(key, data) SHA-1 HMAC,使用 key 加密 data
a || b 字串 a,與字串 b 串連。

計算計數器_位元組

counter_bytes 會標記 ciphertext 中每個 20 位元組部分的順序。請注意,最後一個部分可能包含 1 到 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。

返回頁首

加密配置

加密配置是以解密超本機指定信號所用的配置為基礎。

  1. 序列化:在通訊協定緩衝區中定義的 ExtraTagData 物件例項會先透過 SerializeAsString() 序列化為位元組陣列。

  2. 加密:接著,系統會利用自訂加密配置來加密位元組陣列,藉此將大小負擔降到最低,同時確保安全無虞。加密配置會使用金鑰化的 HMAC 演算法,根據 initialization_vector 產生一個專屬於曝光事件的密鑰鍵盤。

加密虛擬程式碼

byte_array = SerializeAsString(ExtraTagData 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. 產生 padHMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR:使用密文執行這個結果和 <xor>,以反向加密。
  3. 驗證:完整性簽章會傳遞 HMAC(integrity_key, byte_array || initialization_vector) 的 4 個位元組

解密虛擬程式碼

// split up according to length rules
(initialization_vector, ciphertext, integrity_signature) = final_message

// for each 20-byte section of ciphertext
pad = hmac(encryption_key, initialization_vector || counter_bytes)

// for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad

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');
    }
  }

從廣告聯播網通訊協定緩衝區取得資料

%%EXTRA_TAG_DATA%% 中傳遞的資料解碼並解密後,就可以將通訊協定緩衝區去序列化,並取得廣告客戶 ID 做為指定目標。

如果您不熟悉通訊協定緩衝區,請參閱我們的說明文件

定義

廣告聯播網通訊協定緩衝區的定義如下:

message ExtraTagData {
  // advertising_id can be Apple's identifier for advertising (IDFA)
  // or Android's advertising identifier. When the advertising_id is an IDFA,
  // it is the plaintext returned by iOS's [ASIdentifierManager
  // advertisingIdentifier]. For hashed_idfa, the plaintext is the MD5 hash of
  // the IDFA.  Only one of the two fields will be available, depending on the
  // version of the SDK making the request.  Later SDKs provide unhashed values.
  optional bytes advertising_id = 1;
  optional bytes hashed_idfa = 2;
}

您需要按照 C++ 通訊協定緩衝區說明文件中所述,使用 ParseFromString() 將它還原序列化。

如要進一步瞭解 Android advertising_id 和 iOS hashed_idfa 欄位,請參閱「解密廣告 ID」以及「使用廣告識別碼指定行動應用程式廣告空間」。

Java 程式庫

您可以使用 DoubleClickCrypto.java,不必導入加密演算法,對廣告聯播網的廣告客戶 ID 進行編碼及解碼。詳情請參閱加密編譯