Configurar o IP fornecido pelo publisher

Os proxies de privacidade do navegador e as configurações de proteção de IP podem mascarar os endereços IP do cliente em solicitações de anúncios de terceiros para o Google Ad Manager. Os endereços IP mascarados podem reduzir a precisão da segmentação geográfica e a relevância do anúncio. Como o servidor da Web ou a rede de fornecimento de conteúdo (CDN) interage com o usuário em um contexto próprio, seu servidor recebe o endereço IP não mascarado do usuário.

O recurso de IP fornecido pelo publisher permite capturar o endereço IPv4 ou IPv6 não mascarado do usuário no seu servidor, criptografar o endereço usando a criptografia de chave pública híbrida (HPKE, na sigla em inglês) e transmitir o payload criptografado para a Tag de Publisher do Google (GPT) no cliente.

Esta página aborda a integração do IP fornecido pelo publisher com a GPT.

Implementar o fluxo de trabalho do lado do servidor

Para evitar a exposição do endereço IP em texto simples do usuário a scripts de terceiros no navegador, siga estas etapas no servidor da Web ou no worker de borda da CDN:

  • Realizar toda a captura de IP.
  • Busque a chave pública.
  • Realize a criptografia HPKE.

Capturar o endereço IP do cliente

Extraia o endereço IPv4 ou IPv6 não mascarado do cliente da solicitação de página recebida no servidor da Web ou na CDN:

  • Conexões diretas: leia o endereço do soquete remoto da conexão HTTP recebida.
  • Atrás de uma CDN, um balanceador de carga ou um proxy reverso: leia o cabeçalho de IP do cliente do proxy de borda confiável, como o cabeçalho X-Forwarded-For ou um cabeçalho de IP do cliente específico da CDN. Confie apenas nos cabeçalhos de encaminhamento que sua infraestrutura de proxy adiciona ou substitui. Quando o cabeçalho X-Forwarded-For contém vários endereços separados por vírgulas, selecione o IP do cliente que o proxy de borda confiável adicionou.

O Ad Manager aceita os seguintes endereços:

  • IPv4, por exemplo, 203.0.113.42.
  • IPv6, por exemplo, 2001:db8::8a2e:370:7334.

Transmita apenas a string do endereço IP, sem números de porta, colchetes, prefixos CIDR ou espaços em branco ao redor.

Buscar e armazenar em cache a chave pública

O Google publica a chave de criptografia HPKE ativa como um conjunto de chaves da Web JSON (JWKS, na sigla em inglês) RFC 8037 no seguinte endpoint:

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

Uma solicitação GET para esse endpoint retorna um objeto JSON, cuja matriz keys contém um único objeto de chave ativa. Use a chave na propriedade x desse objeto para criptografar o endereço IP do cliente.

O exemplo a seguir busca a chave pública e extrai a propriedade x em caso de sucesso, além de gerar uma exceção em caso de falha:

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

Se for bem-sucedida, a resposta vai incluir a matriz keys que contém um objeto de chave com a propriedade x:

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

A tabela a seguir descreve as propriedades do objeto de chave na matriz keys:

Propriedade Descrição
crv Identificador de curva elíptica. Sempre X25519.
kid Identificador de chave codificado em base64url sem preenchimento, que muda quando o Google alterna a chave.
kty Tipo de chave. Sempre OKP (par de chaves de octeto).
x Chave pública X25519 bruta de 32 bytes codificada em base64url sem padding usada para criptografia.

Armazene em cache a chave pública buscada na memória do servidor e atualize-a pelo menos uma vez a cada 24 horas ou antes, de acordo com o cabeçalho de resposta HTTP Cache-Control. Não busque a chave pública em cada solicitação de página recebida. O Google retém as chaves privadas alternadas. Os payloads criptografados com uma chave pública armazenada em cache nas últimas 24 horas continuam sendo descriptografados sem interrupção durante a rotação de chaves.

Criptografar o endereço IP

Criptografe a representação de bytes UTF-8 da string de endereço IP do cliente usando HPKE (RFC 9180) e a chave pública X25519 de 32 bytes decodificada, x, recuperada em Buscar e armazenar em cache a chave pública.

O exemplo a seguir criptografa uma string de endereço IP:

Java

Este exemplo usa o Tink (com.google.crypto.tink:tink 1.16.0 ou mais recente):

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

Este exemplo usa 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

Este exemplo usa @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

Este exemplo usa @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',
  );
}

Configure sua biblioteca HPKE com o seguinte conjunto de criptografia e parâmetros:

Parâmetro Valor
Mecanismo de encapsulamento de chaves (KEM) DHKEM(X25519, HKDF-SHA256) (0x0020)
Função de derivação de chaves (KDF) HKDF-SHA256 (0x0001)
Criptografia autenticada com dados associados (AEAD, na sigla em inglês) AES-256-GCM (0x0002)
Modo Base (0x00)
Informações do aplicativo (info) Vazio ("")
Dados associados (aad) Vazio ("")

Formate a saída criptografada como a concatenação bruta da chave pública temporária encapsulada de 32 bytes, enc, e o texto criptografado de AEAD, incluindo a tag de autenticação GCM de 16 bytes, sem Tink ou prefixo de cabeçalho personalizado. Codifique o resultado como uma string base64url sem preenchimento.

Criptografe cada payload como uma mensagem HPKE independente de disparo único com um novo par de chaves efêmeras. Se a biblioteca HPKE usar um contexto de remetente com estado, crie um novo contexto para cada criptografia em vez de reutilizá-lo em várias mensagens. Criptografe o endereço IP do cliente por solicitação de página ou armazene em cache a string de texto criptografado base64url resultante por endereço IP por um curto período de vida útil (TTL).

Implementar o fluxo de trabalho do lado do cliente

Depois que o servidor gerar o payload de IP base64url criptografado, transmita-o para a GPT no cliente antes de solicitar anúncios.

Transmitir o IP criptografado para a GPT

Transmita a string de IP criptografada para o método googletag.setConfig usando a propriedade ipAddress em PageSettingsConfig.

O exemplo a seguir insere o payload de IP criptografado gerado pelo servidor no método googletag.setConfig antes de ativar os serviços e mostrar um espaço do anúncio:

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

Substitua ENCRYPTED_IP_PAYLOAD pela string codificada em base64url sem preenchimento que seu servidor produz.

Para limpar um endereço IP configurado anteriormente na página, defina a propriedade ipAddress como o valor null:

googletag.setConfig({ ipAddress: null });

Verificar a integração

Para verificar se a GPT envia o payload de IP criptografado e se o Ad Manager o descriptografa, use as ferramentas para desenvolvedores do seu navegador e o Console do Publisher do Google:

Ferramentas para desenvolvedores do navegador

  1. Abra as ferramentas para desenvolvedores do navegador.
  2. Selecione a guia Rede.
  3. Filtre as solicitações de anúncio para /gampad/ads.
  4. Confirme se o URL da solicitação inclui um parâmetro de consulta ips não vazio. Como a GPT codifica ainda mais o valor ipAddress antes de enviar a solicitação de anúncio, o parâmetro ips não corresponde exatamente à string criptografada.

Console do Editor do Google

  1. Para abrir o console, siga as etapas no guia do Console do Publisher do Google.
  2. Selecione a guia Solicitação de página.
  3. Confirme se nenhum aviso de ipAddress aparece. Se houver um problema com o payload, o console vai registrar um dos seguintes avisos:

    • Se o valor transmitido para a propriedade ipAddress não for uma string base64 ou base64url válida entre 67 e 124 caracteres, a GPT vai ignorar o valor, omitir o parâmetro ips da solicitação de anúncio e registrar o seguinte aviso:

      Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.
      
    • Se o Ad Manager não conseguir descriptografar o payload de IP fornecido ou se o texto simples descriptografado não for um endereço IPv4 ou IPv6 válido, o console vai registrar o seguinte aviso e usar o endereço IP da solicitação de rede:

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