Desencriptación de los identificadores de anunciantes para las redes de publicidad

Las redes de publicidad que usan etiquetas de JavaScript para completar anuncios mediante Authorized Buyers son aptas para recibir identificadores de anunciantes en dispositivos iOS y Android. La información se envía a través de las macros %%EXTRA_TAG_DATA%% o %%ADVERTISING_IDENTIFIER%% en la etiqueta de JavaScript administrada por Authorized Buyers. El resto de esta sección se enfoca en la extracción de %%EXTRA_TAG_DATA%%, pero consulta Remarketing con IDFA o ID de publicidad para obtener detalles sobre el búfer de protocolo encriptado de %%ADVERTISING_IDENTIFIER%% MobileAdvertisingId que se puede desencriptar de forma análoga.

Cronograma

  1. La red de publicidad actualiza sus etiquetas de JavaScript en la aplicación a través de la IU de Authorized Buyers y agrega la macro %%EXTRA_TAG_DATA%% como se explica a continuación.
  2. Durante la publicación, la app solicita un anuncio a Authorized Buyers mediante el SDK de anuncios de Google para dispositivos móviles y pasa el identificador de anunciante de forma segura.
  3. La app vuelve a recibir la etiqueta de JavaScript, con la macro %%EXTRA_TAG_DATA%% completada con el búfer de protocolo de la red de publicidad encriptado que contiene ese identificador.
  4. La app ejecuta esta etiqueta y realiza una llamada a la red de publicidad para el anuncio ganador.
  5. Para utilizar (monetizar) esta información, la red de publicidad debe procesar el búfer de protocolo:
    1. Vuelve a decodificar la string websafe en una cadena de bytes con WebSafeBase64.
    2. Desencriptarlo con el esquema que se describe a continuación.
    3. Deserializa el proto y obtén el ID del anunciante de ExtraTagData.advertising_id o ExtraTagData.hashed_idfa.

Dependencias

  1. El codificador WebSafeBase64
  2. Una biblioteca criptográfica compatible con SHA-1 HMAC, como Openssl.
  3. El compilador de búfer de protocolo de Google.

Decodificar string segura para la Web

Debido a que la información enviada con la macro %%EXTRA_TAG_DATA%% debe enviarse a través de URL, los servidores de Google la codifican con base64 segura para la Web (RFC 3548).

Por lo tanto, antes de intentar desencriptarlos, debes decodificar los caracteres ASCII nuevamente en una cadena de bytes. El código de muestra de C++ que aparece a continuación se basa en el BIO_f_base64() del proyecto OpenSSL y forma parte del código de desencriptación de muestra de 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);
}

Estructura de la cadena de bytes encriptada

Una vez que hayas decodificado los caracteres ASCII en una cadena de bytes, estarás listo para desencriptarlos. La cadena de bytes encriptada contiene 3 secciones:

  • initialization_vector: 16 bytes.
  • ciphertext: Serie de secciones de 20 bytes.
  • integrity_signature: 4 bytes.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

El array de bytes ciphertext se divide en varias secciones de 20 bytes, con la excepción de que la última sección puede contener entre 1 y 20 bytes inclusive. Para cada sección del byte_array original, se genera el ciphertext de 20 bytes correspondiente de la siguiente manera:

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

En el ejemplo anterior, || es concatenación.

Definiciones

Variable Detalles
initialization_vector 16 bytes: único para la impresión.
encryption_key 32 bytes, que se proporcionan durante la configuración de la cuenta.
integrity_key 32 bytes, que se proporcionan durante la configuración de la cuenta.
byte_array Un objeto ExtraTagData serializado, en secciones de 20 bytes.
counter_bytes Valor de bytes que muestra el número ordinal de la sección; consulta a continuación.
final_message Array de bytes total enviado a través de la macro %%EXTRA_TAG_DATA%% (menos la codificación WebSafeBase64).
Operadores Detalles
hmac(key, data) SHA-1 HMAC que usa key para encriptar data.
a || b Se concatenó la cadena a con la cadena b.

Calcular contador_bytes

counter_bytes marca el orden de cada sección de 20 bytes de ciphertext. Ten en cuenta que la última sección puede contener entre 1 y 20 bytes inclusive. Para completar counter_bytes con el valor correcto cuando ejecutas la función hmac(), cuenta las secciones de 20 bytes (incluido el resto) y usa la siguiente tabla de referencia:

Número de sección Valor counter_bytes
0 Ninguna
1 ... 256 1 byte. El valor aumenta de 0 a 255 de forma secuencial.
257 ... 512 2 bytes. El valor del primer byte es 0; el valor del segundo byte incrementa de 0 a 255 de forma secuencial.
513 ... 768 3 bytes. El valor de los dos primeros bytes es 0; el valor del último byte aumenta de 0 a 255 de forma secuencial.

Volver al principio

Esquema de encriptación

El esquema de encriptación se basa en el mismo esquema que se usa para desencriptar el indicador de segmentación hiperlocal.

  1. Serialización: Una instancia del objeto ExtraTagData como se define en el búfer de protocolo primero se serializa a través de SerializeAsString() en un array de bytes.

  2. Encriptación: El array de bytes se encripta con un esquema de encriptación personalizado diseñado para minimizar la sobrecarga de tamaño y, al mismo tiempo, garantizar una seguridad adecuada. El esquema de encriptación usa un algoritmo HMAC con clave para generar un bloque secreto basado en el initialization_vector, que es único para el evento de impresión.

Pseudocódigo de encriptación

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 desencriptación

Tu código de desencriptación debe 1) desencriptar el búfer de protocolo con la clave de encriptación y 2) verificar los bits de integridad con la clave de integridad. Las claves se te proporcionarán durante la configuración de la cuenta. No hay restricciones sobre cómo estructurar tu implementación. En general, debes poder tomar el código de muestra y adaptarlo según tus necesidades.

  1. Genera tu pad: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: Toma este resultado y <xor> con el texto cifrado para revertir la encriptación.
  3. Verificar: La firma de integridad pasa 4 bytes de HMAC(integrity_key, byte_array || initialization_vector).

Pseudocódigo de desencriptación

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

Ejemplo de código C++

Aquí se incluye una función clave de nuestro código de ejemplo de desencriptación 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');
    }
  }

Cómo obtener datos del búfer de protocolo de la red publicitaria

Una vez que hayas decodificado y desencriptado los datos que se pasaron en %%EXTRA_TAG_DATA%%, estarás listo para deserializar el búfer de protocolo y obtener el identificador de anunciante para la segmentación.

Si no conoces los búferes de protocolo, consulta nuestra documentación.

Definición

Nuestro búfer de protocolo de la red de publicidad se define de la siguiente manera:

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

Deberás deserializarla con ParseFromString() como se describe en la documentación del búfer de protocolo C++.

Para obtener detalles sobre los campos hashed_idfa de iOS y advertising_id de Android, consulta Desencriptar el ID de publicidad y Cómo segmentar el inventario de apps para dispositivos móviles con el IDFA.

Biblioteca Java

En lugar de implementar los algoritmos criptográficos para codificar y decodificar los identificadores de anunciantes para redes de publicidad, puedes usar DoubleClickCrypto.java. Para obtener más información, consulta Criptografía.