Como descriptografar identificadores de anunciantes para redes de publicidade

As redes de publicidade que usam tags JavaScript para preencher anúncios por meio do Authorized Buyers estão qualificadas para receber identificadores de anunciantes para dispositivos Android e iOS. As informações são enviadas pela macro %%EXTRA_TAG_DATA%% ou %%ADVERTISING_IDENTIFIER%% na tag JavaScript gerenciada pelo Authorized Buyers. O restante desta seção se concentra na extração de %%EXTRA_TAG_DATA%%. Consulte Remarketing com IDFA ou ID de publicidade para mais detalhes sobre o buffer proto criptografado %%ADVERTISING_IDENTIFIER%% MobileAdvertisingId que pode ser descriptografado de maneira análoga.

Cronograma

  1. A rede de publicidade atualiza as tags JavaScript no app por meio da interface do Authorized Buyers, adicionando a macro %%EXTRA_TAG_DATA%%, conforme explicado abaixo.
  2. No momento da veiculação, o app solicita um anúncio do Authorized Buyers pelo SDK dos anúncios para dispositivos móveis do Google e transmite o identificador do anunciante de maneira segura.
  3. O app recebe de volta a tag JavaScript, com a macro %%EXTRA_TAG_DATA%% preenchida com o buffer de protocolo criptografado da rede de publicidade contendo esse identificador.
  4. O app executa essa tag, fazendo uma chamada para a rede de publicidade do anúncio vencedor.
  5. Para usar (gerar receita) essas informações, a rede de publicidade precisa processar o buffer de protocolo:
    1. Decodifique a string segura para Web novamente em uma string de bytes com WebSafeBase64.
    2. Descriptografe-o usando o esquema descrito abaixo.
    3. Desserialize o proto e receba o ID do anunciante de ExtraTagData.advertising_id ou ExtraTagData.hashed_idfa.

Dependências

  1. O codificador WebSafeBase64.
  2. Uma biblioteca de criptografia compatível com o HMAC SHA-1, como Openssl.
  3. O compilador de buffer de protocolo do Google.

Decodificar string segura da Web

Como as informações enviadas pela macro %%EXTRA_TAG_DATA%% precisam ser enviadas por um URL, os servidores do Google as codificam com base64 segura para a Web (RFC 3548).

Antes de tentar descriptografar, é necessário decodificar os caracteres ASCII de volta em uma bytestring. O exemplo de código C++ abaixo é baseado na BIO_f_base64() do OpenSSL Project (link em inglês) e faz parte do exemplo de código de descriptografia do 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);
}

Estrutura de bytestring criptografada

Depois de decodificar os caracteres ASCII de volta em uma string de bytes, você está pronto para descriptografá-los. Ela contém três seções:

  • initialization_vector: 16 bytes.
  • ciphertext: série de seções de 20 bytes.
  • integrity_signature: 4 bytes.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

A matriz de bytes ciphertext é dividida em várias seções de 20 bytes, com a exceção de que a última seção pode conter entre 1 e 20 bytes. Para cada seção do byte_array original, o ciphertext de 20 bytes correspondente é gerado como:

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

em que || é concatenação.

Definições

Variável Detalhes
initialization_vector 16 bytes: exclusivo da impressão.
encryption_key 32 bytes: fornecidos na configuração da conta.
integrity_key 32 bytes: fornecidos na configuração da conta.
byte_array Um objeto ExtraTagData serializado, em seções de 20 bytes.
counter_bytes Valor de byte mostrando o número ordinal da seção, conforme abaixo.
final_message Matriz de bytes total enviada pela macro %%EXTRA_TAG_DATA%% (menos a codificação WebSafeBase64).
Operadores Detalhes
hmac(key, data) SHA-1 HMAC que usa key para criptografar data.
a || b string a concatenada com a string b.

Calcular contador_bytes

counter_bytes marca a ordem de cada seção de 20 bytes do ciphertext. Observe que a última seção pode conter entre 1 e 20 bytes. Para preencher counter_bytes com o valor correto ao executar a função hmac(), conte as seções de 20 bytes (incluindo o restante) e use a seguinte tabela de referência:

Número da seção Valor counter_bytes
0 Nenhum
1 ... 256 1 byte. O valor aumenta de 0 a 255 sequencialmente.
257 ... 512 2 bytes. O valor do primeiro byte é 0, e o valor do segundo byte aumenta de 0 para 255 sequencialmente.
513 ... 768 3 bytes. O valor dos dois primeiros bytes é 0. O valor do último byte é incrementado de 0 a 255 sequencialmente.

Voltar ao início

Esquema de criptografia

O esquema de criptografia é baseado no mesmo esquema usado para descriptografar o indicador de segmentação hiperlocal.

  1. Serialização: uma instância do objeto ExtraTagData, conforme definido no buffer de protocolo, é serializada primeiro por meio de SerializeAsString() para uma matriz de bytes.

  2. Criptografia: a matriz de bytes é então criptografada usando um esquema de criptografia personalizado projetado para minimizar a sobrecarga de tamanho e, ao mesmo tempo, garantir a segurança adequada. O esquema de criptografia usa um algoritmo HMAC com chave para gerar um preenchimento de chave secreta com base no initialization_vector, que é exclusivo do evento de impressão.

Pseudocódigo de criptografia

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

Esquema de descriptografia

Seu código de descriptografia precisa 1) descriptografar o buffer de protocolo usando a chave de criptografia e 2) verificar os bits de integridade com essa chave. As chaves serão fornecidas a você durante a configuração da conta. Não há restrições em como você estrutura a implementação. Na maioria das vezes, você precisa conseguir adaptar o exemplo de código de acordo com suas necessidades.

  1. Gere seu pad: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: use esse resultado e <xor> com o texto criptografado para reverter a criptografia.
  3. Verificar: a assinatura de integridade transmite 4 bytes de HMAC(integrity_key, byte_array || initialization_vector).

Pseudocódigo de descriptografia

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

Exemplo de código C++

Incluída aqui está uma função de chave do nosso código de exemplo de descriptografia completo.

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

Obter dados do buffer de protocolo da rede de publicidade

Depois de decodificar e descriptografar os dados transmitidos em %%EXTRA_TAG_DATA%%, é possível desserializar o buffer de protocolo e receber o identificador do anunciante para segmentação.

Se você não estiver familiarizado com buffers de protocolo, comece com nossa documentação.

Definição

O buffer de protocolo da rede de publicidade é definido assim:

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

Será necessário desserializá-lo usando ParseFromString(), conforme descrito na documentação do buffer de protocolo C++.

Para detalhes sobre os campos advertising_id do Android e hashed_idfa do iOS, consulte Descriptografar ID de publicidade e Como segmentar inventário de apps para dispositivos móveis com o IDFA.

Biblioteca Java

Em vez de implementar os algoritmos criptográficos para codificar e decodificar os identificadores de anunciante para redes de publicidade, você pode usar o DoubleClickCrypto.java. Para mais informações, consulte Criptografia.