Yayıncı tarafından sağlanan IP'yi ayarlama

Tarayıcı gizlilik proxy'leri ve IP koruma ayarları, Google Ad Manager'a gönderilen üçüncü taraf reklam isteklerinde istemci IP adreslerini maskeleyebilir. Maskelenmiş IP adresleri, coğrafi hedefleme doğruluğunu ve reklam alaka düzeyini azaltabilir. Web sunucunuz veya içerik yayınlama ağınız (CDN), kullanıcıyla birinci taraf bağlamında etkileşim kurduğundan sunucunuz kullanıcının maskelenmemiş IP adresini alır.

Yayıncı tarafından sağlanan IP özelliği, kullanıcının maskelenmemiş IPv4 veya IPv6 adresini sunucunuzda yakalamanıza, Karma Ortak Anahtar Şifreleme (HPKE) kullanarak adresi şifrelemenize ve şifrelenmiş yükü istemcideki Google Yayıncı Etiketi'ne (GPT) iletmenize olanak tanır.

Bu sayfada, yayıncı tarafından sağlanan IP'nin GPT ile entegrasyonu ele alınmaktadır.

Sunucu tarafı iş akışını uygulama

Kullanıcının düz metin IP adresinin tarayıcıdaki üçüncü taraf komut dosyalarına gösterilmesini önlemek için web sunucunuzda veya CDN uç çalışanınızda aşağıdaki adımları uygulayın:

  • Tüm IP yakalama işlemlerini gerçekleştirme
  • Ortak anahtarı getirin.
  • HPKE şifrelemesi gerçekleştirin.

İstemci IP adresini yakalama

Web sunucunuzda veya CDN'nizde gelen sayfa isteğinden istemcinin maskelenmemiş IPv4 veya IPv6 adresini ayıklayın:

  • Doğrudan bağlantılar: Gelen HTTP bağlantısının uzak soket adresini okuyun.
  • CDN, yük dengeleyici veya ters proxy'nin arkasında: Güvenilir uç proxy'nizden istemci IP başlığını (ör. X-Forwarded-For başlığı veya CDN'ye özgü istemci IP başlığı) okuyun. Yalnızca proxy altyapınızın eklediği veya geçersiz kıldığı yönlendirme üstbilgilerine güvenin. X-Forwarded-For üstbilgisi virgülle ayrılmış birden fazla adres içerdiğinde, güvenilir uç proxy'nin eklediği istemci IP'sini seçin.

Ad Manager aşağıdaki adresleri destekler:

  • IPv4, örneğin 203.0.113.42.
  • IPv6, örneğin 2001:db8::8a2e:370:7334.

Yalnızca bağlantı noktası numaraları, parantezler, CIDR ön ekleri veya çevreleyen boşluklar olmadan yalın IP adresi dizesini iletin.

Ortak anahtarı getirme ve önbelleğe alma

Google, etkin HPKE şifreleme anahtarını aşağıdaki uç noktada RFC 8037 JSON Web Anahtarı Kümesi (JWKS) olarak yayınlar:

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

Bu uç noktaya gönderilen bir GET isteği, keys dizisi tek bir etkin anahtar nesnesi içeren bir JSON nesnesi döndürür. İstemci IP adresini şifrelemek için bu nesnenin x özelliğindeki anahtarı kullanın.

Aşağıdaki örnekte, ortak anahtar getirilir ve başarılı olursa x özelliği ayıklanır, başarısız olursa istisna oluşturulur:

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

İşlem başarılı olursa yanıt, x özelliğine sahip bir anahtar nesnesi içeren keys dizisini içerir:

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

Aşağıdaki tabloda, keys dizisindeki anahtar nesnesinin özellikleri açıklanmaktadır:

Mülk Açıklama
crv Eliptik eğri tanımlayıcısı. Her zaman X25519.
kid Google anahtarı değiştirdiğinde değişen, doldurulmamış base64url ile kodlanmış anahtar tanımlayıcı.
kty Anahtar türü. Her zaman OKP (Sekizli Anahtar Çifti).
x Şifreleme için kullanılan, doldurulmamış base64url ile kodlanmış 32 baytlık ham X25519 ortak anahtarı.

Alınan ortak anahtarı sunucunuzun belleğinde önbelleğe alın ve anahtarı en az 24 saatte bir veya HTTP Cache-Control yanıt başlığına göre daha sık yenileyin. Ortak anahtarı her gelen sayfa isteğinde getirmeyin. Google, rotasyona tabi tutulan özel anahtarları saklar. Önceki 24 saat içinde önbelleğe alınmış bir ortak anahtarla şifrelenen yüklerin şifresi, anahtar rotasyonu sırasında kesintisiz olarak çözülmeye devam eder.

IP adresini şifreleme

HPKE (RFC 9180) ve Ortak anahtarı getirme ve önbelleğe alma bölümünde alınan kodu çözülmüş 32 baytlık X25519 ortak anahtarını (x) kullanarak istemcinin IP adresi dizesinin UTF-8 bayt temsilini şifreleyin.

Aşağıdaki örnekte bir IP adresi dizesi şifrelenmektedir:

Java

Bu örnekte Tink (com.google.crypto.tink:tink 1.16.0 veya daha yeni bir sürüm) kullanılmaktadır:

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

Bu örnekte pyhpke kullanılmıştır:

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

Bu örnekte @hpke/core kullanılmıştır:

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

Bu örnekte @hpke/core kullanılmıştır:

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

HPKE kitaplığınızı aşağıdaki şifre paketi ve parametrelerle yapılandırın:

Parametre Değer
Anahtar Kapsülleme Mekanizması (KEM) DHKEM(X25519, HKDF-SHA256) (0x0020)
Anahtar Türetme İşlevi (KDF) HKDF-SHA256 (0x0001)
İlişkili Verilerle Kimliği Doğrulanmış Şifreleme (AEAD) AES-256-GCM (0x0002)
Mod Taban (0x00)
Uygulama bilgileri (info) Boş ("")
İlişkili veriler (aad) Boş ("")

Şifrelenmiş çıkışı, 32 baytlık kapsüllenmiş geçici ortak anahtarın (enc) ve 16 baytlık GCM kimlik doğrulama etiketi dahil olmak üzere AEAD şifreli metninin ham birleşimi olarak biçimlendirin. Tink veya özel başlık ön eki kullanmayın. Sonucu, doldurulmamış bir base64url dizesi olarak kodlayın.

Her yükü, yeni bir geçici anahtar çiftiyle bağımsız, tek seferlik bir HPKE mesajı olarak şifreleyin. HPKE kitaplığınız durum bilgisi olan bir gönderen bağlamı kullanıyorsa her şifreleme için yeni bir bağlam oluşturun. Bağlamı birden fazla mesajda tekrar kullanmayın. İstemci IP adresini sayfa isteği başına şifreleyin veya elde edilen base64url şifreli metin dizesini kısa bir geçerlilik süresi (TTL) için IP adresi başına önbelleğe alın.

İstemci tarafı iş akışını uygulama

Sunucunuz şifrelenmiş base64url IP yükünü oluşturduktan sonra, reklam istemeden önce yükü istemcideki GPT'ye iletin.

Şifrelenmiş IP'yi GPT'ye iletme

PageSettingsConfig üzerindeki ipAddress özelliğini kullanarak şifrelenmiş IP dizesini googletag.setConfig yöntemine iletin.

Aşağıdaki örnekte, hizmetler etkinleştirilmeden ve bir reklam alanı gösterilmeden önce sunucu tarafından oluşturulan şifrelenmiş IP yükü googletag.setConfig yöntemine eklenmektedir:

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

ENCRYPTED_IP_PAYLOAD değerini, sunucunuzun ürettiği doldurulmamış base64url kodlu dizeyle değiştirin.

Sayfada daha önce yapılandırılmış bir IP adresini temizlemek için ipAddress özelliğini null değerine ayarlayın:

googletag.setConfig({ ipAddress: null });

Entegrasyonu doğrulama

GPT'nin şifrelenmiş IP yükünüzü gönderdiğini ve Ad Manager'ın yükün şifresini çözdüğünü doğrulamak için tarayıcınızın geliştirici araçlarını ve Google Yayıncı Konsolu'nu kullanın:

Tarayıcı geliştirici araçları

  1. Tarayıcınızın geliştirici araçlarını açın.
  2. Ağ sekmesini seçin.
  3. /gampad/ads için reklam isteklerini filtreleyin.
  4. İstek URL'sinin boş olmayan bir ips sorgu parametresi içerdiğini doğrulayın. GPT, reklam isteğini göndermeden önce ipAddress değerini daha fazla kodladığından ips parametresi, şifrelenmiş dizenizle tam olarak eşleşmez.

Google Yayıncı Konsolu

  1. Konsolu açmak için Google Yayıncı Konsolu kılavuzundaki adımları uygulayın.
  2. Sayfa İsteği sekmesini seçin.
  3. ipAddress uyarısı gösterilmediğini doğrulayın. Yükle ilgili bir sorun varsa konsol aşağıdaki uyarılardan birini günlüğe kaydeder:

    • ipAddress özelliğine iletilen değer, 67 ile 124 karakter arasında geçerli bir base64 veya base64url dizesi değilse GPT değeri yoksayar, ips parametresini reklam isteğinden çıkarır ve aşağıdaki uyarıyı günlüğe kaydeder:

      Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.
      
    • Ad Manager, sağlanan IP yükünün şifresini çözemezse veya şifresi çözülen düz metin geçerli bir IPv4 ya da IPv6 adresi değilse konsol aşağıdaki uyarıyı günlüğe kaydeder ve ağ isteği IP adresine geri döner:

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