解密价格确认

如果您的广告素材包含 ${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}

场景

  1. 您的 OpenRTB 出价应用在返回给 Google 的 HTML 代码段或 VAST 网址中包含 ${AUCTION_PRICE} 宏。
  2. Google 会使用未填充的网页安全 Base64 编码 (RFC 3548) 将胜出价格替换为宏。
  3. 该代码段会以您选择的格式传递确认信息。例如,您可以在广告中呈现的不可见像素请求的网址中传递确认信息。
  4. 在服务器上,您的应用会使用可在网址中安全使用的 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_keye_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,而无需实现加密算法来编码和解码胜出价格。如需了解详情,请参阅加密