如果廣告聯播網使用
JavaScript 代碼透過 Authorized Buyers 供應廣告,就可接收 Android 和 iOS 裝置的廣告客戶 ID。這些資訊是透過 Authorized Buyers 管理的 JavaScript 代碼中的 %%EXTRA_TAG_DATA%%
或 %%ADVERTISING_IDENTIFIER%%
巨集傳送。本節的其餘部分著重於擷取 %%EXTRA_TAG_DATA%%
,但請參閱「
使用廣告識別碼或廣告 ID 進行再行銷」一文,進一步瞭解 %%ADVERTISING_IDENTIFIER%%
加密 proto 緩衝區 MobileAdvertisingId
能以類似方式解密。
時間表
- 廣告聯播網會透過 Authorized Buyers UI 更新 JavaScript 應用程式內代碼,並加入
%%EXTRA_TAG_DATA%%
巨集,如下所示。 - 放送廣告時,應用程式透過 Google Mobile Ads SDK 向 Authorized Buyers 請求廣告,同時以安全的方式傳遞廣告客戶 ID。
- 應用程式會傳回 JavaScript 代碼,
%%EXTRA_TAG_DATA%%
巨集會填入包含該 ID 的加密廣告聯播網通訊協定緩衝區。 - 應用程式會執行這個代碼,呼叫廣告聯播網以請求勝出的廣告。
- 廣告聯播網必須處理通訊協定緩衝區,才能使用這些資訊 (營利):
- 使用 WebSafeBase64 將 websafe 字串解碼為位元組字串。
- 請按照以下配置進行解密。
- 將 Pro 反序列化,並從 ExtraTagData.advertising_id 或 ExtraTagData.hashed_idfa 取得廣告客戶 ID。
依附元件
- WebSafeBase64 編碼器。
- 支援 SHA-1 HMAC 的加密編譯程式庫,例如 Openssl。
- 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。 |
加密配置
加密配置是以解密超本機指定信號所用的配置為基礎。
序列化:在通訊協定緩衝區中定義的 ExtraTagData 物件例項會先透過
SerializeAsString()
序列化為位元組陣列。加密:接著,系統會利用自訂加密配置來加密位元組陣列,藉此將大小負擔降到最低,同時確保安全無虞。加密配置會使用金鑰化的 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) 使用完整性金鑰驗證完整性位元。系統會在帳戶設定期間提供金鑰。實作方式沒有任何限制。在大部分情況下,您應該能夠擷取程式碼範例,並依照需求加以調整。
- 產生 pad:
HMAC(encryption_key, initialization_vector || counter_bytes)
- XOR:使用密文執行這個結果和
<xor>
,以反向加密。 - 驗證:完整性簽章會傳遞
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 進行編碼及解碼。詳情請參閱加密編譯。