解密價格確認
透過集合功能整理內容
你可以依據偏好儲存及分類內容。
如果廣告素材包含 ${AUCTION_PRICE}
巨集,Google 會在廣告素材得標時通知您得標價格。
展開巨集時,巨集會以加密形式傳回勝出價格。例如,您可以在廣告素材中加入這項資訊,並在廣告中顯示不可見的像素要求:
<div>
<script language='JavaScript1.1' src='https://example.com?creativeID=5837243'/>
<img src='https://example.com/t.gif?price=${AUCTION_PRICE}' width='1' height='1'/>
</div>
${AUCTION_PRICE}
巨集也可以納入影片廣告素材的 VAST 網址,但不能納入 VAST 中的曝光網址:
https://example.com/vast/v?price=${AUCTION_PRICE}
情境
- 您的 OpenRTB 出價應用程式在傳回 Google 的 HTML 程式碼片段或 VAST 網址中,包含
${AUCTION_PRICE}
巨集。
- Google 會以未填充的網路安全 Base64 編碼 (RFC 3548) 取代巨集的得標價格。
- 程式碼片段會以您選擇的格式傳遞確認訊息。舉例來說,確認訊息可能會傳遞至廣告中顯示的隱藏像素要求網址。
- 在伺服器上,應用程式會使用網頁安全的 base64 解碼功能,解碼得標價格資訊並解密結果。
依附元件
您需要支援 SHA-1 HMAC 的加密編譯程式庫,例如 Openssl。
程式碼範例
範例程式碼以 Java 和 C++ 提供,可從 privatedatacommunicationprotocol 專案下載。
Java 程式碼範例會使用 Apache 共用程式專案中的 base64 解碼器。您不需要下載 Apache commons 程式碼,因為參考實作項目包含必要的部分,因此是自給自足的。
C++ 範例程式碼會使用 OpenSSL base64 BIO 方法。這個函式會採用具網路安全性的 Base64 編碼字串 (RFC 3548) 並進行解碼。一般來說,網頁安全的 base64 字串會將「="」填充字元替換成「.」(請注意,引號是為了閱讀清楚而加入,並未包含在通訊協定中),但巨集替換作業不會填入加密價格。參考實作會加入填充字元,因為 OpenSSL 無法處理未填充的字串。
編碼
得標價格加密和解密作業需要兩個共用密鑰。完整性金鑰和加密金鑰,分別稱為 i_key
和 e_key
。這兩組金鑰會在帳戶設定時以網頁安全的 base64 字串形式提供,並可在 Authorized Buyers 頁面的「出價工具設定 > RTB 設定 > 加密金鑰」下方找到。
完整性和加密金鑰範例:
skU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o= // Encryption key (e_key)
arO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo= // Integrity key (i_key)
應用程式應先對金鑰進行網路安全解碼,再進行 Base64 解碼:
e_key = WebSafeBase64Decode('skU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o=')
i_key = WebSafeBase64Decode('arO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo=')
加密配置
價格會使用自訂加密方案加密,這個方案旨在盡可能減少大小開銷,同時確保足夠的安全性。加密方案會使用金鑰 HMAC 演算法,根據不重複的曝光事件 ID 產生密鑰填充資料。
加密價格的長度固定為 28 位元組。這項資料包含 16 位元組的初始化向量、8 位元組的密文,以及 4 位元組的完整性簽章。根據 RFC 3548,加密的價格採用網路安全 Base64 編碼,並省略填充字元。因此,無論支付的得標價為何,28 個位元組的加密價格都會編碼為 38 個字元的網路安全 Base64 字串。
加密價格範例:
YWJjMTIzZGVmNDU2Z2hpN7fhCuPemCce_6msaw // 100 CPI micros
YWJjMTIzZGVmNDU2Z2hpN7fhCuPemCAWJRxOgA // 1900 CPI micros
YWJjMTIzZGVmNDU2Z2hpN7fhCuPemC32prpWWw // 2700 CPI micros
加密格式如下:
{initialization_vector (16 bytes)}{encrypted_price (8 bytes)}
{integrity (4 bytes)}
價格會以 <price xor HMAC(encryption_key,
initialization_vector)>
加密,因此解密程序會計算 HMAC(encryption_key,initialization_vector)
,並與經過加密的價格進行 XOR 運算,以便解除加密。完整性階段會使用 <HMAC(integrity_key, price||initialization_vector)>
的 4 個位元組,其中 ||
是連結。
輸入 |
iv |
初始化向量 (16 個位元組 - 曝光次數) |
e_key |
加密金鑰 (32 個位元組 - 在帳戶設定時提供) |
i_key |
完整性金鑰 (32 個位元組 - 在帳戶設定時提供) |
price |
(8 個位元組 - 以帳戶貨幣的百萬分之一為單位) |
Notation |
hmac(k, d) |
使用金鑰 k 計算資料 d 的 SHA-1 HMAC |
a || b |
字串 a 與字串 b 串連 |
虛擬程式碼 |
pad = hmac(e_key, iv) // first 8 bytes
enc_price = pad <xor> price
signature = hmac(i_key, price || iv) // first 4 bytes
final_message = WebSafeBase64Encode( iv || enc_price || signature ) |
解密配置
解密程式碼必須使用加密金鑰解密價格,並使用完整性金鑰驗證完整性位元。系統會在設定期間提供這些金鑰。實作結構的詳細資料沒有任何限制。在大多數情況下,您應該可以使用程式碼範例,並根據需求加以調整。
輸入 |
e_key |
加密金鑰 (32 個位元組) - 在帳戶設定時提供 |
i_key |
完整性金鑰 (32 個位元組) - 在帳戶設定時提供 |
final_message |
38 個字元的網路安全 Base64 編碼 |
虛擬程式碼 |
// Base64 padding characters are omitted.
// Add any required base64 padding (= or ==).
final_message_valid_base64 = AddBase64Padding(final_message)
// Web-safe decode, then base64 decode.
enc_price = WebSafeBase64Decode(final_message_valid_base64)
// Message is decoded but remains encrypted.
(iv, p, sig) = enc_price // Split up according to fixed lengths.
price_pad = hmac(e_key, iv)
price = p <xor> price_pad
conf_sig = hmac(i_key, price || iv)
success = (conf_sig == sig) |
偵測過時回應攻擊
如要偵測過時回應或重播攻擊,建議您在考量時區差異後,篩除時間戳記與系統時間有顯著差異的回應。
初始化向量會在前 8 個位元組中包含時間戳記。以下 C++ 函式可讀取此檔案:
void GetTime(const char* iv, struct timeval* tv) {
uint32 val;
memcpy(&val, iv, sizeof(val));
tv->tv_sec = htonl(val);
memcpy(&val, iv+sizeof(val), sizeof(val));
tv->tv_usec = htonl(val)
}
您可以使用下列 C++ 程式碼,將時間戳記轉換為人類可讀的格式:
struct tm tm;
localtime_r(&tv->tv_sec, &tm);
printf("%04d-%02d-%02d|%02d:%02d:%02d.%06ld",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
tv_.tv_usec);
Java 程式庫
您可以使用
DoubleClickCrypto.java,而非實作加密演算法來編碼及解碼得標價格。詳情請參閱「密碼學」。
除非另有註明,否則本頁面中的內容是採用創用 CC 姓名標示 4.0 授權,程式碼範例則為阿帕契 2.0 授權。詳情請參閱《Google Developers 網站政策》。Java 是 Oracle 和/或其關聯企業的註冊商標。
上次更新時間:2025-08-21 (世界標準時間)。
[null,null,["上次更新時間:2025-08-21 (世界標準時間)。"],[[["\u003cp\u003eGoogle can reveal the winning price of an auction if the creative includes either the \u003ccode\u003e${AUCTION_PRICE}\u003c/code\u003e macro for OpenRTB or \u003ccode\u003e%%WINNING_PRICE%%\u003c/code\u003e for the Google RTB protocol.\u003c/p\u003e\n"],["\u003cp\u003eThe winning price, when expanded from the macro, is returned in an encrypted format and can be embedded in elements such as an invisible pixel request or a VAST URL, but not the impression URL.\u003c/p\u003e\n"],["\u003cp\u003eDecrypting the winning price requires both an integrity key (\u003ccode\u003ei_key\u003c/code\u003e) and an encryption key (\u003ccode\u003ee_key\u003c/code\u003e), provided during account setup, and a crypto library that supports SHA-1 HMAC.\u003c/p\u003e\n"],["\u003cp\u003eThe encrypted price consists of a 16-byte initialization vector, 8 bytes of ciphertext, and a 4-byte integrity signature, totaling 28 bytes, which are then encoded into a 38-character web-safe base64 string.\u003c/p\u003e\n"],["\u003cp\u003eTo guard against stale response attacks, it's recommended to filter responses based on the timestamp found in the initialization vector, comparing it against the current system time.\u003c/p\u003e\n"]]],["When a creative wins an auction, Google reveals the winning price via macros. OpenRTB uses `${AUCTION_PRICE}`, while the deprecated Google RTB uses `%%WINNING_PRICE%%`. These macros return an encrypted winning price that can be inserted into a creative's pixel request or a video's VAST URL. The encrypted price, a 38-character web-safe base64 string, is decrypted using provided encryption and integrity keys, alongside SHA-1 HMAC. The encryption includes an initialization vector, ciphertext, and integrity signature. Sample code in Java and C++ are available.\n"],null,["# Decrypt Price Confirmations\n\nWhen your creative wins an auction, Google can inform you what the winning\nprice was if the creative includes the `${AUCTION_PRICE}` macro.\n\nWhen the macro is expanded, it returns the winning price in an encrypted\nform. It can be included in a creative, for example, with an invisible pixel\nrequest rendered as part of the ad: \n\n```genshi\n\u003cdiv\u003e\n \u003cscript language='JavaScript1.1' src='https://example.com?creativeID=5837243'/\u003e\n \u003cimg src='https://example.com/t.gif?price=${AUCTION_PRICE}' width='1' height='1'/\u003e\n\u003c/div\u003e\n```\n\nThe `${AUCTION_PRICE}` macro can also be included in the VAST URL of\na video creative, but not in the impression URL in the VAST: \n\n```genshi\nhttps://example.com/vast/v?price=${AUCTION_PRICE}\n```\n\nScenario\n--------\n\n1. Your OpenRTB bidding application includes the `${AUCTION_PRICE}` macro in the HTML snippet or VAST URL it returns to Google.\n2. Google substitutes the winning price for the macro in unpadded web-safe base64 encoding ([RFC 3548](//tools.ietf.org/html/rfc3548)).\n3. The snippet passes the confirmation in the format you have chosen. For example, the confirmation might be passed in the URL of an invisible pixel request rendered as part of the ad.\n4. On the server, your application web-safe base64 decodes the winning price information and decrypts the result.\n\nDependencies\n------------\n\nYou will need a crypto library that supports SHA-1 HMAC, such as\nOpenssl.\n\nSample code\n-----------\n\nSample code is provided in Java and C++ and can be downloaded from the [privatedatacommunicationprotocol\nproject](//code.google.com/p/privatedatacommunicationprotocol).\n\n- The Java sample code uses the base64 decoder from the [Apache\n commons project](//commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html). You will not need to download the Apache commons code,\n as the reference implementation includes the necessary part and is therefore\n self-contained.\n\n- The C++ sample code uses the [OpenSSL\n base64 BIO method](//www.openssl.org/docs/man1.1.0/crypto/BIO_f_base64.html). It takes a web-safe base64 encoded string ([RFC 3548](//tools.ietf.org/html/rfc3548)) and decodes it.\n Normally, web-safe base64 strings replace \"=\" padding with \".\" (note that\n quotation marks are added for reading clarity and are not included in the\n protocol) but the macro substitution does not pad the encrypted price. The\n reference implementation adds padding because OpenSSL has trouble with\n unpadded strings.\n\nEncoding\n--------\n\nWinning price encryption and decryption requires two secret, but shared,\nkeys. An integrity key, and encryption key, referred to as `i_key`,\nand `e_key` respectively. Both keys are provided at account setup as\nweb-safe base64 strings, and can be found on the Authorized Buyers page\nunder [**Bidder\nsettings \\\u003e RTB settings \\\u003e Encryption keys**](//support.google.com/authorizedbuyers/answer/10858928).\n\nExample integrity and encryption keys: \n\n```scdoc\nskU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o= // Encryption key (e_key)\narO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo= // Integrity key (i_key)\n```\n\nKeys should be web-safe decoded and then base64 decoded by your\napplication: \n\n```scdoc\ne_key = WebSafeBase64Decode('skU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o=')\ni_key = WebSafeBase64Decode('arO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo=')\n```\n\nEncryption scheme\n-----------------\n\nThe price is encrypted using a custom encryption scheme that is designed to\nminimize size overhead while ensuring adequate security. The encryption scheme\nuses a keyed HMAC algorithm to generate a secret pad based on the unique\nimpression event ID.\n\nThe encrypted price has a fixed length of 28 bytes. It is comprised of a\n16-byte initialization vector, 8 bytes of ciphertext, and a 4-byte integrity\nsignature. The encrypted price is web-safe base64-encoded, according to RFC\n3548, with padding characters omitted. Thus, the 28-byte encrypted price is\nencoded as a 38 character web-safe base-64 string irrespective of the winning\nprice paid.\n\nExample encrypted prices: \n\n```scdoc\nYWJjMTIzZGVmNDU2Z2hpN7fhCuPemCce_6msaw // 100 CPI micros\nYWJjMTIzZGVmNDU2Z2hpN7fhCuPemCAWJRxOgA // 1900 CPI micros\nYWJjMTIzZGVmNDU2Z2hpN7fhCuPemC32prpWWw // 2700 CPI micros\n```\n\nThe encrypted format is: \n\n```scdoc\n{initialization_vector (16 bytes)}{encrypted_price (8 bytes)}\n{integrity (4 bytes)}\n```\n\nThe price is encrypted as `\u003cprice xor HMAC(encryption_key,\ninitialization_vector)\u003e` so decryption calculates\n`HMAC(encryption_key,initialization_vector)` and xor's with the\nencrypted price to reverse the encryption. The integrity stage takes 4 bytes of\n`\u003cHMAC(integrity_key, price||initialization_vector)\u003e` where\n`||` is concatenation.\n\n| Inputs ||\n|--------------|-------------------------------------------------------------|\n| `iv` | initialization vector (16 bytes - unique to the impression) |\n| `e_key` | encryption key (32 bytes - provided at account set up) |\n| `i_key` | integrity key (32 bytes - provided at account set up) |\n| `price` | (8 bytes - in micros of account currency) |\n| `hmac(k, d)` | SHA-1 HMAC of data `d`, using key `k` |\n| `a || b` | string `a` concatenated with string `b` |\n| ```scdoc pad = hmac(e_key, iv) // first 8 bytes enc_price = pad \u003cxor\u003e price signature = hmac(i_key, price || iv) // first 4 bytes final_message = WebSafeBase64Encode( iv || enc_price || signature ) ``` ||\n\nDecryption scheme\n-----------------\n\nYour decryption code must decrypt the price using the encryption key, and\nverify the integrity bits with the integrity key. The keys will be provided to\nyou during setup. There aren't any restrictions on the details of how you\nstructure your implementation. For the most part, you should be able to take\nthe sample code and adapt it according to your needs.\n\n| Inputs ||\n|-----------------|-------------------------------------------------------|\n| `e_key` | encryption key, 32 bytes - provided at account set up |\n| `i_key` | integrity key, 32 bytes - provided at account set up |\n| `final_message` | 38 characters web-safe base64 encoded |\n| ```scilab // Base64 padding characters are omitted. // Add any required base64 padding (= or ==). final_message_valid_base64 = AddBase64Padding(final_message) // Web-safe decode, then base64 decode. enc_price = WebSafeBase64Decode(final_message_valid_base64) // Message is decoded but remains encrypted. (iv, p, sig) = enc_price // Split up according to fixed lengths. price_pad = hmac(e_key, iv) price = p \u003cxor\u003e price_pad conf_sig = hmac(i_key, price || iv) success = (conf_sig == sig) ``` ||\n\nDetect stale response attacks\n-----------------------------\n\nTo detect stale response, or replay, attacks, it's recommended that you\nfilter responses with a timestamp that differs significantly from the system\ntime, after accounting for timezone differences.\n\nThe initialization vector contains a timestamp in the first 8 bytes. It can\nbe read by the following C++ function: \n\n```gdscript\nvoid GetTime(const char* iv, struct timeval* tv) {\n uint32 val;\n memcpy(&val, iv, sizeof(val));\n tv-\u003etv_sec = htonl(val);\n memcpy(&val, iv+sizeof(val), sizeof(val));\n tv-\u003etv_usec = htonl(val)\n}\n```\n\nThe timestamp can be converted to a human readable form using the following\nC++ code: \n\n```css+lasso\nstruct tm tm;\nlocaltime_r(&tv-\u003etv_sec, &tm);\n\nprintf(\"%04d-%02d-%02d|%02d:%02d:%02d.%06ld\",\n tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,\n tm.tm_hour, tm.tm_min, tm.tm_sec,\n tv_.tv_usec);\n```\n\nJava library\n------------\n\nInstead of implementing the crypto algorithms to encode and decode\nthe winning price, you can use\n[DoubleClickCrypto.java](//github.com/google/openrtb-doubleclick/blob/master/doubleclick-core/src/main/java/com/google/doubleclick/crypto/DoubleClickCrypto.java). For more information, see\n[Cryptography](//github.com/google/openrtb-doubleclick/wiki#cryptography)."]]