解密价格确认

当您的广告素材赢得竞价后,如果定义该广告素材的 HTML 代码段或 VAST 网址包含 WINNING_PRICE 宏,Google 可以告知您胜出的价格。Google 会以加密形式返回胜出价格。以下主题介绍了您的应用如何解密胜出价格信息。

WINNING_PRICE 宏可包含在广告素材中,例如,当隐形像素请求作为广告的一部分呈现时:

<div>
  <script language='JavaScript1.1' src='https://example.com?creativeID=5837243'/>
  <img src='https://example.com/t.gif?price=%%WINNING_PRICE%%' width='1' height='1'/>
</div>

WINNING_PRICE 宏还可以包含在视频广告素材的 VAST 网址中(但不能包含在 VAST 的展示网址中):

https://example.com/vast/v?price=%%WINNING_PRICE%%

场景

  1. 您的应用在返回给 Google 的 HTML 代码段或 VAST 网址中包含 WINNING_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 方法。它采用可在网络上安全使用的 base64 编码字符串 (RFC 3548) 并对其进行解码。 正常情况下,网络安全的 base64 字符串会将“=”填充替换为“.”(请注意,添加引号是为了便于阅读,但引号不包含在协议中),但替换宏时并不会填充加密价格。由于 OpenSSL 无法处理未填充的字符串,因此参考实现会添加内边距。

编码

胜出价格加密和解密需要两个共享的密钥。完整性密钥和加密密钥,分别称为 i_keye_key。这两个密钥在帐号开设时以网络安全 base64 字符串的形式提供,可在 Authorized Buyers 页面的出价工具设置 > 实时出价设置 > 加密密钥下找到。

完整性和加密密钥示例:

skU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o=  // Encryption key (e_key)
arO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo=  // Integrity key (i_key)

密钥应先在 Web 安全中进行解码,然后由您的应用进行 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) 和 xor 的值,从而逆转加密。完整性阶段会使用 4 个字节的 <HMAC(integrity_key, price||initialization_vector)>,其中 || 是串联的。

输入内容
iv 初始化矢量(16 个字节 - 展示所独有)
e_key 加密密钥(32 个字节 - 在开设帐号时提供)
i_key 完整性密钥(32 个字节 - 在创建账号时提供)
price (8 个字节 - 以帐号币种的微单位表示)
Notation
hmac(k, d) 数据 d 的 SHA-1 HMAC,使用密钥 k
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 使用网络安全 base64 编码的 38 个字符
伪代码
// 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,而不是实现加密算法来对胜出价格进行编码和解码。如需了解详情,请参阅加密