Serwery proxy przeglądarki zapewniające prywatność i ustawienia ochrony adresów IP mogą maskować adresy IP klientów w przypadku żądań reklam innych firm wysyłanych do Google Ad Managera. Maskowanie adresów IP może zmniejszyć dokładność kierowania geograficznego i trafność reklam. Serwer WWW lub sieć dostarczania treści (CDN) wchodzi w interakcję z użytkownikiem w kontekście własnym, dlatego serwer otrzymuje nieukryty adres IP użytkownika.
Funkcja adresu IP przekazywanego przez wydawcę umożliwia przechwytywanie na serwerze niezamaskowanego adresu IPv4 lub IPv6 użytkownika, szyfrowanie go za pomocą hybrydowego szyfrowania kluczem publicznym (HPKE) i przekazywanie zaszyfrowanych danych do tagu wydawcy Google (GPT) na urządzeniu klienta.
Na tej stronie znajdziesz informacje o integracji adresu IP przekazywanego przez wydawcę z GPT.
Wdrażanie przepływu pracy po stronie serwera
Aby uniknąć ujawnienia adresu IP użytkownika w formie zwykłego tekstu skryptom innych firm w przeglądarce, wykonaj te czynności na serwerze WWW lub w usłudze CDN:
- Przechwytywanie wszystkich adresów IP.
- Pobierz klucz publiczny.
- wykonywać szyfrowanie HPKE;
Przechwytywanie adresu IP klienta
Wyodrębnij niezamaskowany adres IPv4 lub IPv6 klienta z przychodzącego żądania strony na serwerze WWW lub w sieci CDN:
- Połączenia bezpośrednie: odczytaj adres gniazda zdalnego przychodzącego połączenia HTTP.
- Za siecią CDN, systemem równoważenia obciążenia lub zwrotnym serwerem proxy: odczytaj nagłówek IP klienta z zaufanego serwera proxy, np. nagłówek
X-Forwarded-Forlub nagłówek IP klienta specyficzny dla sieci CDN. Ufaj tylko nagłówkom przekierowania, które dodaje lub zastępuje infrastruktura proxy. Jeśli nagłówekX-Forwarded-Forzawiera wiele adresów rozdzielonych przecinkami, wybierz adres IP klienta dodany przez zaufany serwer proxy brzegu sieci.
Ad Manager obsługuje te adresy:
- IPv4, na przykład
203.0.113.42. - IPv6, na przykład
2001:db8::8a2e:370:7334.
Przekaż tylko ciąg adresu IP, bez numerów portów, nawiasów, prefiksów CIDR i otaczających spacji.
Pobierz klucz publiczny i umieść go w pamięci podręcznej
Google publikuje aktywny klucz szyfrowania HPKE jako zestaw kluczy internetowych JSON (JWKS) RFC 8037 w tym punkcie końcowym:
https://www.gstatic.com/real-ip/ip_enc_key.json
Żądanie GET do tego punktu końcowego zwraca obiekt JSON, którego tablica keys zawiera pojedynczy aktywny obiekt klucza. Użyj klucza we właściwości x tego obiektu, aby zaszyfrować adres IP klienta.
W tym przykładzie pobierany jest klucz publiczny i wyodrębniana właściwość x w przypadku powodzenia, a w przypadku niepowodzenia zgłaszany jest wyjątek:
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;
}
Jeśli operacja się powiedzie, odpowiedź będzie zawierać tablicę keys z obiektem klucza z właściwością x:
{
"keys": [
{
"kty": "OKP",
"crv": "X25519",
"x": "7bws5TIHOUrcJNofEubW3DPW8zR9EtjFNLm_Grv98TA",
"kid": "EZMLFw"
}
]
}
W tej tabeli opisujemy właściwości obiektu klucza w tablicy keys:
| Właściwość | Opis |
|---|---|
crv |
Identyfikator krzywej eliptycznej. Zawsze X25519. |
kid |
Identyfikator klucza zakodowany w formacie base64url bez dopełnienia, który zmienia się, gdy Google rotuje klucz. |
kty |
Typ klucza. Zawsze OKP (para kluczy oktetowych). |
x |
Niedopełniony, zakodowany w formacie base64url, 32-bajtowy nieprzetworzony klucz publiczny X25519 używany do szyfrowania. |
Zapisz pobrany klucz publiczny w pamięci podręcznej na serwerze i odświeżaj go co najmniej raz na 24 godziny lub częściej, zgodnie z nagłówkiem odpowiedzi HTTP Cache-Control. Nie pobieraj klucza publicznego przy każdym przychodzącym żądaniu strony.
Google zachowuje rotowane klucze prywatne. Ładunki zaszyfrowane za pomocą klucza publicznego zapisanego w pamięci podręcznej w ciągu ostatnich 24 godzin są nadal odszyfrowywane bez przerw podczas rotacji kluczy.
Szyfrowanie adresu IP
Zaszyfruj reprezentację bajtową UTF-8 ciągu adresu IP klienta, używając HPKE (RFC 9180) i zdekodowanego 32-bajtowego klucza publicznego X25519 (x) pobranego w kroku Pobieranie i buforowanie klucza publicznego.
Ten przykład szyfruje ciąg znaków adresu IP:
Java
W tym przykładzie używamy Tink (com.google.crypto.tink:tink w wersji 1.16.0 lub nowszej):
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
W tym przykładzie użyto 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
W tym przykładzie użyto @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
W tym przykładzie użyto @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',
);
}
Skonfiguruj bibliotekę HPKE, używając tego zestawu szyfrów i tych parametrów:
| Parametr | Wartość |
|---|---|
| Mechanizm hermetyzacji klucza (KEM) | DHKEM(X25519, HKDF-SHA256) (0x0020) |
| Funkcja derywacji klucza (KDF) | HKDF-SHA256 (0x0001) |
| szyfrowanie uwierzytelnione z danymi powiązanymi (AEAD) | AES-256-GCM (0x0002) |
| Tryb | Podstawowy (0x00) |
Informacje o aplikacji (info) |
Puste ("") |
Powiązane dane (aad) |
Puste ("") |
Sformatuj zaszyfrowane dane wyjściowe jako nieprzetworzoną konkatenację 32-bajtowego enkapsulowanego tymczasowego klucza publicznego, enc i tekstu szyfrowanego AEAD, w tym 16-bajtowego tagu uwierzytelniania GCM, bez Tink ani niestandardowego prefiksu nagłówka. Zakoduj wynik jako ciąg base64url bez dopełnienia.
Zaszyfruj każdy ładunek jako samodzielną, jednorazową wiadomość HPKE z nową parą kluczy efemerycznych. Jeśli biblioteka HPKE używa kontekstu nadawcy ze stanem, utwórz nowy kontekst dla każdego szyfrowania, zamiast używać go ponownie w wielu wiadomościach. Zaszyfruj adres IP klienta na żądanie strony lub zapisz w pamięci podręcznej wynikowy ciąg tekstu zaszyfrowanego base64url na adres IP na krótki czas życia danych (TTL).
Wdrażanie przepływu pracy po stronie klienta
Gdy serwer wygeneruje zaszyfrowane dane IP w formacie base64url, przekaż je do GPT na kliencie przed wysłaniem żądania reklam.
Przekazywanie zaszyfrowanego adresu IP do GPT
Przekaż zaszyfrowany ciąg znaków adresu IP do metody googletag.setConfig, używając właściwości ipAddress w PageSettingsConfig.
W poniższym przykładzie wygenerowany przez serwer zaszyfrowany ładunek IP jest wstawiany do metody googletag.setConfig przed włączeniem usług i wyświetleniem boksu reklamowego:
<!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>Zastąp ENCRYPTED_IP_PAYLOAD ciągiem znaków zakodowanym w formacie base64url bez dopełnienia, który generuje Twój serwer.
Aby usunąć wcześniej skonfigurowany adres IP na stronie, ustaw właściwość ipAddress na wartość null:
googletag.setConfig({ ipAddress: null });
Weryfikowanie integracji
Aby sprawdzić, czy tag GPT wysyła zaszyfrowane dane o adresie IP, a Ad Manager je odszyfrowuje, użyj narzędzi dla programistów w przeglądarce i Konsoli Google dla wydawcy:
Narzędzia dla programistów przeglądarki
- Otwórz narzędzia dla programistów w przeglądarce.
- Kliknij kartę Sieć.
- Filtruj żądania reklamy według:
/gampad/ads. - Sprawdź, czy adres URL żądania zawiera niepusty parametr zapytania
ips. Ponieważ GPT dodatkowo koduje wartośćipAddressprzed wysłaniem żądania reklamy, parametripsnie pasuje dokładnie do zaszyfrowanego ciągu znaków.
Konsola Google dla wydawców
- Aby otworzyć konsolę, wykonaj czynności opisane w przewodniku po Konsoli Google dla wydawcy.
- Kliknij kartę Żądanie strony.
Sprawdź, czy nie pojawiają się ostrzeżenia
ipAddress. Jeśli wystąpi problem z ładunkiem, konsola zarejestruje jedno z tych ostrzeżeń:Jeśli wartość przekazana do właściwości
ipAddressnie jest prawidłowym ciągiem base64 lub base64url o długości od 67 do 124 znaków, GPT ignoruje tę wartość, pomija parametripsw żądaniu reklamy i rejestruje to ostrzeżenie:Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.Jeśli Ad Manager nie może odszyfrować podanych danych IP lub jeśli odszyfrowany zwykły tekst nie jest prawidłowym adresem IPv4 lub IPv6, konsola rejestruje następujące ostrzeżenie i wraca do adresu IP żądania sieciowego:
Decryption failed on the provided encrypted client IP (supplied via googletag.setConfig({ipAddress: ...})).