Odszyfrowywanie hiperlokalnych sygnałów kierowania

Jeśli wydawcy przekazują do usługi Authorized Buyers dane o lokalizacji na komórki, które są bardziej szczegółowe niż kod pocztowy, Authorized Buyers wysyła do kupujących nowy rekord – BidRequest.encrypted_hyperlocal_set w postaci geofencingu.

oś czasu

  1. Użytkownik instaluje aplikację mobilną z reklamami i wyraża zgodę na jej dostęp do lokalizacji urządzenia innym firmom oraz udostępnianie ich. Aplikacja jest też zintegrowana z pakietem SDK Google Ads i wysyła do Google lokalizację urządzenia.
  2. Serwery Google generują specjalny sygnał kierowania lokalnego, który reprezentuje położenie geograficzne urządzenia, np. w celu ochrony prywatności użytkownika.
  3. Serwery Google serializują i szyfrują sygnał kierowania lokalnego, korzystając z klucza bezpieczeństwa każdego kupującego. Pamiętaj, że do odszyfrowywania WINNING_PRICE makr licytujący używa tego samego klucza.
  4. Licytujący odszyfrowuje i deserializuje sygnał kierowania hiperlokalnego w buforze protokołu. Licytujący może następnie przeanalizować sygnał i ustalić odpowiednią stawkę.

Zależności

Potrzebujesz biblioteki kryptograficznej, która obsługuje HMAC SHA-1, np. Openssl.

Definicja

Sygnał kierowania hiperlokalnego jest zdefiniowany w protoku:

// A hyperlocal targeting location when available.
//
message Hyperlocal {
  // A location on the Earth's surface.
  //
  message Point {
    optional float latitude = 1;
    optional float longitude = 2;
  }

  // The mobile device can be at any point inside the geofence polygon defined
  // by a list of corners.  Currently, the polygon is always a parallelogram
  // with 4 corners.
  repeated Point corners = 1;
}

message HyperlocalSet {
  // This field currently contains at most one hyperlocal polygon.
  repeated Hyperlocal hyperlocal = 1;

  // The approximate geometric center of the geofence area.  It is calculated
  // exclusively based on the geometric shape of the geofence area and in no
  // way indicates the mobile device's actual location within the geofence
  // area. If multiple hyperlocal polygons are specified above then
  // center_point is the geometric center of all hyperlocal polygons.
  optional Hyperlocal.Point center_point = 2;
}

// Hyperlocal targeting signal when available, encrypted as described at
// https://developers.google.com/authorized-buyers/rtb/response-guide/decrypt-hyperlocal
optional bytes encrypted_hyperlocal_set = 40;

Każdy sygnał kierowania hiperlokalnego zawiera co najmniej jeden wielokąt i punkt środkowy. W przypadku każdego wielokąta sygnał kierowania lokalnego obejmuje:

  • Szerokość i długość geograficzna każdego wielokąta po kolei będą przekazywane jako pole powtarzane corners.
  • Przybliżone centrum geometryczne obszaru geofencingu przekazanego w opcjonalnym polu center_point.

Struktura sygnału kierowania

Zaszyfrowany sygnał kierowania hiperlokalnego zawarty w danych BidRequest.encrypted_hyperlocal_set zawiera 3 sekcje:

  • initialization_vector: 16 bajtów.
  • ciphertext: seria po 20 bajtów.
  • integrity_signature: 4 bajty.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

Tablica ciphertext jest podzielona na wiele sekcji po 20 bajtów z wyjątkiem części, w której cała ostatnia sekcja może zawierać od 1 do 20 bajtów. Dla każdej sekcji oryginalnego pliku byte_array odpowiadające im 20-bajtowe pole ciphertext jest generowane jako:

<byte_array <xor> HMAC(encryption_key, initialization_vector || counter_bytes)>

Gdzie || to połączenie.

Definicje

Zmienna Szczegóły
initialization_vector 16 bajtów – niepowtarzalnych dla wyświetlenia.
encryption_key 32 bajty – podane podczas konfigurowania konta.
integrity_key 32 bajty – podane podczas konfigurowania konta.
byte_array Zserializowano obiekt HyperlocalSet w sekcjach 20-bajtowych.
counter_bytes Wartość w bajcie zawierająca liczbę porządkową sekcji (patrz niżej).
final_message Tablica bajtów wysłana przez pole BidRequest.encrypted_hyperlocal_set.
Operatory Szczegóły
hmac(key, data) Szyfrowanie MAC SHA-1, wykorzystując key do szyfrowania data.
a || b ciąg a połączony z ciągiem b.

Oblicz licznik_bajtów

counter_bytes wskazuje kolejność każdej 20-bajtowych sekcji tagu ciphertext. Ostatnia sekcja może zawierać od 1 do 20 bajtów. Aby funkcja counter_bytes działała prawidłowo z funkcją hmac(), policzyć sekcje 20-bajtowe (w tym pozostałą) i użyć tej tabeli:

Numer sekcji Wartość: counter_bytes
0 Brak
1 ... 256 1 bajt. Sekwencja zwiększa się z 0 do 255.
257 ... 512 2 bajty. Wartość pierwszego bajta wynosi 0, wartość drugiego bajtu rośnie od 0 do 255 po kolei.
513 ... 768 3 bajty. Wartość w pierwszych 2 bajtach wynosi 0, a wartość ostatniego bajtu jest rosnąca w zakresie od 0 do 255.

Nie oczekujemy, że długość zasobu BidRequest.encrypted_hyperlocal_set przekroczy jeden kilobajt, nawet biorąc pod uwagę dalszy rozwój. Mimo to wartość counter_bytes może służyć do obsługi sygnału kierowania lokalnego o dowolnej długości.

Schemat szyfrowania

Schemat szyfrowania dla sygnału kierowania lokalnego jest oparty na tym samym schemacie co odszyfrowywanie potwierdzeń cen.

  1. Serializuj: sygnał kierowania hiperlokalnego, który jest instancją obiektu HyperlocalSet zdefiniowanego w proto, jest najpierw szeregowany przez tablicę SerializeAsString() do tablicy bajtów.

  2. Szyfrowanie: bajt jest szyfrowany przy użyciu niestandardowego schematu szyfrowania, który minimalizuje koszty ogólne i zapewnia właściwe zabezpieczenia. Schemat szyfrowania używa algorytmu HMAC z kluczem, aby wygenerować tajny panel na podstawie initialization_vector, który jest unikalny dla zdarzenia wyświetlenia.

Pseudokod szyfrowania

byte_array = SerializeAsString(HyperlocalSet object)
pad = hmac(encryption_key, initialization_vector || counter_bytes )  // for each 20-byte section of byte_array
ciphertext = pad <xor> byte_array // for each 20-byte section of byte_array
integrity_signature = hmac(integrity_key, byte_array || initialization_vector)  // first 4 bytes
final_message = initialization_vector || ciphertext || integrity_signature

Schemat odszyfrowywania

Kod odszyfrowywania musi: 1) odszyfrować sygnał kierowania hiperlokalnego za pomocą klucza szyfrowania oraz 2) zweryfikować bity integralności za pomocą klucza integralności. Klucze zostaną Ci udostępnione podczas konfiguracji konta. Nie obowiązują żadne ograniczenia dotyczące struktury implementacji. W większości przypadków możesz pobrać przykładowy kod i dostosować go do swoich potrzeb.

  1. Wygeneruj tablet: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: odnieś ten wynik, a <xor> z szyfrem.
  3. Zweryfikuj: podpis integralności przekazuje 4 bajty HMAC(integrity_key, byte_array || initialization_vector)

Pseudokod szyfrowania

(initialization_vector, ciphertext, integrity_signature) = final_message // split up according to length rules
pad = hmac(encryption_key, initialization_vector || counter_bytes)  // for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad // for each 20-byte section of ciphertext
confirmation_signature = hmac(integrity_key, byte_array || initialization_vector)
success = (confirmation_signature == integrity_signature)

Przykładowy kod C++

Poniżej znajdziesz kluczową funkcję naszego pełnego przykładowego kodu odszyfrowywania.

bool DecryptByteArray(
    const string& ciphertext, const string& encryption_key,
    const string& integrity_key, string* cleartext) {
  // Step 1. find the length of initialization vector and clear text.
  const int cleartext_length =
      ciphertext.size() - kInitializationVectorSize - kSignatureSize;
  if (cleartext_length < 0) {
    // The length cannot be correct.
    return false;
  }

  string iv(ciphertext, 0, kInitializationVectorSize);

  // Step 2. recover clear text
  cleartext->resize(cleartext_length, '\0');
  const char* ciphertext_begin = string_as_array(ciphertext) + iv.size();
  const char* const ciphertext_end = ciphertext_begin + cleartext->size();
  string::iterator cleartext_begin = cleartext->begin();

  bool add_iv_counter_byte = true;
  while (ciphertext_begin < ciphertext_end) {
    uint32 pad_size = kHashOutputSize;
    uchar encryption_pad[kHashOutputSize];

    if (!HMAC(EVP_sha1(), string_as_array(encryption_key),
              encryption_key.length(), (uchar*)string_as_array(iv),
              iv.size(), encryption_pad, &pad_size)) {
      printf("Error: encryption HMAC failed.\n");
      return false;
    }

    for (int i = 0;
         i < kBlockSize && ciphertext_begin < ciphertext_end;
         ++i, ++cleartext_begin, ++ciphertext_begin) {
      *cleartext_begin = *ciphertext_begin ^ encryption_pad[i];
    }

    if (!add_iv_counter_byte) {
      char& last_byte = *iv.rbegin();
      ++last_byte;
      if (last_byte == '\0') {
        add_iv_counter_byte = true;
      }
    }

    if (add_iv_counter_byte) {
      add_iv_counter_byte = false;
      iv.push_back('\0');
    }
  }
}

Próbka i sygnał hiperlokalny

Aby przetestować i zweryfikować kod:

  1. Przekonwertuj ciąg znaków zawierający 308 znaków szesnastkowych na tablicę po 154 bajtów. Na przykład w tym ciągu:
    E2014EA201246E6F6E636520736F7572636501414243C0ADF6B9B6AC17DA218FB50331EDB376701309CAAA01246E6F6E636520736F7572636501414243C09ED4ECF2DB7143A9341FDEFD125D96844E25C3C202466E6F6E636520736F7572636502414243517C16BAFADCFAB841DE3A8C617B2F20A1FB7F9EA3A3600256D68151C093C793B0116DB3D0B8BE9709304134EC9235A026844F276797
    
    przekonwertuj go na tablicę 154-bajtową w następujący sposób:
    const char serialized_result[154] = { 0xE2, 0x01, 0x4E, ... };
    
  2. Wywołaj metodę BidRequest.ParsePartialFromString(), aby destylować tablicę 154-bajtową do bufora protokołu BidRequest.
    BidRequest bid_req;
    bid_req.ParsePartialFromString(serialzed_result);
    
  3. Sprawdź, czy pole BidRequest zawiera tylko 3 pola:
    • encrypted_hyperlocal_set
      Zadeklarowano w wiadomości BidReqeust.
    • encrypted_advertising_id
      Zadeklarowano w wiadomości BidReqeust.Mobile.
    • encrypted_hashed_idfa
      Zadeklarowano w wiadomości BidReqeust.Mobile.

    Przykład:

    encrypted_hyperlocal_set:(
        {  100,  100 },
        {  200, -300 },
        { -400,  500 },
        { -600, -700 },)
    encrypted_advertising_id: { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }
    encrypted_hashed_idfa : { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0xF1 }
    
  4. Aby odszyfrować 3 pola i sprawdzić, czy są prawidłowo odszyfrowane, użyj tych pól encryption_key i integrity_key.
    encryption_key = {0x02, 0xEE, 0xa8, 0x3c, 0x6c, 0x12, 0x11, 0xe1, 0x0b,
        0x9f, 0x88, 0x96, 0x6c, 0xee, 0xc3, 0x49, 0x08, 0xeb, 0x94, 0x6f, 0x7e,
        0xd6, 0xe4, 0x41, 0xaf, 0x42, 0xb3, 0xc0, 0xf3, 0x21, 0x81, 0x40};
    
    integrity_key = {0xbf, 0xFF, 0xec, 0x55, 0xc3, 0x01, 0x30, 0xc1, 0xd8,
        0xcd, 0x18, 0x62, 0xed, 0x2a, 0x4c, 0xd2, 0xc7, 0x6a, 0xc3, 0x3b, 0xc0,
        0xc4, 0xce, 0x8a, 0x3d, 0x3b, 0xbd, 0x3a, 0xd5, 0x68, 0x77, 0x92};
    

Wykrywanie nieaktualnych ataków

Aby wykryć nieaktualne odpowiedzi, zalecamy filtrowanie odpowiedzi z sygnaturą czasową, która różni się znacznie od czasu systemowego po uwzględnieniu różnic w strefach czasowych. Nasze serwery mają ustawiony czas PST/PDT.

Szczegółowe informacje o implementacji znajdziesz w artykule „Wykrywanie nieaktualnych ataków odpowiedzi” w artykule Odszyfrowywanie potwierdzeń cen.

Biblioteka Java

Zamiast implementować algorytmy kryptograficzne w celu kodowania i dekodowania sygnałów kierowania hiperlokalnego, możesz użyć pliku DoubleClickCrypto.java. Więcej informacji znajdziesz na stronie Kryptografia.