Configura l'IP fornito dal publisher

I proxy per la privacy del browser e le impostazioni di protezione IP possono mascherare gli indirizzi IP client nelle richieste di annunci di terze parti a Google Ad Manager. Gli indirizzi IP mascherati possono ridurre l'accuratezza del targeting geografico e la pertinenza degli annunci. Poiché il tuo server web o la tua rete CDN (Content Delivery Network) interagisce con l'utente in un contesto proprietario, il tuo server riceve l'indirizzo IP in chiaro dell'utente.

La funzionalità IP fornito dal publisher ti consente di acquisire l'indirizzo IPv4 o IPv6 in chiaro dell'utente sul tuo server, criptarlo utilizzando la crittografia a chiave pubblica ibrida (HPKE) e trasmettere il payload criptato al Tag publisher di Google (GPT) sul client.

Questa pagina illustra l'integrazione dell'IP fornito dal publisher con GPT.

Implementa il workflow lato server

Per evitare di esporre l'indirizzo IP in testo non crittografato dell'utente a script di terze parti nel browser, segui questi passaggi sul server web o sull'edge worker CDN:

  • Esegui l'acquisizione di tutti gli IP.
  • Recupera la chiave pubblica.
  • Esegui la crittografia HPKE.

Acquisisci l'indirizzo IP client

Estrai l'indirizzo IPv4 o IPv6 in chiaro del client dalla richiesta di pagina in entrata sul tuo server web o CDN:

  • Connessioni dirette: leggi l'indirizzo del socket remoto della connessione HTTP in entrata.
  • Dietro una CDN, un bilanciatore del carico o un reverse proxy: leggi l'intestazione IP client dal proxy perimetrale attendibile, ad esempio l'intestazione X-Forwarded-For o un'intestazione IP client specifica della CDN. Considera attendibili solo le intestazioni di forwarding aggiunte o sostituite dalla tua infrastruttura proxy. Quando l'intestazione X-Forwarded-For contiene più indirizzi separati da virgole, seleziona l'IP client aggiunto dal proxy perimetrale attendibile.

Ad Manager supporta i seguenti indirizzi:

  • IPv4, ad esempio 203.0.113.42.
  • IPv6, ad esempio 2001:db8::8a2e:370:7334.

Passa solo la stringa dell'indirizzo IP, senza numeri di porta, parentesi, prefissi CIDR o spazi vuoti circostanti.

Recupera la chiave pubblica e memorizzala nella cache

Google pubblica la chiave di crittografia HPKE attiva come set di chiavi web JSON (JWKS) RFC 8037 nel seguente endpoint:

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

Una richiesta GET a questo endpoint restituisce un oggetto JSON, il cui array keys contiene un singolo oggetto chiave attivo. Utilizza la chiave nella proprietà x di quell'oggetto per criptare l'indirizzo IP client.

L'esempio seguente recupera la chiave pubblica ed estrae la proprietà x in caso di esito positivo, mentre genera un'eccezione in caso di esito negativo:

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

In caso di esito positivo, la risposta include l'array keys contenente un oggetto chiave con la proprietà x:

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

La tabella seguente descrive le proprietà dell'oggetto chiave nell'array keys:

Proprietà Descrizione
crv Identificatore della curva ellittica. Sempre X25519.
kid Identificatore della chiave con codifica base64url senza spaziatura interna, che cambia quando Google ruota la chiave.
kty Tipo di chiave. Sempre OKP (coppia di chiavi ottetto).
x Chiave pubblica X25519 non elaborata a 32 byte con codifica base64url senza spaziatura interna utilizzata per la crittografia.

Memorizza nella cache la chiave pubblica recuperata sul tuo server e aggiornala almeno una volta ogni 24 ore o prima, in base all'intestazione della risposta HTTP Cache-Control. Non recuperare la chiave pubblica a ogni richiesta di pagina in entrata. Google conserva le chiavi private ruotate. I payload criptati con una chiave pubblica memorizzata nella cache nelle 24 ore precedenti continuano a essere decriptati senza interruzioni durante la rotazione delle chiavi.

Cripta l'indirizzo IP

Esegui la crittografia della rappresentazione di byte UTF-8 della stringa dell'indirizzo IP del client utilizzando HPKE (RFC 9180) e la chiave pubblica X25519 decodificata a 32 byte, x, recuperata in Recupera e memorizza nella cache la chiave pubblica.

L'esempio seguente cripta una stringa di indirizzo IP:

Java

Questo esempio utilizza Tink (com.google.crypto.tink:tink 1.16.0 o versioni successive):

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

Questo esempio utilizza pyhpke:

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

Questo esempio utilizza @hpke/core:

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

Questo esempio utilizza @hpke/core:

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

Configura la libreria HPKE con la suite di crittografia e i parametri seguenti:

Parametro Valore
Key Encapsulation Mechanism (KEM) DHKEM(X25519, HKDF-SHA256) (0x0020)
Key Derivation Function (KDF) HKDF-SHA256 (0x0001)
Crittografia autenticata con dati associati (AEAD) AES-256-GCM (0x0002)
Modalità Base (0x00)
Informazioni sull'applicazione (info) Vuoto ("")
Dati associati (aad) Vuoto ("")

Formatta l'output criptato come concatenazione non elaborata della chiave pubblica temporanea incapsulata di 32 byte, enc, e del testo cifrato AEAD, incluso il tag di autenticazione GCM di 16 byte, senza Tink o prefisso di intestazione personalizzato. Codifica il risultato come stringa base64url senza spaziatura interna.

Cripta ogni payload come messaggio HPKE autonomo singolo con una nuova coppia di chiavi temporanee. Se la tua libreria HPKE utilizza un contesto del mittente stateful, crea un nuovo contesto per ogni crittografia anziché riutilizzarlo in più messaggi. Cripta l'indirizzo IP client per richiesta di pagina o memorizza nella cache la stringa di testo crittografato base64url risultante per indirizzo IP per un breve periodo di durata (TTL).

Implementa il flusso di lavoro lato client

Dopo che il server ha generato il payload IP base64url criptato, passalo a GPT sul client prima di richiedere gli annunci.

Passa l'IP criptato a GPT

Passa la stringa IP criptata al metodo googletag.setConfig utilizzando la proprietà ipAddress su PageSettingsConfig.

L'esempio seguente inserisce il payload IP crittografato generato dal server nel metodo googletag.setConfig prima di attivare i servizi abilitati e visualizzare un'area annunci:

<!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>

Sostituisci ENCRYPTED_IP_PAYLOAD con la stringa con codifica base64url senza spaziatura interna prodotta dal tuo server.

Per cancellare un indirizzo IP configurato in precedenza nella pagina, imposta la proprietà ipAddress sul valore null:

googletag.setConfig({ ipAddress: null });

Verifica l'integrazione

Per verificare che GPT invii il payload IP criptato e che Ad Manager lo decripti, utilizza gli strumenti per sviluppatori del browser e Google Publisher Console:

Strumenti per sviluppatori del browser

  1. Apri gli strumenti per sviluppatori del browser.
  2. Seleziona la scheda Rete.
  3. Filtra le richieste di annunci per /gampad/ads.
  4. Verifica che l'URL della richiesta includa un parametro di query ips non vuoto. Poiché GPT codifica ulteriormente il valore ipAddress prima di inviare la richiesta di annuncio, il parametro ips non corrisponde esattamente alla stringa criptata.

Google Publisher Console

  1. Per aprire la console, segui i passaggi descritti nella guida di Google Publisher Console.
  2. Seleziona la scheda Richiesta di pagina.
  3. Verifica che non vengano visualizzati avvisi ipAddress. Se si verifica un problema con il payload, la console registra uno dei seguenti avvisi:

    • Se il valore passato alla proprietà ipAddress non è una stringa base64 o base64url valida compresa tra 67 e 124 caratteri, GPT ignora il valore, omette il parametro ips dalla richiesta di annuncio e registra il seguente avviso:

      Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.
      
    • Se Ad Manager non riesce a decriptare il payload IP fornito o se il testo non criptato decriptato non è un indirizzo IPv4 o IPv6 valido, la console registra il seguente avviso e utilizza l'indirizzo IP della richiesta di rete:

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