Vom Publisher bereitgestellte IP-Adresse einrichten

Browser-Datenschutz-Proxys und IP-Schutzeinstellungen können Client-IP-Adressen bei Drittanbieter-Anzeigenanfragen an Google Ad Manager maskieren. Maskierte IP-Adressen können die Genauigkeit des geografischen Targetings und die Anzeigenrelevanz beeinträchtigen. Da Ihr Webserver oder Content Delivery Network (CDN) mit dem Nutzer in einem Erstanbieterkontext interagiert, erhält Ihr Server die unmaskierte IP-Adresse des Nutzers.

Mit der Funktion „Vom Publisher bereitgestellte IP-Adresse“ können Sie die unmaskierte IPv4- oder IPv6-Adresse des Nutzers auf Ihrem Server erfassen, die Adresse mit Hybrid Public Key Encryption (HPKE) verschlüsseln und die verschlüsselte Nutzlast an das Google Publisher-Tag (GPT) auf dem Client übergeben.

Auf dieser Seite wird die Integration von vom Publisher bereitgestellten IP-Adressen in das GPT beschrieben.

Serverseitigen Workflow implementieren

Damit die Klartext-IP-Adresse des Nutzers nicht für Drittanbieterskripts im Browser sichtbar ist, führen Sie die folgenden Schritte auf Ihrem Webserver oder CDN-Edge-Worker aus:

  • Führen Sie alle IP-Erfassungen durch.
  • Rufen Sie den öffentlichen Schlüssel ab.
  • HPKE-Verschlüsselung durchführen

Client-IP-Adresse erfassen

Extrahieren Sie die unmaskierte IPv4- oder IPv6-Adresse des Clients aus der eingehenden Seitenanfrage auf Ihrem Webserver oder CDN:

  • Direkte Verbindungen: Lesen Sie die Remote-Socket-Adresse der eingehenden HTTP-Verbindung.
  • Hinter einem CDN, Load Balancer oder Reverse-Proxy: Lesen Sie den Client-IP-Header von Ihrem vertrauenswürdigen Edge-Proxy, z. B. den X-Forwarded-For-Header oder einen CDN-spezifischen Client-IP-Header. Vertrauen Sie nur Weiterleitungsheadern, die von Ihrer Proxy-Infrastruktur hinzugefügt oder überschrieben werden. Wenn der Header X-Forwarded-For mehrere kommagetrennte Adressen enthält, wählen Sie die Client-IP-Adresse aus, die vom vertrauenswürdigen Edge-Proxy hinzugefügt wurde.

In Ad Manager werden die folgenden Adressen unterstützt:

  • IPv4, z. B. 203.0.113.42.
  • IPv6, z. B. 2001:db8::8a2e:370:7334.

Übergeben Sie nur den reinen IP-Adressstring ohne Portnummern, Klammern, CIDR-Präfixe oder umgebende Leerzeichen.

Öffentlichen Schlüssel abrufen und im Cache speichern

Google veröffentlicht den aktiven HPKE-Verschlüsselungsschlüssel als JSON Web Key Set (JWKS) gemäß RFC 8037 am folgenden Endpunkt:

https://www.gstatic.com/real-ip/ip_enc_key.json

Eine GET-Anfrage an diesen Endpunkt gibt ein JSON-Objekt zurück, dessen keys-Array ein einzelnes aktives Schlüsselobjekt enthält. Verwenden Sie den Schlüssel im Attribut x dieses Objekts, um die Client-IP-Adresse zu verschlüsseln.

Im folgenden Beispiel wird der öffentliche Schlüssel abgerufen und das Attribut x bei Erfolg extrahiert. Bei einem Fehler wird eine Ausnahme ausgelöst:

Java

private static final String KEY_ENDPOINT_URL = "https://www.gstatic.com/real-ip/ip_enc_key.json";

/**
 * Fetches the active X25519 public key from Google's key endpoint.
 *
 * <p>In production, cache the returned public key in memory and refresh it at least once every 24
 * hours rather than fetching it on every incoming request.
 *
 * @return The unpadded Base64URL-encoded 32-byte X25519 public key ('x').
 */
public static String fetchPublicKey() throws IOException, InterruptedException {
  return fetchPublicKey(KEY_ENDPOINT_URL);
}

public static String fetchPublicKey(String endpointUrl) throws IOException, InterruptedException {
  HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
  HttpRequest request =
      HttpRequest.newBuilder()
          .uri(URI.create(endpointUrl))
          .timeout(Duration.ofSeconds(5))
          .GET()
          .build();
  HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
  if (response.statusCode() < 200 || response.statusCode() >= 300) {
    throw new IOException("Failed to fetch public key, HTTP status: " + response.statusCode());
  }
  JsonObject json = JsonParser.parseString(response.body()).getAsJsonObject();
  JsonArray keys = json.getAsJsonArray("keys");
  if (keys == null || keys.isEmpty()) {
    throw new IOException("No keys found in response from " + endpointUrl);
  }
  JsonObject keyObj = keys.get(0).getAsJsonObject();
  if (!keyObj.has("x")) {
    throw new IOException("Key payload missing 'x' property: " + keyObj);
  }
  return keyObj.get("x").getAsString();
}

Python

KEY_ENDPOINT_URL = "https://www.gstatic.com/real-ip/ip_enc_key.json"


def fetch_public_key(url: str = KEY_ENDPOINT_URL) -> str:
  """Fetches the active X25519 public key from Google's key endpoint.

  In production, cache the returned public key in memory and refresh it at
  least once every 24 hours rather than fetching it on every incoming request.

  Args:
    url: URL of the public key endpoint.

  Returns:
    The unpadded Base64URL-encoded 32-byte X25519 public key ('x').
  """
  with urllib.request.urlopen(url, timeout=5) as response:
    data = json.loads(response.read().decode("utf-8"))
  keys = data.get("keys", [])
  if not keys:
    raise ValueError(f"No keys found in response from {url}")
  key_payload = keys[0].get("x")
  if not key_payload:
    raise ValueError(f"Key payload missing 'x' property: {keys[0]}")
  return key_payload


JavaScript

const KEY_ENDPOINT_URL = 'https://www.gstatic.com/real-ip/ip_enc_key.json';

/**
 * Fetches the active X25519 public key from Google's key endpoint.
 *
 * In production, cache the returned public key in memory and refresh it at
 * least once every 24 hours rather than fetching it on every incoming request.
 *
 * @param {string=} url URL of the public key endpoint.
 * @return {!Promise<string>} The unpadded Base64URL-encoded 32-byte X25519
 *     public key ('x').
 */
async function fetchPublicKey(url = KEY_ENDPOINT_URL) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(
      `Failed to fetch public key, HTTP status: ${response.status}`,
    );
  }
  const data = await response.json();
  if (!data.keys || data.keys.length === 0) {
    throw new Error(`No keys found in response from ${url}`);
  }
  const keyPayload = data.keys[0].x;
  if (!keyPayload) {
    throw new Error(
      `Key payload missing 'x' property: ${JSON.stringify(data.keys[0])}`,
    );
  }
  return keyPayload;
}

TypeScript

const KEY_ENDPOINT_URL = 'https://www.gstatic.com/real-ip/ip_enc_key.json';

interface JwkKey {
  kty?: string;
  crv?: string;
  x?: string;
  kid?: string;
}

interface KeyEndpointResponse {
  keys?: JwkKey[];
}

/**
 * Fetches the active X25519 public key from Google's key endpoint.
 *
 * In production, cache the returned public key in memory and refresh it at
 * least once every 24 hours rather than fetching it on every incoming request.
 *
 * @param url URL of the public key endpoint.
 * @return The unpadded Base64URL-encoded 32-byte X25519 public key ('x').
 */
export async function fetchPublicKey(
  url: string = KEY_ENDPOINT_URL,
): Promise<string> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(
      `Failed to fetch public key, HTTP status: ${response.status}`,
    );
  }
  const data = (await response.json()) as KeyEndpointResponse;
  if (!data.keys || data.keys.length === 0) {
    throw new Error(`No keys found in response from ${url}`);
  }
  const keyPayload = data.keys[0].x;
  if (!keyPayload) {
    throw new Error(
      `Key payload missing 'x' property: ${JSON.stringify(data.keys[0])}`,
    );
  }
  return keyPayload;
}

Wenn der Vorgang erfolgreich war, enthält die Antwort das Array keys mit einem Schlüsselobjekt mit dem Attribut x:

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "X25519",
      "x": "7bws5TIHOUrcJNofEubW3DPW8zR9EtjFNLm_Grv98TA",
      "kid": "EZMLFw"
    }
  ]
}

In der folgenden Tabelle werden die Attribute des Schlüsselobjekts im Array keys beschrieben:

Attribut Beschreibung
crv ID der elliptischen Kurve. Immer X25519.
kid Ungepolsterte base64url-codierte Schlüssel-ID, die sich ändert, wenn Google den Schlüssel rotiert.
kty Schlüsseltyp. Immer OKP (Oktett-Schlüsselpaar).
x Nicht aufgefüllter, base64url-codierter, 32 Byte langer, unbearbeiteter öffentlicher X25519-Schlüssel, der für die Verschlüsselung verwendet wird.

Speichern Sie den abgerufenen öffentlichen Schlüssel im Cache auf Ihrem Server und aktualisieren Sie den Schlüssel mindestens einmal alle 24 Stunden oder früher, je nach HTTP-Cache-Control-Antwortheader. Rufen Sie den öffentlichen Schlüssel nicht bei jeder eingehenden Seitenanfrage ab. Google bewahrt rotierte private Schlüssel auf. Nutzlasten, die mit einem öffentlichen Schlüssel verschlüsselt wurden, der in den letzten 24 Stunden im Cache gespeichert wurde, werden während der Schlüsselrotation ohne Unterbrechung entschlüsselt.

IP-Adresse verschlüsseln

Verschlüsseln Sie die UTF-8-Byte-Darstellung des IP-Adressstrings des Clients mit HPKE (RFC 9180) und dem decodierten öffentlichen 32-Byte-X25519-Schlüssel x, der unter Öffentlichen Schlüssel abrufen und im Cache speichern abgerufen wurde.

Im folgenden Beispiel wird ein IP-Adressstring verschlüsselt:

Java

In diesem Beispiel wird Tink (com.google.crypto.tink:tink 1.16.0 oder höher) verwendet:

static {
  try {
    HybridConfig.register();
  } catch (GeneralSecurityException e) {
    throw new ExceptionInInitializerError(e);
  }
}

/**
 * Encrypts an IP address using HPKE (DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-256-GCM).
 *
 * @param pubXB64Url Base64URL-encoded 32-byte X25519 public key ('x').
 * @param ipStr Plaintext IPv4 or IPv6 address string (for example, "203.0.113.42").
 * @return Unpadded Base64URL-encoded payload (enc || ciphertext).
 */
@AccessesPartialKey
public static String encryptIp(String pubXB64Url, String ipStr) throws GeneralSecurityException {
  // 1. Decode the 32-byte X25519 public key 'x' from Base64URL.
  byte[] pubBytes = Base64.getUrlDecoder().decode(pubXB64Url);
  if (pubBytes.length != 32) {
    throw new IllegalArgumentException(
        "Public key must be exactly 32 bytes, got " + pubBytes.length);
  }

  // 2. Configure HPKE parameters (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256,
  //    AES_256_GCM, NO_PREFIX).
  HpkeParameters parameters =
      HpkeParameters.builder()
          .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
          .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
          .setAeadId(HpkeParameters.AeadId.AES_256_GCM)
          .setVariant(HpkeParameters.Variant.NO_PREFIX)
          .build();

  HpkePublicKey publicKey =
      HpkePublicKey.create(parameters, Bytes.copyFrom(pubBytes), /* idRequirement= */ null);

  // 3. Encrypt the UTF-8 IP address bytes. With Variant.NO_PREFIX, Tink
  //    outputs raw (enc || ciphertext_with_tag).
  KeysetHandle handle =
      KeysetHandle.newBuilder()
          .addEntry(KeysetHandle.importKey(publicKey).withRandomId().makePrimary())
          .build();

  HybridEncrypt encryptor = handle.getPrimitive(RegistryConfiguration.get(), HybridEncrypt.class);
  byte[] payload =
      encryptor.encrypt(ipStr.getBytes(StandardCharsets.UTF_8), /* contextInfo= */ new byte[0]);

  // 4. Return unpadded Base64URL-encoded payload.
  return Base64.getUrlEncoder().withoutPadding().encodeToString(payload);
}

Python

In diesem Beispiel wird pyhpke verwendet:

def encrypt_ip(pub_x_b64url: str, ip_str: str) -> str:
  """Encrypts an IP address using HPKE (RFC 9180).

  Args:
    pub_x_b64url: Base64URL-encoded 32-byte X25519 public key ('x').
    ip_str: Plaintext IPv4 or IPv6 address string (for example, "203.0.113.42").

  Returns:
    Unpadded Base64URL-encoded payload (enc || ciphertext).
  """
  # 1. Decode the 32-byte X25519 public key 'x' from Base64URL.
  pub_bytes = base64.urlsafe_b64decode(
      pub_x_b64url + "=" * (-len(pub_x_b64url) % 4)
  )
  if len(pub_bytes) != 32:
    raise ValueError(
        f"Public key must be exactly 32 bytes, got {len(pub_bytes)}"
    )

  # 2. Configure HPKE cipher suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256,
  #    AES256_GCM).
  suite = CipherSuite.new(
      KEMId.DHKEM_X25519_HKDF_SHA256, KDFId.HKDF_SHA256, AEADId.AES256_GCM
  )
  public_key = suite.kem.deserialize_public_key(pub_bytes)

  # 3. Create a fresh sender context per encryption (you may cache 'suite' and
  #    'public_key', but never reuse 'sender' across multiple seal() calls) and
  #    package (enc || ciphertext).
  enc, sender = suite.create_sender_context(public_key)
  ciphertext = sender.seal(ip_str.encode("utf-8"))

  # 4. Return unpadded Base64URL-encoded payload.
  return base64.urlsafe_b64encode(enc + ciphertext).decode("utf-8").rstrip("=")


JavaScript

In diesem Beispiel wird @hpke/core verwendet:

/**
 * Encrypts an IP address using HPKE (RFC 9180).
 *
 * @param {string} pubXB64Url Base64URL-encoded 32-byte X25519 public key ('x').
 * @param {string} ipStr Plaintext IPv4 or IPv6 address string (for example,
 *     "203.0.113.42").
 * @return {!Promise<string>} Unpadded Base64URL-encoded payload
 *     (enc || ciphertext).
 */
async function encryptIp(pubXB64Url, ipStr) {
  // 1. Decode the 32-byte X25519 public key 'x' from Base64URL.
  const pubBytes = Buffer.from(pubXB64Url, 'base64url');
  if (pubBytes.length !== 32) {
    throw new Error(
      `Public key must be exactly 32 bytes, got ${pubBytes.length}`,
    );
  }

  // 2. Configure HPKE cipher suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256,
  //    AES_256_GCM).
  const suite = new CipherSuite({
    kem: new DhkemX25519HkdfSha256(),
    kdf: new HkdfSha256(),
    aead: new Aes256Gcm(),
  });
  const rawPubKey = new Uint8Array(pubBytes).buffer;
  const publicKey = await suite.kem.importKey('raw', rawPubKey, true);

  // 3. Encrypt the UTF-8 IP address bytes and package (enc || ciphertext).
  const plaintext = new Uint8Array(Buffer.from(ipStr, 'utf8')).buffer;
  const {enc, ct} = await suite.seal(
    {recipientPublicKey: publicKey},
    plaintext,
  );

  // 4. Return unpadded Base64URL-encoded payload.
  return Buffer.concat([Buffer.from(enc), Buffer.from(ct)]).toString(
    'base64url',
  );
}

TypeScript

In diesem Beispiel wird @hpke/core verwendet:

/**
 * Encrypts an IP address using HPKE (RFC 9180).
 *
 * @param pubXB64Url Base64URL-encoded 32-byte X25519 public key ('x').
 * @param ipStr Plaintext IPv4 or IPv6 address string (for example,
 *     "203.0.113.42").
 * @return Unpadded Base64URL-encoded payload (enc || ciphertext).
 */
export async function encryptIp(
  pubXB64Url: string,
  ipStr: string,
): Promise<string> {
  // 1. Decode the 32-byte X25519 public key 'x' from Base64URL.
  const pubBytes = Buffer.from(pubXB64Url, 'base64url');
  if (pubBytes.length !== 32) {
    throw new Error(
      `Public key must be exactly 32 bytes, got ${pubBytes.length}`,
    );
  }

  // 2. Configure HPKE cipher suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256,
  //    AES_256_GCM).
  const suite = new CipherSuite({
    kem: new DhkemX25519HkdfSha256(),
    kdf: new HkdfSha256(),
    aead: new Aes256Gcm(),
  });
  const rawPubKey = new Uint8Array(pubBytes).buffer;
  const publicKey = await suite.kem.importKey('raw', rawPubKey, true);

  // 3. Encrypt the UTF-8 IP address bytes and package (enc || ciphertext).
  const plaintext = new Uint8Array(Buffer.from(ipStr, 'utf8')).buffer;
  const {enc, ct} = await suite.seal(
    {recipientPublicKey: publicKey},
    plaintext,
  );

  // 4. Return unpadded Base64URL-encoded payload.
  return Buffer.concat([Buffer.from(enc), Buffer.from(ct)]).toString(
    'base64url',
  );
}

Konfigurieren Sie Ihre HPKE-Bibliothek mit der folgenden Cipher Suite und den folgenden Parametern:

Parameter Wert
Key Encapsulation Mechanism (KEM) DHKEM(X25519, HKDF-SHA256) (0x0020)
Schlüsselableitungsfunktion (Key Derivation Function, KDF) HKDF-SHA256 (0x0001)
Authentifizierte Verschlüsselung mit verknüpften Daten (Authenticated Encryption with Associated Data, AEAD) AES-256-GCM (0x0002)
Modus Basis (0x00)
Anwendungsinformationen (info) Leer ("")
Verknüpfte Daten (aad) Leer ("")

Formatiere die verschlüsselte Ausgabe als Rohverkettung des gekapselten flüchtigen öffentlichen 32-Byte-Schlüssels enc und des AEAD-Chiffretexts, einschließlich des 16-Byte-GCM-Authentifizierungs-Tags, ohne Tink- oder benutzerdefiniertes Header-Präfix. Codieren Sie das Ergebnis als base64url-String ohne Padding.

Verschlüsseln Sie jede Nutzlast als eigenständige HPKE-Nachricht mit einem neuen flüchtigen Schlüsselpaar. Wenn Ihre HPKE-Bibliothek einen zustandsorientierten Senderkontext verwendet, erstellen Sie für jede Verschlüsselung einen neuen Kontext, anstatt ihn für mehrere Nachrichten wiederzuverwenden. Verschlüsseln Sie die Client-IP-Adresse pro Seitenanfrage oder speichern Sie den resultierenden Base64url-Chiffretextstring pro IP-Adresse für eine kurze Gültigkeitsdauer (TTL) im Cache.

Clientseitigen Workflow implementieren

Nachdem Ihr Server die verschlüsselte base64url-IP-Nutzlast generiert hat, übergeben Sie die Nutzlast an GPT auf dem Client, bevor Sie Anzeigen anfordern.

Verschlüsselte IP-Adresse an GPT übergeben

Übergeben Sie den verschlüsselten IP-String mit dem Attribut ipAddress für PageSettingsConfig an die Methode googletag.setConfig.

Im folgenden Beispiel wird die vom Server generierte verschlüsselte IP-Nutzlast in die Methode googletag.setConfig eingefügt, bevor Dienste aktiviert und eine Anzeigenfläche angezeigt wird:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Publisher-provided IP example</title>
    <script src="https://securepubads.g.doubleclick.net/tag/js/gpt.js" crossorigin="anonymous" async></script>
    <script>
      window.googletag = window.googletag || {cmd: []};

      // Inject the base64url-encoded encrypted IP generated by your server.
      const encryptedClientIp = 'ENCRYPTED_IP_PAYLOAD';

      googletag.cmd.push(function() {
        // Configure the encrypted IP before enabling services.
        googletag.setConfig({
          ipAddress: encryptedClientIp,
        });

        googletag
            .defineSlot('/6355419/Travel/Europe/France/Paris', [300, 250], 'banner-ad')
            .addService(googletag.pubads());
        googletag.enableServices();
      });
    </script>
  </head>
  <body>
    <div id="banner-ad" style="width: 300px; height: 250px;"></div>
    <script>
      googletag.cmd.push(function() {
        googletag.display('banner-ad');
      });
    </script>
  </body>
</html>

Ersetzen Sie ENCRYPTED_IP_PAYLOAD durch den von Ihrem Server erzeugten base64url-codierten String ohne Padding.

Wenn Sie eine zuvor konfigurierte IP-Adresse auf der Seite löschen möchten, setzen Sie das Attribut ipAddress auf den Wert null:

googletag.setConfig({ ipAddress: null });

Einbindung verifizieren

Wenn Sie prüfen möchten, ob GPT die verschlüsselte IP-Nutzlast sendet und Ad Manager sie entschlüsselt, verwenden Sie die Entwicklertools Ihres Browsers und die Google Publisher Console:

Browserbasierte Entwicklertools

  1. Öffnen Sie die Entwicklertools Ihres Browsers.
  2. Wählen Sie den Tab Network (Netzwerk) aus.
  3. Filtern Sie nach Anzeigenanfragen an /gampad/ads.
  4. Prüfen Sie, ob die Anfrage-URL einen nicht leeren Suchparameter ips enthält. Da GPT den Wert ipAddress vor dem Senden der Anzeigenanfrage weiter codiert, stimmt der Parameter ips nicht genau mit Ihrem verschlüsselten String überein.

Google Publisher Console

  1. Folgen Sie der Anleitung im Leitfaden zur Google Publisher Console, um die Console zu öffnen.
  2. Wählen Sie den Tab Seitenanfrage aus.
  3. Prüfen Sie, ob keine ipAddress-Warnungen angezeigt werden. Wenn ein Problem mit der Nutzlast vorliegt, wird in der Konsole eine der folgenden Warnungen protokolliert:

    • Wenn der an das Attribut ipAddress übergebene Wert kein gültiger base64- oder base64url-String mit einer Länge zwischen 67 und 124 Zeichen ist, ignoriert GPT den Wert, lässt den Parameter ips in der Anzeigenanfrage weg und protokolliert die folgende Warnung:

      Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.
      
    • Wenn Ad Manager die bereitgestellte IP-Nutzlast nicht entschlüsseln kann oder der entschlüsselte Klartext keine gültige IPv4- oder IPv6-Adresse ist, wird in der Konsole die folgende Warnung protokolliert und auf die IP-Adresse der Netzwerkanfrage zurückgegriffen:

      Decryption failed on the provided encrypted client IP (supplied via googletag.setConfig({ipAddress: ...})).