Расшифровка идентификаторов рекламодателей для рекламных сетей

Рекламные сети, использующие теги JavaScript для заполнения объявлений через Авторизованных покупателей, имеют право получать идентификаторы рекламодателя как для устройств Android, так и для iOS. Информация отправляется через макрос %%EXTRA_TAG_DATA%% или %%ADVERTISING_IDENTIFIER%% в теге JavaScript, управляемом Авторизованными покупателями. Остальная часть этого раздела посвящена извлечению %%EXTRA_TAG_DATA%% , но подробности о зашифрованном прото-буфере %%ADVERTISING_IDENTIFIER%% MobileAdvertisingId , который можно расшифровать аналогичным образом, см. в разделе Ремаркетинг с IDFA или рекламным идентификатором.

Хронология

  1. Рекламная сеть обновляет теги JavaScript в приложениях через пользовательский интерфейс Авторизованных покупателей , добавляя макрос %%EXTRA_TAG_DATA%% , как описано ниже.
  2. Во время показа приложение запрашивает рекламу у Авторизованных покупателей через Google Mobile Ads SDK , безопасно передавая идентификатор рекламодателя.
  3. Приложение получает обратно тег JavaScript с макросом %%EXTRA_TAG_DATA%% , заполненным зашифрованным буфером протокола рекламной сети, содержащим этот идентификатор.
  4. Приложение запускает этот тег, вызывая рекламную сеть для поиска победившего объявления.
  5. Чтобы использовать (монетизировать) эту информацию, рекламная сеть должна обработать буфер протокола:
    1. Декодируйте строку веб-безопасности обратно в байтовую строку с помощью WebSafeBase64.
    2. Расшифруйте его, используя схему, изложенную ниже.
    3. Десериализуйте прототип и получите идентификатор рекламодателя из ExtraTagData.advertising_id или ExtraTagData.hashed_idfa.

Зависимости

  1. Кодировщик WebSafeBase64 .
  2. Криптобиблиотека, поддерживающая SHA-1 HMAC, например Openssl .
  3. Компилятор буфера протокола Google.

Декодировать строку веб-безопасности

Поскольку информация, отправляемая через макрос %%EXTRA_TAG_DATA%% должна отправляться через URL-адрес, серверы Google кодируют ее с помощью веб-безопасного base64 ( RFC 3548 ).

Поэтому, прежде чем пытаться расшифровать, вы должны декодировать символы ASCII обратно в байтовую строку. Приведенный ниже пример кода C++ основан на BIO_f_base64() проекта OpenSSL и является частью примера кода расшифровки 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<char*>(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);
}

Структура зашифрованной байтовой строки

После того как вы декодировали символы ASCII обратно в байтовую строку, вы готовы ее расшифровать. Зашифрованная байтовая строка содержит 3 раздела:

  • initialization_vector : 16 байт.
  • ciphertext : серия 20-байтовых секций.
  • integrity_signature : 4 байта.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

Массив байтов ciphertext разделен на несколько секций по 20 байт, за исключением того, что самая последняя секция может содержать от 1 до 20 байтов включительно. Для каждого раздела исходного byte_array соответствующий 20-байтовый ciphertext генерируется как:

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

где || является конкатенация.

Определения

Переменная Подробности
initialization_vector 16 байт — уникально для впечатления.
encryption_key 32 байта — предоставляются при настройке учетной записи.
integrity_key 32 байта — предоставляются при настройке учетной записи.
byte_array Сериализованный объект ExtraTagData в секциях по 20 байт.
counter_bytes Значение байта, показывающее порядковый номер раздела, см. ниже.
final_message Общий массив байтов, отправленный через макрос %%EXTRA_TAG_DATA%% (без кодировки WebSafeBase64).
Операторы Подробности
hmac(key, data) SHA-1 HMAC, использование key для шифрования data .
a || b строка a объединена со строкой b .

Вычислить счетчик_байтов

counter_bytes отмечает порядок каждой 20-байтовой секции ciphertext . Обратите внимание, что последняя секция может содержать от 1 до 20 байт включительно. Чтобы заполнить counter_bytes правильным значением при запуске функции hmac() , подсчитайте 20-байтовые секции (включая остаток) и используйте следующую справочную таблицу:

Номер раздела значение counter_bytes
0 Никто
1 … 256 1 байт. Значение последовательно увеличивается от 0 до 255.
257…512 2 байта. Значение первого байта равно 0, значение второго байта последовательно увеличивается от 0 до 255.
513…768 3 байта. Значение первых двух байтов равно 0, значение последнего байта последовательно увеличивается от 0 до 255.

Вернуться наверх

Схема шифрования

Схема шифрования основана на той же схеме, которая используется для расшифровки сигнала гиперлокального нацеливания .

  1. Сериализация : экземпляр объекта ExtraTagData, определенный в буфере протокола, сначала сериализуется с помощью SerializeAsString() в массив байтов.

  2. Шифрование . Затем массив байтов шифруется с использованием специальной схемы шифрования, предназначенной для минимизации накладных расходов по размеру и обеспечения адекватной безопасности. Схема шифрования использует алгоритм HMAC с ключами для создания секретной панели на основе initialization_vector , уникального для события показа.

Псевдокод шифрования

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

Схема расшифровки

Ваш код дешифрования должен: 1) расшифровать буфер протокола с помощью ключа шифрования и 2) проверить биты целостности с помощью ключа целостности. Ключи будут предоставлены вам во время настройки учетной записи. Нет никаких ограничений на то, как вы структурируете свою реализацию. По большей части вы сможете взять пример кода и адаптировать его в соответствии со своими потребностями.

  1. Создайте свой блокнот : HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR : возьмите этот результат и <xor> с зашифрованным текстом, чтобы отменить шифрование.
  3. Проверьте : подпись целостности передает 4 байта HMAC(integrity_key, byte_array || initialization_vector)

Псевдокод расшифровки

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

Пример кода C++

Сюда включена ключевая функция из нашего полного примера кода расшифровки .

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');
    }
  }

Получить данные из буфера протокола рекламной сети.

После декодирования и расшифровки данных, переданных в %%EXTRA_TAG_DATA%% , вы готовы десериализовать буфер протокола и получить идентификатор рекламодателя для таргетинга.

Если вы не знакомы с буферами протоколов, начните с нашей документации .

Определение

Наш буфер протокола рекламной сети определяется следующим образом:

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

Вам нужно будет десериализовать его с помощью ParseFromString() , как описано в документации по буферу протокола C++ .

Подробные сведения о полях advertising_id и iOS hashed_idfa см. в разделах «Расшифровка рекламного идентификатора» и «Таргетинг ресурсов мобильных приложений с помощью IDFA» .

Java-библиотека

Вместо реализации криптоалгоритмов для кодирования и декодирования идентификаторов рекламодателей для рекламных сетей вы можете использовать DoubleClickCrypto.java . Дополнительные сведения см. в разделе Криптография .