解密价格确认
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
如果您的广告素材包含 ${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 Commons 项目中的 base64 解码器。您无需下载 Apache commons 代码,因为参考实现包含必要的部分,因此是自包含的。
C++ 示例代码使用 OpenSSL base64 BIO 方法。它接受可在 web 环境中安全使用的 base64 编码字符串 (RFC 3548),并对其进行解码。
通常,适用于网络的 Base64 字符串会将“="”填充替换为“.”(请注意,添加引号是为了方便阅读,协议中不包含引号),但宏替换不会对加密的价格进行填充。由于 OpenSSL 无法处理未填充的字符串,因此参考实现会添加填充。
编码
获胜价格的加密和解密需要两个共享的密钥。完整性密钥和加密密钥,分别称为 i_key
和 e_key
。这两个密钥在账号设置时以 Web 安全的 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 个字符的网络安全 base-64 字符串。
加密价格示例:
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)
,并与加密的价格进行异或运算,以便对加密进行逆向操作。完整性阶段需要 4 个字节的 <HMAC(integrity_key, price||initialization_vector)>
,其中 ||
是串联。
输入 |
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,而无需实现加密算法来编码和解码胜出价格。如需了解详情,请参阅加密。
如未另行说明,那么本页面中的内容已根据知识共享署名 4.0 许可获得了许可,并且代码示例已根据 Apache 2.0 许可获得了许可。有关详情,请参阅 Google 开发者网站政策。Java 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-08-21。
[null,null,["最后更新时间 (UTC):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)."]]