Decriptazione degli identificatori degli inserzionisti per le reti pubblicitarie

Le reti pubblicitarie che utilizzano i tag JavaScript per compilare gli annunci tramite Authorized Buyers sono idonee a ricevere gli identificatori dell'inserzionista sia per i dispositivi Android che per iOS. Le informazioni vengono inviate tramite la macro %%EXTRA_TAG_DATA%% o %%ADVERTISING_IDENTIFIER%% nel tag JavaScript gestito da Authorized Buyers. Il resto di questa sezione è incentrato sull'estrazione di %%EXTRA_TAG_DATA%%, ma consulta la sezione Remarketing con l'IDFA o l'ID pubblicità per i dettagli sul %%ADVERTISING_IDENTIFIER%%buffer proto criptato MobileAdvertisingId che può essere decriptato in modo analogo.

Sequenza

  1. La rete pubblicitaria aggiorna i tag in-app JavaScript tramite l'interfaccia utente di Authorized Buyers, aggiungendo la macro %%EXTRA_TAG_DATA%%, come spiegato di seguito.
  2. Al momento della pubblicazione, l'app richiede un annuncio da Authorized Buyers tramite l'SDK Google Mobile Ads, trasmettendo in modo sicuro l'identificatore dell'inserzionista.
  3. L'app riceve il tag JavaScript con la macro %%EXTRA_TAG_DATA%% compilata con il buffer di protocollo della rete pubblicitaria criptato contenente l'identificatore.
  4. L'app esegue questo tag, effettuando una chiamata alla rete pubblicitaria per l'annuncio vincente.
  5. Per poter utilizzare (monetizzare) queste informazioni, la rete pubblicitaria deve elaborare il buffer di protocollo:
    1. Decodifica la stringa websafe in una stringa di byte con WebSafeBase64.
    2. Decriptalo utilizzando lo schema descritto di seguito.
    3. Deserializza il protocollo e recupera l'ID inserzionista da ExtraTagData.advertising_id o ExtraTagData.hashed_idfa.

Dipendenze

  1. L'encoder WebSafeBase64.
  2. Una libreria di crittografia che supporta HMAC SHA-1, ad esempio Openssl.
  3. Il compilatore del buffer di protocollo di Google.

Decodifica la stringa websafe

Poiché le informazioni inviate tramite la macro %%EXTRA_TAG_DATA%% devono essere inviate tramite l'URL, i server di Google le codificano con il protocollo base64 sicuro per il web (RFC 3548).

Pertanto, prima di tentare la decrittografia, devi decodificare di nuovo i caratteri ASCII in una stringa di byte. Il codice C++ di esempio riportato di seguito si basa sul modulo BIO_f_base64() del progetto OpenSSL e fa parte del codice di decriptazione di esempio di 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);
}

Struttura della bytestringa criptata

Dopo aver decodificato di nuovo i caratteri ASCII in una stringa di byte, puoi decriptarla. La stringa di byte criptata contiene tre sezioni:

  • initialization_vector: 16 byte.
  • ciphertext: serie di sezioni da 20 byte.
  • integrity_signature: 4 byte.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

L'array di ciphertext byte è diviso in più sezioni da 20 byte, con l'eccezione che l'ultima sezione può contenere tra 1 e 20 byte inclusi. Per ogni sezione dell'elemento byte_array originale, il valore ciphertext a 20 byte corrispondente viene generato come:

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

dove || è la concatenazione.

Definizioni

Variabile Dettagli
initialization_vector 16 byte, univoci per l'impressione.
encryption_key 32 byte, forniti al momento della creazione dell'account.
integrity_key 32 byte, forniti al momento della creazione dell'account.
byte_array Un oggetto ExtraTagData serializzato in sezioni di 20 byte.
counter_bytes Valore byte che mostra il numero ordinale della sezione, vedi sotto.
final_message Array di byte totali inviati tramite la macro %%EXTRA_TAG_DATA%% (meno la codifica WebSafeBase64).
Operatori Dettagli
hmac(key, data) HMAC SHA-1, utilizzando key per criptare data.
a || b stringa a concatenata con la stringa b.

Calcolare contatori_byte

counter_bytes segna l'ordine di ogni sezione da 20 byte dell'elemento ciphertext. Tieni presente che l'ultima sezione potrebbe contenere da 1 a 20 byte inclusi. Per inserire in counter_bytes il valore corretto quando esegui la funzione hmac(), conta le sezioni a 20 byte (incluso il resto) e utilizza la seguente tabella di riferimento:

Numero sezione Valore counter_bytes
0 Nessuno
1 ... 256 1 byte. Il valore viene incrementato in sequenza da 0 a 255.
257 ... 512 2 byte. Il valore del primo byte è 0, mentre il valore del secondo byte aumenta da 0 a 255 in sequenza.
513 ... 768 3 byte. Il valore dei primi due byte è 0, mentre il valore dell'ultimo byte aumenta da 0 a 255 in sequenza.

Torna all'inizio

Schema di crittografia

Lo schema di crittografia si basa sullo stesso schema utilizzato per decriptare l'indicatore di targeting iperlocale.

  1. Serializzazione: un'istanza dell'oggetto ExtraTagData definita nel buffer di protocollo viene dapprima serializzata tramite SerializeAsString() in un array di byte.

  2. Crittografia: l'array di byte viene quindi criptato utilizzando uno schema di crittografia personalizzato progettato per ridurre al minimo l'overhead delle dimensioni, garantendo al contempo una sicurezza adeguata. Lo schema di crittografia utilizza un algoritmo HMAC con chiave per generare un secret pad basato su initialization_vector, univoco per l'evento di impressione.

Pseudocodice di crittografia

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

Schema di decrittografia

Il codice di decriptazione deve 1) decriptare il buffer di protocollo utilizzando la chiave di crittografia e 2) verificare i bit di integrità con la chiave di integrità. Le chiavi ti verranno fornite durante la creazione dell'account. Non sono previste limitazioni su come strutturare l'implementazione. Nella maggior parte dei casi, dovresti essere in grado di prendere il codice campione e adattarlo alle tue esigenze.

  1. Genera il pad: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: prendi questo risultato e <xor> con il testo crittografato per invertire la crittografia.
  3. Verifica: la firma di integrità passa 4 byte di HMAC(integrity_key, byte_array || initialization_vector)

Pseudocodice di decrittografia

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

Codice C++ di esempio

Qui è inclusa una funzione chiave del nostro codice di esempio di decriptazione 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');
    }
  }

Recupero dati dal buffer di protocollo della rete pubblicitaria

Dopo aver decodificato e decriptato i dati trasmessi in %%EXTRA_TAG_DATA%%, puoi deserializzare il buffer di protocollo e ottenere l'identificatore dell'inserzionista per il targeting.

Se non hai dimestichezza con i buffer di protocollo, consulta la nostra documentazione.

Definizione

Il nostro buffer di protocollo della rete pubblicitaria è definito come segue:

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

Dovrai deserializzarlo utilizzando ParseFromString() come descritto nella documentazione relativa al buffer del protocollo C++.

Per maggiori dettagli sui campi Android advertising_id e iOS hashed_idfa, consulta Decriptare l'ID pubblicità e Targeting dell'inventario per app mobile con l'IDFA.

libreria Java

Anziché implementare gli algoritmi di crittografia per codificare e decodificare gli identificatori inserzionista per le reti pubblicitarie, puoi utilizzare DoubleClickCrypto.java. Per ulteriori informazioni, consulta la pagina relativa alla crittografia.