Odszyfrowywanie potwierdzeń cen

Gdy Twoja kreacja wygra aukcję, Google może poinformować Cię o wygrywającej cenie, jeśli zawiera ona makro ${AUCTION_PRICE}.

Po rozwinięciu makro zwraca zwycięską cenę w zaszyfrowanej formie. Może ono być uwzględnione w kreacji, np. za pomocą żądania niewidocznego piksela renderowanego jako część reklamy:

<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>

Makro ${AUCTION_PRICE} może też występować w adresie URL VAST kreacji wideo, ale nie w adresie URL wyświetlenia w VAST:

https://example.com/vast/v?price=${AUCTION_PRICE}

Scenariusz

  1. Twoja aplikacja do określania stawek za pomocą OpenRTB zawiera makro ${AUCTION_PRICE}w fragmentie kodu HTML lub adresie URL VAST zwracanym do Google.
  2. Google zastępuje wygraną cenę w makro za pomocą niewypełnionego kodowania base64 bezpiecznego dla sieci (RFC 3548).
  3. Fragment kodu przekazuje potwierdzenie w wybranym formacie. Potwierdzenia można na przykład przekazywać w adresie URL żądania niewidocznego piksela renderowanego w ramach reklamy.
  4. Na serwerze aplikacja dekoduje informacje o zwycięskiej cenie za pomocą funkcji base64 w formacie web-safe i odszyfrowuje wynik.

Zależności

Potrzebna będzie biblioteka kryptograficzna obsługująca SHA-1 HMAC, np. Openssl.

Przykładowy kod

Przykładowy kod jest dostępny w językach Java i C++. Można go pobrać z projektu privatedatacommunicationprotocol.

  • Przykładowy kod w Javie korzysta z dekodera base64 z projektu Apache Commons. Nie musisz pobierać kodu Apache Commons, ponieważ implementacja referencyjna zawiera niezbędne części i jest więc samowystarczalna.

  • Przykładowy kod C++ korzysta z metody base64 BIO OpenSSL. Dekoduje ciąg znaków zakodowany w standardzie base64 (RFC 3548) i przekształca go w tekst. Zwykle ciągi tekstowe Base64 bezpieczne dla stron internetowych zastępują wypełnienie „="” przez „". (Uwaga: cudzysłówy są dodawane w celu ułatwienia czytania i nie są uwzględniane w protokole), ale zastąpienie za pomocą makra nie wypełnia zaszyfrowanej ceny. Implementacja referencyjna dodaje wypełnienie, ponieważ OpenSSL ma problemy z niewypełnionymi ciągami znaków.

Kodowanie

Szyfrowanie i odszyfrowywanie zwycięskich cen wymaga użycia 2 tajnych, ale udostępnionych kluczy. Klucz integralności i klucz szyfrowania, odpowiednio i_key i e_key. Oba klucze są udostępniane podczas konfigurowania konta w postaci bezpiecznych dla sieci ciągów znaków w formacie base64. Znajdziesz je na stronie Authorized Buyers w sekcji Ustawienia systemu licytującego > Ustawienia RTB > Klucze szyfrowania.

Przykłady kluczy integralności i szyfrowania:

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

Klucze powinny być najpierw zdekodowane w bezpiecznym formacie internetowym, a potem zdekodowane w formacie base64 przez aplikację:

e_key = WebSafeBase64Decode('skU7Ax_NL5pPAFyKdkfZjZz2-VhIN8bjj1rVFOaJ_5o=')
i_key = WebSafeBase64Decode('arO23ykdNqUQ5LEoQ0FVmPkBd7xB5CO89PDZlSjpFxo=')

Schemat szyfrowania

Cena jest szyfrowana za pomocą niestandardowego schematu szyfrowania, który ma na celu zminimalizowanie rozmiaru obciążenia przy jednoczesnym zapewnieniu odpowiedniego poziomu bezpieczeństwa. Schemat szyfrowania używa algorytmu HMAC z kluczem, aby wygenerować tajny blok na podstawie unikalnego identyfikatora zdarzenia wyświetlenia.

Zaszyfrowana cena ma stałą długość 28 bajtów. Składa się z 16-bajtowego wektora inicjującego, 8-bajtowego tekstu zaszyfrowanego i 4-bajtowego podpisu integralności. Zaszyfrowana cena jest zakodowana w formacie base64 zgodnym ze standardem RFC 3548, z pominięciem znaków wypełnienia. W związku z tym zaszyfrowana cena o długości 28 bajtów jest kodowana jako ciąg znaków o długości 38 znaków w formacie Base64, niezależnie od wygranej ceny.

Przykłady zaszyfrowanych cen:

YWJjMTIzZGVmNDU2Z2hpN7fhCuPemCce_6msaw  // 100 CPI micros
YWJjMTIzZGVmNDU2Z2hpN7fhCuPemCAWJRxOgA  // 1900 CPI micros
YWJjMTIzZGVmNDU2Z2hpN7fhCuPemC32prpWWw  // 2700 CPI micros

Zaszyfrowany format:

{initialization_vector (16 bytes)}{encrypted_price (8 bytes)}
{integrity (4 bytes)}

Cena jest zaszyfrowana jako <price xor HMAC(encryption_key, initialization_vector)>, więc deszyfrowanie oblicza HMAC(encryption_key,initialization_vector) i xoruje z zaszyfrowaną ceną, aby odwrócić szyfrowanie. Etap integralności zajmuje 4 bajty <HMAC(integrity_key, price||initialization_vector)>, gdzie || to konkatenacja.

Wejścia
iv wektor inicjujący (16 bajtów – unikalny dla danego wyświetlenia);
e_key klucz szyfrowania (32 bajty – podany podczas konfigurowania konta);
i_key klucz integralności (32 bajty – podany podczas konfigurowania konta);
price (8 bajtów – w mikrowalucie konta)
Notacja
hmac(k, d) HMAC SHA-1 danych d, przy użyciu klucza k
a || b ciąg tekstowy a złączony z ciągiem tekstowym b
Pseudokod
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 )

Schemat odszyfrowywania

Kod odszyfrowywania musi odszyfrować cenę za pomocą klucza szyfrowania i zweryfikować bity integralności za pomocą klucza integralności. Klucze zostaną udostępnione podczas konfiguracji. Nie ma żadnych ograniczeń dotyczących szczegółów struktury implementacji. W większości przypadków możesz pobrać przykładowy kod i dostosować go do swoich potrzeb.

Wejścia
e_key klucz szyfrowania (32 bajty) – podany podczas konfigurowania konta;
i_key klucz integralności, 32 bajty – podany podczas konfigurowania konta;
final_message 38 znaków, obsługiwana w internecie i zakodowana w base64
Pseudokod
// 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)

Wykrywanie ataków z nieaktualnymi odpowiedziami

Aby wykrywać nieaktualne odpowiedzi lub ataki polegające na powtórzeniu odpowiedzi, zalecamy odfiltrowywanie odpowiedzi z sygnaturą czasową, która znacznie różni się od czasu systemowego, uwzględniając różnice w strefach czasowych.

Wektor inicjowania zawiera sygnaturę czasową w pierwszych 8 bajtach. Może być odczytywany przez tę funkcję 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)
}

Czas można przekonwertować na czytelną dla człowieka postać za pomocą tego kodu 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);

Biblioteka Java

Zamiast implementować algorytmy kryptograficzne do kodowania i dekodowania ceny zwycięskiej oferty, możesz użyć pliku DoubleClickCrypto.java. Więcej informacji znajdziesz w artykule Kryptografia.