Odszyfrowywanie identyfikatorów reklamodawcy w sieciach reklamowych

Sieci reklamowe korzystające z tagów JavaScript do wypełniania reklam za pomocą Authorized Buyers mogą otrzymywać identyfikatory reklamodawców zarówno na urządzenia z Androidem, jak i z iOS. Informacje są wysyłane za pomocą makra %%EXTRA_TAG_DATA%% lub %%ADVERTISING_IDENTIFIER%% w tagu JavaScript zarządzanym przez program Authorized Buyers. Pozostała część tej sekcji dotyczy wyodrębniania danych %%EXTRA_TAG_DATA%%. W sekcji Remarketing z wykorzystaniem identyfikatora IDFA lub identyfikatora wyświetlania reklam znajdziesz szczegółowe informacje o %%ADVERTISING_IDENTIFIER%%zaszyfrowanym buforze protokołuMobileAdvertisingId, który można w podobny sposób odszyfrować.

Oś czasu

  1. Sieć reklamowa aktualizuje tagi JavaScript w aplikacji za pomocą interfejsu Authorized Buyers, dodając makro %%EXTRA_TAG_DATA%% w sposób opisany poniżej.
  2. W momencie wyświetlenia aplikacja wysyła żądanie reklamy do Authorized Buyers za pomocą pakietu SDK do reklam mobilnych Google, a jednocześnie bezpiecznie przekazuje identyfikator reklamodawcy.
  3. Aplikacja zwraca tag JavaScript, a makro %%EXTRA_TAG_DATA%% jest wypełnione zaszyfrowanym buforem protokołu sieci reklamowej zawierającym ten identyfikator.
  4. Aplikacja uruchamia ten tag, wywołując sieć reklamową w przypadku zwycięskiej reklamy.
  5. Aby korzystać z tych informacji (zarabiać), sieć reklamowa musi przetworzyć bufor protokołu:
    1. Zdekoduj ciąg znaków w sieci z powrotem do ciągu bajtów przy użyciu WebSafeBase64.
    2. Odszyfruj go, korzystając ze schematu opisanego poniżej.
    3. Zdeserializuj proto i uzyskaj identyfikator reklamodawcy z ExtraTagData.advertising_id lub ExtraTagData.hashed_idfa.

Zależności

  1. Koder WebSafeBase64.
  2. Biblioteka kryptograficzna obsługująca protokół SHA-1 HMAC, np. Openssl.
  3. Kompilator bufora protokołu Google.

Dekoduj ciąg znaków dotyczący bezpieczeństwa w internecie

Informacje wysyłane za pomocą makra %%EXTRA_TAG_DATA%% muszą być wysyłane za pomocą adresu URL, dlatego serwery Google kodują je w bezpiecznym formacie base64 (RFC 3548).

Dlatego przed próbą odszyfrowania musisz zdekodować znaki ASCII z powrotem do ciągu bajtów. Przykładowy kod w C++ poniżej jest oparty na projekcie BIO_f_base64()OpenSSL Project i jest częścią przykładowego kodu odszyfrowywania Google.

string AddPadding(const string& b64_string) {
  if (b64_string.size() % 4 == 3) {
    return b64_string + "=";
  } else if (b64_string.size() % 4 == 2) {
    return b64_string + "==";
  }
  return b64_string;
}

// Adapted from http://www.openssl.org/docs/man1.1.0/crypto/BIO_f_base64.html
// Takes a web safe base64 encoded string (RFC 3548) and decodes it.
// Normally, web safe base64 strings have padding '=' replaced with '.',
// but we will not pad the ciphertext. We add padding here because
// openssl has trouble with unpadded strings.
string B64Decode(const string& encoded) {
  string padded = AddPadding(encoded);
  // convert from web safe -> normal base64.
  int32 index = -1;
  while ((index = padded.find_first_of('-', index + 1)) != string::npos) {
    padded[index] = '+';
  }
  index = -1;
  while ((index = padded.find_first_of('_', index + 1)) != string::npos) {
    padded[index] = '/';
  }

  // base64 decode using openssl library.
  const int32 kOutputBufferSize = 256;
  char output[kOutputBufferSize];

  BIO* b64 = BIO_new(BIO_f_base64());
  BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
  BIO* bio = BIO_new_mem_buf(const_cast(padded.data()),
                             padded.length());
  bio = BIO_push(b64, bio);
  int32 out_length = BIO_read(bio, output, kOutputBufferSize);
  BIO_free_all(bio);
  return string(output, out_length);
}

Struktura zaszyfrowanego ciągu bajtów

Po zdekodowaniu znaków ASCII z powrotem do ciągu bajtów możesz go odszyfrować. Zaszyfrowany ciąg bajtów zawiera 3 sekcje:

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

Tablica bajtowa ciphertext jest dzielona na wiele sekcji 20-bajtowych, z tym że ostatnia sekcja może zawierać od 1 do 20 bajtów. W przypadku każdej sekcji oryginalnej byte_array odpowiedni 20-bajtowy element ciphertext jest generowany jako:

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

gdzie || to konkatenacja.

Definicje

Zmienna Szczegóły
initialization_vector 16 bajtów – unikalny dla wyświetlenia.
encryption_key 32 bajty – podawane podczas zakładania konta.
integrity_key 32 bajty – podawane podczas zakładania konta.
byte_array Zserializowany obiekt ExtraTagData w sekcjach 20-bajtowych.
counter_bytes Wartość bajtów wskazująca numer porządkowy sekcji (patrz poniżej).
final_message Łączna tablica bajtów wysłana przez makro %%EXTRA_TAG_DATA%% (bez kodowania WebSafeBase64).
Operatory Szczegóły
hmac(key, data) SHA-1 HMAC, używany do szyfrowania data, przy użyciu key.
a || b ciąg znaków a połączony z ciągiem tekstowym b.

Oblicz bajty licznika

counter_bytes oznacza kolejność każdej 20-bajtowej sekcji ciphertext. Pamiętaj, że ostatnia sekcja może zawierać od 1 do 20 bajtów. Aby wypełnić element counter_bytes prawidłową wartością podczas uruchamiania funkcji hmac(), policz sekcje 20-bajtowe (wraz z pozostałymi) i skorzystaj z tej tabeli referencyjnej:

Numer sekcji Wartość: counter_bytes
0 Brak
1...256 1 bajt. Wartość rośnie sekwencyjnie od 0 do 255.
257 ... 512 2 bajty. Wartość pierwszego bajtu to 0, a wartość drugiego bajta zwiększa się kolejno od 0 do 255.
513 ... 768 3 bajty. Wartość z pierwszych 2 bajtów wynosi 0, a wartość ostatniego bajtu zwiększa się kolejno od 0 do 255.

Powrót do góry

Schemat szyfrowania

Schemat szyfrowania jest oparty na tym samym schemacie co do odszyfrowywania sygnału kierowania hiperlokalnego.

  1. Serializacja: instancja obiektu ExtraTagData zdefiniowana w buforze protokołu jest najpierw zserializowana przez SerializeAsString() do tablicy bajtów.

  2. Szyfrowanie: tablica bajtów jest następnie szyfrowana przy użyciu niestandardowego schematu szyfrowania, który miałby zminimalizować obciążenie związane z rozmiarem i zapewnić odpowiedni poziom bezpieczeństwa. Schemat szyfrowania korzysta z algorytmu HMAC z kluczem, aby generować tajną klawiaturę na podstawie parametru initialization_vector, która jest unikalna dla zdarzenia wyświetlenia.

Pseudokod szyfrowania

byte_array = SerializeAsString(ExtraTagData 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ć bufor protokołu przy użyciu klucza szyfrowania i 2) zweryfikować bity integralności za pomocą klucza integralności. Klucze zostaną Ci udostępnione podczas zakładania konta. Nie ma żadnych ograniczeń struktury implementacji. Najczęściej wystarczy wziąć przykładowy kod i dostosować go do swoich potrzeb.

  1. Wygeneruj pad: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: użyj tego wyniku i <xor> razem z tekstem szyfrowanym, aby odwrócić szyfrowanie.
  3. Weryfikacja: podpis integralności przekazuje 4 bajty pliku HMAC(integrity_key, byte_array || initialization_vector)

Pseudokod odszyfrowywania

// split up according to length rules
(initialization_vector, ciphertext, integrity_signature) = final_message

// for each 20-byte section of ciphertext
pad = hmac(encryption_key, initialization_vector || counter_bytes)

// for each 20-byte section of ciphertext
byte_array = ciphertext <xor> pad

confirmation_signature = hmac(integrity_key, byte_array ||
                         initialization_vector)
success = (confirmation_signature == integrity_signature)

Przykładowy kod w C++

Tutaj znajdziesz funkcję klucza z 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');
    }
  }

Pobieranie danych z bufora protokołów sieci reklamowej

Po zdekodowaniu i odszyfrowaniu danych przekazywanych w funkcji %%EXTRA_TAG_DATA%% możesz dokonać deserializacji bufora protokołu i uzyskać identyfikator reklamodawcy na potrzeby kierowania.

Jeśli nie wiesz, czym są bufory protokołów, zacznij od naszej dokumentacji.

Definicja

Nasz bufor protokołu sieci reklamowej jest zdefiniowany następująco:

message ExtraTagData {
  // advertising_id can be Apple's identifier for advertising (IDFA)
  // or Android's advertising identifier. When the advertising_id is an IDFA,
  // it is the plaintext returned by iOS's [ASIdentifierManager
  // advertisingIdentifier]. For hashed_idfa, the plaintext is the MD5 hash of
  // the IDFA.  Only one of the two fields will be available, depending on the
  // version of the SDK making the request.  Later SDKs provide unhashed values.
  optional bytes advertising_id = 1;
  optional bytes hashed_idfa = 2;
}

Musisz go poddać deserializacji za pomocą ParseFromString() zgodnie z opisem w dokumentacji bufora protokołów C++.

Szczegółowe informacje o polach advertising_id i iOS hashed_idfa znajdziesz w artykułach Odszyfrowywanie identyfikatora wyświetlania reklam i Kierowanie na zasoby reklamowe w aplikacjach mobilnych za pomocą identyfikatora IDFA.

Biblioteka Java

Zamiast implementować algorytmy kryptograficzne do kodowania i dekodowania identyfikatorów reklamodawcy w sieciach reklamowych, możesz użyć DoubleClickCrypto.java. Więcej informacji znajdziesz w artykule na temat kryptografii.