如果您的广告素材包含 ${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,而无需实现加密算法来编码和解码胜出价格。如需了解详情,请参阅加密。