Les proxys de confidentialité du navigateur et les paramètres de protection des adresses IP peuvent masquer les adresses IP des clients dans les demandes d'annonces tierces envoyées à Google Ad Manager. Les adresses IP masquées peuvent réduire la précision du ciblage géographique et la pertinence des annonces. Étant donné que votre serveur Web ou votre réseau de diffusion de contenu (CDN) interagit avec l'utilisateur dans un contexte first party, votre serveur reçoit l'adresse IP non masquée de l'utilisateur.
La fonctionnalité d'adresse IP fournie par l'éditeur vous permet de capturer l'adresse IPv4 ou IPv6 non masquée de l'utilisateur sur votre serveur, de la chiffrer à l'aide du chiffrement hybride à clé publique (HPKE) et de transmettre la charge utile chiffrée au tag d'éditeur Google (GPT) sur le client.
Cette page explique comment intégrer l'adresse IP fournie par l'éditeur à GPT.
Implémenter le workflow côté serveur
Pour éviter d'exposer l'adresse IP en texte brut de l'utilisateur à des scripts tiers dans le navigateur, procédez comme suit sur votre serveur Web ou votre CDN Edge Worker :
- Effectuez toutes les captures d'adresses IP.
- Récupérez la clé publique.
- Effectuez le chiffrement HPKE.
Capturer l'adresse IP client
Extrayez l'adresse IPv4 ou IPv6 non masquée du client de la requête de page entrante sur votre serveur Web ou votre CDN :
- Connexions directes : lisez l'adresse du socket distant de la connexion HTTP entrante.
- Derrière un CDN, un équilibreur de charge ou un proxy inverse : lisez l'en-tête d'adresse IP client à partir de votre proxy périphérique de confiance, tel que l'en-tête
X-Forwarded-Forou un en-tête d'adresse IP client spécifique au CDN. N'approuvez que les en-têtes de transfert que votre infrastructure de proxy ajoute ou remplace. Lorsque l'en-têteX-Forwarded-Forcontient plusieurs adresses séparées par une virgule, sélectionnez l'adresse IP du client ajoutée par le proxy périphérique de confiance.
Ad Manager accepte les adresses suivantes :
- IPv4, par exemple
203.0.113.42. - IPv6, par exemple
2001:db8::8a2e:370:7334.
Transmettez uniquement la chaîne d'adresse IP brute, sans numéros de port, crochets, préfixes CIDR ni espaces environnants.
Récupérer et mettre en cache la clé publique
Google publie la clé de chiffrement HPKE active en tant qu'ensemble de clés Web JSON (JWKS) RFC 8037 au niveau du point de terminaison suivant :
https://www.gstatic.com/real-ip/ip_enc_key.json
Une requête GET envoyée à ce point de terminaison renvoie un objet JSON dont le tableau keys contient un seul objet de clé active. Utilisez la clé de la propriété x de cet objet pour chiffrer l'adresse IP client.
L'exemple suivant récupère la clé publique et extrait la propriété x en cas de réussite, et génère une exception en cas d'échec :
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;
}
Si la requête aboutit, la réponse inclut le tableau keys contenant un objet de clé avec la propriété x :
{
"keys": [
{
"kty": "OKP",
"crv": "X25519",
"x": "7bws5TIHOUrcJNofEubW3DPW8zR9EtjFNLm_Grv98TA",
"kid": "EZMLFw"
}
]
}
Le tableau suivant décrit les propriétés de l'objet clé dans le tableau keys :
| Propriété | Description |
|---|---|
crv |
Identifiant de courbe elliptique. Toujours X25519. |
kid |
Identifiant de clé encodé en base64url sans marge, qui change lorsque Google effectue une rotation de la clé. |
kty |
Type de clé. Toujours OKP (paire de clés d'octets). |
x |
Clé publique X25519 brute de 32 octets encodée en base64url sans marge intérieure, utilisée pour le chiffrement. |
Mettez en cache la clé publique récupérée dans la mémoire de votre serveur et actualisez-la au moins une fois toutes les 24 heures, ou plus tôt selon l'en-tête de réponse HTTP Cache-Control. Ne récupérez pas la clé publique à chaque requête de page entrante.
Google conserve les clés privées alternées. Les charges utiles chiffrées avec une clé publique mise en cache au cours des 24 heures précédentes continuent d'être déchiffrées sans interruption pendant la rotation des clés.
Chiffrer l'adresse IP
Chiffrez la représentation en octets UTF-8 de la chaîne d'adresse IP du client à l'aide de HPKE (RFC 9180) et de la clé publique X25519 de 32 octets décodée, x, récupérée dans Récupérer et mettre en cache la clé publique.
L'exemple suivant chiffre une chaîne d'adresse IP :
Java
Cet exemple utilise Tink (com.google.crypto.tink:tink 1.16.0 ou version ultérieure) :
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
Cet exemple utilise 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
Cet exemple utilise @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
Cet exemple utilise @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',
);
}
Configurez votre bibliothèque HPKE avec la suite de chiffrement et les paramètres suivants :
| Paramètre | Valeur |
|---|---|
| Mécanisme d'encapsulation de clé (KEM) | DHKEM(X25519, HKDF-SHA256) (0x0020) |
| Fonction de dérivation de clé (KDF) | HKDF-SHA256 (0x0001) |
| Chiffrement authentifié avec données associées (AEAD) | AES-256-GCM (0x0002) |
| Mode | Base (0x00) |
Informations sur l'application (info) |
Vide ("") |
Données associées (aad) |
Vide ("") |
Mettez en forme le résultat chiffré en tant que concaténation brute de la clé publique éphémère encapsulée de 32 octets, enc, et du texte chiffré AEAD, y compris le tag d'authentification GCM de 16 octets, sans préfixe d'en-tête Tink ou personnalisé. Encodez le résultat sous forme de chaîne base64url sans marge intérieure.
Chiffrez chaque charge utile en tant que message HPKE unique et autonome avec une nouvelle paire de clés éphémères. Si votre bibliothèque HPKE utilise un contexte d'expéditeur avec état, créez un contexte pour chaque chiffrement au lieu de le réutiliser pour plusieurs messages. Chiffrez l'adresse IP client par requête de page ou mettez en cache la chaîne de texte chiffré base64url obtenue par adresse IP pour une courte durée de vie (TTL).
Implémenter le workflow côté client
Une fois que votre serveur a généré la charge utile d'adresse IP base64url chiffrée, transmettez-la à GPT sur le client avant de demander des annonces.
Transmettre l'adresse IP chiffrée à GPT
Transmettez la chaîne d'adresse IP chiffrée à la méthode googletag.setConfig à l'aide de la propriété ipAddress sur PageSettingsConfig.
L'exemple suivant insère la charge utile IP chiffrée générée par le serveur dans la méthode googletag.setConfig avant d'activer les services et d'afficher un espace publicitaire :
<!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>Remplacez ENCRYPTED_IP_PAYLOAD par la chaîne encodée en base64url sans marge intérieure produite par votre serveur.
Pour effacer une adresse IP précédemment configurée sur la page, définissez la propriété ipAddress sur la valeur null :
googletag.setConfig({ ipAddress: null });
Vérifier l'intégration
Pour vérifier que GPT envoie votre charge utile d'adresse IP chiffrée et qu'Ad Manager la déchiffre, utilisez les outils pour les développeurs de votre navigateur et la console Google pour les éditeurs :
Outils pour les développeurs dans un navigateur
- Ouvrez les outils pour les développeurs de votre navigateur.
- Sélectionnez l'onglet Réseau.
- Filtrez les demandes d'annonces pour
/gampad/ads. - Vérifiez que l'URL de la requête inclut un paramètre de requête
ipsnon vide. Étant donné que GPT encode davantage la valeuripAddressavant d'envoyer la demande d'annonce, le paramètreipsne correspond pas exactement à votre chaîne chiffrée.
Console Google pour les éditeurs
- Pour ouvrir la console, suivez les étapes du guide Console Google pour les éditeurs.
- Sélectionnez l'onglet Demande de page.
Vérifiez qu'aucun avertissement
ipAddressne s'affiche. En cas de problème avec la charge utile, la console consigne l'un des avertissements suivants :Si la valeur transmise à la propriété
ipAddressn'est pas une chaîne base64 ou base64url valide comprise entre 67 et 124 caractères, GPT l'ignore, omet le paramètreipsde la demande d'annonce et consigne l'avertissement suivant :Invalid value encountered when calling: googletag.setConfig: {"ipAddress":"..."}. (string) ... was found at PageSettingsConfig.ipAddress. Invalid IP address format.Si Ad Manager ne parvient pas à déchiffrer la charge utile d'adresse IP fournie ou si le texte brut déchiffré n'est pas une adresse IPv4 ou IPv6 valide, la console consigne l'avertissement suivant et utilise l'adresse IP de la requête réseau :
Decryption failed on the provided encrypted client IP (supplied via googletag.setConfig({ipAddress: ...})).