Entschlüsseln von Werbetreibenden-IDs für Werbenetzwerke

Werbenetzwerke, in denen JavaScript-Tags zum Füllen von Anzeigen über Authorized Buyers verwendet werden, können Werbetreibenden-IDs sowohl für Android- als auch für iOS-Geräte empfangen. Die Informationen werden über das Makro %%EXTRA_TAG_DATA%% oder %%ADVERTISING_IDENTIFIER%% im von Authorized Buyers verwalteten JavaScript-Tag gesendet. Der Rest dieses Abschnitts konzentriert sich auf die Extraktion von %%EXTRA_TAG_DATA%%. Unter Remarketing mit IDFA oder Werbe-ID finden Sie jedoch weitere Informationen zum %%ADVERTISING_IDENTIFIER%%-verschlüsselten Proto-Zwischenspeicher MobileAdvertisingId, der entsprechend entschlüsselt werden kann.

Zeitplan

  1. Die JavaScript-In-App-Tags des Werbenetzwerks werden über die Authorized Buyers-Benutzeroberfläche aktualisiert und über das %%EXTRA_TAG_DATA%%-Makro wie unten beschrieben hinzugefügt.
  2. Bei der Auslieferung fordert die App über das Google Mobile Ads SDK eine Anzeige von Authorized Buyers an und übergibt die Werbetreibenden-ID sicher.
  3. Die App empfängt das JavaScript-Tag und das Makro %%EXTRA_TAG_DATA%% wird mit dem verschlüsselten Protokollzwischenspeicher des Werbenetzwerks ausgefüllt, der diese ID enthält.
  4. Die App führt dieses Tag aus und sendet einen Aufruf an das Werbenetzwerk für die erfolgreiche Anzeige.
  5. Zur Verwendung (Monetarisierung) dieser Informationen muss das Werbenetzwerk den Protokollpuffer verarbeiten:
    1. Decodieren Sie den websicheren String mit WebSafeBase64 wieder in einen Bytestring.
    2. Entschlüsseln Sie sie mit dem unten beschriebenen Schema.
    3. Deserialisieren Sie das Protokoll und rufen Sie die Werbetreibenden-ID aus ExtraTagData.advertising_id oder ExtraTagData.hashed_idfa ab.

Abhängigkeiten

  1. Den WebSafeBase64-Encoder
  2. Eine Kryptobibliothek, die SHA-1 HMAC unterstützt, z. B. Openssl.
  3. Den Protokollpuffer-Compiler von Google.

Websicheren String decodieren

Da die über das %%EXTRA_TAG_DATA%%-Makro gesendeten Informationen über eine URL gesendet werden müssen, werden sie von den Google-Servern mit websicherem Base64-Code (RFC 3548) codiert.

Bevor Sie daher eine Entschlüsselung versuchen, müssen Sie die ASCII-Zeichen wieder in einen Bytestring decodieren. Der folgende C++-Beispielcode basiert auf BIO_f_base64() des OpenSSL-Projekts und ist Teil des Beispiel-Entschlüsselungscodes von 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);
}

Struktur eines verschlüsselten Bytestrings

Nachdem Sie die ASCII-Zeichen wieder in einen Bytestring decodiert haben, können Sie ihn entschlüsseln. Der verschlüsselte Bytestring enthält drei Abschnitte:

  • initialization_vector: 16 Byte.
  • ciphertext: Reihe von 20-Byte-Abschnitten
  • integrity_signature: 4 Byte.
{initialization_vector (16 bytes)}{ciphertext (20-byte sections)}{integrity_signature (4 bytes)}

Das Byte-Array ciphertext ist in mehrere 20-Byte-Abschnitte unterteilt, mit der Ausnahme, dass der letzte Abschnitt zwischen 1 und einschließlich 20 Byte enthalten kann. Für jeden Abschnitt der ursprünglichen byte_array wird der entsprechende 20-Byte-ciphertext so generiert:

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

wobei || die Verkettung ist.

Definitionen

Variable Details
initialization_vector 16 Byte: für die Impression eindeutig.
encryption_key 32 Byte werden bei der Kontoeinrichtung angegeben.
integrity_key 32 Byte werden bei der Kontoeinrichtung angegeben.
byte_array Ein serialisiertes ExtraTagData-Objekt in 20-Byte-Abschnitten.
counter_bytes Bytewert mit der Ordinalzahl des Abschnitts, siehe unten.
final_message Gesamtbyte-Array, das über das %%EXTRA_TAG_DATA%%-Makro gesendet wurde (ohne WebSafeBase64-Codierung).
Operatoren Details
hmac(key, data) SHA-1-HMAC, mit key zum Verschlüsseln von data
a || b Der String a ist mit dem String b verkettet.

Zählerbyte berechnen

counter_bytes gibt die Reihenfolge der 20-Byte-Abschnitte von ciphertext an. Der letzte Abschnitt kann zwischen 1 und 20 Byte (einschließlich) enthalten. Um counter_bytes beim Ausführen der Funktion hmac() mit dem richtigen Wert zu füllen, zählen Sie die 20-Byte-Abschnitte (einschließlich des Rests) und verwenden Sie die folgende Referenztabelle:

Abschnittsnummer counter_bytes Wert
0 Keine
1 ... 256 1 Byte. Der Wert erhöht sich sequenziell von 0 bis 255.
257 ... 512 2 Byte. Der Wert des ersten Bytes ist 0, der Wert des zweiten Bytes erhöht sich sequenziell von 0 auf 255.
513 ... 768 3 Byte. Der Wert der ersten beiden Byte ist 0, der Wert des letzten Byte wird sequenziell von 0 auf 255 erhöht.

Nach oben

Verschlüsselungsschema

Das Verschlüsselungsschema basiert auf demselben Schema, das auch für die Entschlüsselung des hyperlokalen Targeting-Signals verwendet wird.

  1. Serialisierung: Eine Instanz des ExtraTagData-Objekts, wie im Protokollpuffer definiert, wird zuerst über SerializeAsString() zu einem Byte-Array serialisiert.

  2. Verschlüsselung: Das Byte-Array wird dann mit einem benutzerdefinierten Verschlüsselungsschema verschlüsselt, um den Größen-Overhead zu minimieren und gleichzeitig eine angemessene Sicherheit zu gewährleisten. Das Verschlüsselungsschema verwendet einen verschlüsselten HMAC-Algorithmus, um ein geheimes Pad basierend auf dem initialization_vector zu generieren, das für das Impressionsereignis eindeutig ist.

Pseudocode für Verschlüsselung

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

Entschlüsselungsmethode

Der Entschlüsselungscode muss 1) den Protokollpuffer mit dem Verschlüsselungsschlüssel entschlüsseln und 2) die Integritätsbits mit dem Integritätsschlüssel verifizieren. Die Schlüssel erhalten Sie bei der Kontoeinrichtung. Für die Strukturierung Ihrer Implementierung gibt es keine Einschränkungen. In den meisten Fällen sollten Sie den Beispielcode an Ihre Anforderungen anpassen können.

  1. Eingabefeld generieren: HMAC(encryption_key, initialization_vector || counter_bytes)
  2. XOR: Verwenden Sie dieses Ergebnis und <xor> mit dem Geheimtext, um die Verschlüsselung umzukehren.
  3. Überprüfen: Die Integritätssignatur übergibt 4 Byte von HMAC(integrity_key, byte_array || initialization_vector)

Entschlüsselungs-Pseudocode

// 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++-Beispielcode

Sie enthält eine Schlüsselfunktion aus unserem vollständigen Entschlüsselungsbeispielcode.

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

Daten aus dem Werbenetzwerk-Protokollpuffer abrufen

Nachdem Sie die in %%EXTRA_TAG_DATA%% übergebenen Daten decodiert und entschlüsselt haben, können Sie den Protokollpuffer deserialisieren und die Werbetreibenden-ID für das Targeting abrufen.

Weitere Informationen zu Protokollpuffern finden Sie in unserer Dokumentation.

Definition

Unser Werbenetzwerk-Protokollzwischenspeicher ist wie folgt definiert:

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

Sie müssen ihn mit ParseFromString() deserialisieren, wie in der C++-Protokollpufferdokumentation beschrieben.

Weitere Informationen zu den Feldern hashed_idfa für Android und advertising_id finden Sie unter Werbe-ID entschlüsseln und Targeting auf Inventar in mobilen Apps mit IDFA.

Java-Bibliothek

Anstatt die Kryptoalgorithmen zum Codieren und Decodieren der Werbetreibenden-IDs für Werbenetzwerke zu implementieren, können Sie DoubleClickCrypto.java verwenden. Weitere Informationen finden Sie unter Kryptografie.