Push
Chiffrement
Pour un appareil qui a enregistré sa propre clé, honkio chiffre chaque notification avec cette clé : Apple et Google reçoivent le contenu chiffré. Apple voit un texte de remplacement au lieu du contenu, et le badge, le son, l'identifiant de fil et la clé de regroupement restent lisibles; pour Google, seule la clé de regroupement reste lisible. Seul cet appareil peut le déchiffrer.
Fonctionnement du chiffrement
Chaque installation de votre application crée sa propre paire de clés de chiffrement. Les paquets Swift et Android de HonkIO la créent la première fois que votre application demande la clé publique, et gardent la clé privée sur l'appareil, qu'elle ne quitte jamais. Votre application envoie la clé publique à votre serveur avec son jeton push, et votre serveur l'enregistre avec l'appareil (encryption_public_key dans PUT /v1/push/devices).
Pour un appareil qui a enregistré une clé, honkio chiffre ensuite chaque notification avec cette clé, et envoie à Apple un texte de remplacement et l'enveloppe chiffrée, ou à Google l'enveloppe seule. Du contenu, Apple ne peut lire que le texte de remplacement, propre à chaque application (placeholder_title et placeholder_body, titre par défaut New notification). Sur iOS, le badge, le son et l'identifiant de fil sont eux aussi hors de l'enveloppe, lisibles par Apple, car iOS les applique avant que votre extension ne déchiffre quoi que ce soit; sur Android, ils sont dans l'enveloppe. La clé de regroupement est hors de l'enveloppe sur les deux, car Apple et Google s'en servent pour remplacer une notification plus ancienne.
Ce qu'il protège : Apple, Google et tout ce qui se trouve entre honkio et l'appareil ne peuvent pas lire le titre, le corps ni les données. Chaque appareil ne peut déchiffrer que les notifications envoyées à sa propre clé : un appareil compromis lit ses propres notifications, et celles de personne d'autre.
Ce qu'il ne protège pas : ce n'est pas du chiffrement de bout en bout. honkio chiffre le contenu et le voit donc, comme il voit le texte d'un message qu'il envoie pour vous, et votre serveur le voit puisqu'il l'envoie. Pour un contenu sensible, enregistrez une clé pour chaque appareil et exigez le chiffrement.
Un appareil enregistré sans clé reçoit ses notifications en clair, sauf si l'application exige le chiffrement (plus bas).
Enregistrer la clé de chaque appareil
encryption_public_key est la clé publique de l'appareil telle que les paquets la renvoient : le point P-256 non compressé de 65 octets, en base64url sans remplissage. honkio vérifie qu'il s'agit d'un point de la courbe et refuse toute autre valeur avec un 422 VALIDATION_ERROR. La clé n'est jamais renvoyée : un appareil porte plutôt encryption_key_id, les 16 premiers caractères de base64url(SHA-256(clé)), qui est aussi le kid de chaque enveloppe qui lui est envoyée.
Enregistrez-la à chaque lancement avec le jeton, comme le font les exemples des paquets plus bas. Un nouvel enregistrement du même jeton sans encryption_public_key conserve sa clé, mais un nouveau jeton, après un renouvellement du jeton APNs ou FCM, est enregistré comme un nouvel appareil sans clé, sauf si vous envoyez la clé avec lui. Les paquets donnent la clé publique à votre application en même temps que le jeton : votre serveur doit donc toujours transmettre les deux. Seul un encryption_public_key explicitement à null la retire; pour une application qui exige le chiffrement, cet appareil ne reçoit alors plus de notifications (ENCRYPTION_KEY_MISSING) jusqu'à ce qu'il enregistre de nouveau une clé.
// Call this from your backend when the app reports its token.
const { data: device, error } = await honkio.push.devices.register({
appId: 'APP_ID',
token: 'DEVICE_TOKEN',
platform: 'ios', // or 'android'
userId: 'user_1042', // your own identifier for this person
apnsEnvironment: 'production', // iOS only: 'sandbox' for development builds
encryptionPublicKey: 'DEVICE_PUBLIC_KEY', // the key the HonkIO push package made on the device
})
if (error) throw new Error(error.message)
console.log(device.id, device.active, device.encryption_key_id)from honkio import Honkio
client = Honkio() # reads HONKIO_API_KEY
# Call this from your backend when the app reports its token.
device = client.push.devices.register(
app_id="APP_ID",
token="DEVICE_TOKEN",
platform="ios", # or "android"
user_id="user_1042", # your own identifier for this person
apns_environment="production", # iOS only: "sandbox" for development builds
encryption_public_key="DEVICE_PUBLIC_KEY", # the key the HonkIO push package made on the device
)
print(device["id"], device["active"], device["encryption_key_id"])<?php
use Honkio\Honkio;
$honkio = new Honkio(); // reads HONKIO_API_KEY
// Call this from your backend when the app reports its token.
$device = $honkio->push->devices->register([
'app_id' => 'APP_ID',
'token' => 'DEVICE_TOKEN',
'platform' => 'ios', // or 'android'
'user_id' => 'user_1042', // your own identifier for this person
'apns_environment' => 'production', // iOS only: 'sandbox' for development builds
'encryption_public_key' => 'DEVICE_PUBLIC_KEY', // the key the HonkIO push package made on the device
]);
echo $device['id'], ' ', $device['active'] ? 'active' : 'inactive', ' ', $device['encryption_key_id'] ?? 'no key', PHP_EOL;curl -X PUT https://api.honkio.ca/v1/push/devices \
-H "Authorization: Bearer mk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"app_id": "APP_ID",
"token": "test_accept",
"platform": "ios",
"user_id": "user_1042",
"apns_environment": "sandbox",
"encryption_public_key": "DEVICE_PUBLIC_KEY"
}'
# Response: { "id": "pdev_...", "active": true, "encryption_key_id": "cHRL-UfvREIeEx3Q", ... }Pour faire la rotation de la clé d'un appareil, créez-en une nouvelle sur l'appareil (HonkioPush.rotateEncryptionKey dans les deux paquets) et enregistrez-la : la nouvelle clé remplace l'ancienne immédiatement. Les paquets gardent aussi la clé précédente, si bien que les notifications envoyées à l'ancienne clé avant que votre serveur enregistre la nouvelle s'ouvrent encore.
Exiger le chiffrement
require_encryption est désactivé par défaut et se règle par application push, dans POST /v1/push/apps, dans PATCH /v1/push/apps/:id ou dans le tableau de bord. Le désactiver depuis le tableau de bord vous demande d'abord de confirmer votre identité, car les appareils sans clé reçoivent alors les notifications en clair. Désactivé, un appareil qui a une clé reçoit chaque notification chiffrée et un appareil sans clé la reçoit en clair.
Activé, chaque livraison se fait avec chiffrement ou n'a pas lieu. Un appareil sans clé est refusé pour cette notification avec le error_code de livraison ENCRYPTION_KEY_MISSING : il ne reçoit jamais la notification en clair, n'est jamais facturé et n'est pas compté dans device_count. Si tous les appareils visés sont refusés, la notification échoue avec ENCRYPTION_KEY_MISSING, ou envoie son texto de repli si elle en a un. L'activer s'applique aussi aux notifications déjà planifiées ou en file d'attente : une livraison vers un appareil sans clé est refusée au moment de l'envoi, et remboursée.
Le champ encrypted d'une notification est le require_encryption de l'application au moment de l'envoi, et le champ encrypted de chaque livraison indique si cet appareil l'a reçue avec chiffrement.
require_encryption est aussi ce qui protège contre le retrait de la clé d'un appareil. Tant qu'il est désactivé, quiconque peut enregistrer des appareils (une clé avec le w de la permission push_devices, ou une autorisation OAuth push:devices) peut retirer la clé d'un appareil avec null, et les notifications suivantes de cet appareil partent en clair; aucun webhook n'est déclenché. Activé, cet appareil est plutôt refusé. Activez-le dès que chaque installation de votre application envoie sa clé.
// Every push is encrypted to each device that registered a key. With
// requireEncryption, a device without one is skipped (ENCRYPTION_KEY_MISSING)
// instead of getting plaintext, and is not charged.
const { data: pushApp, error } = await honkio.push.apps.update('APP_ID', { requireEncryption: true })
if (error) throw new Error(error.message)
console.log(pushApp.require_encryption) // truefrom honkio import Honkio
client = Honkio() # reads HONKIO_API_KEY
# Every push is encrypted to each device that registered a key. With
# require_encryption, a device without one is skipped (ENCRYPTION_KEY_MISSING)
# instead of getting plaintext, and is not charged.
push_app = client.push.apps.update("APP_ID", require_encryption=True)
print(push_app["require_encryption"]) # True<?php
use Honkio\Honkio;
$honkio = new Honkio(); // reads HONKIO_API_KEY
// Every push is encrypted to each device that registered a key. With
// require_encryption, a device without one is skipped (ENCRYPTION_KEY_MISSING)
// instead of getting plaintext, and is not charged.
$pushApp = $honkio->push->apps->update('APP_ID', ['require_encryption' => true]);
echo $pushApp['require_encryption'] ? 'required' : 'optional', PHP_EOL;curl -X PATCH https://api.honkio.ca/v1/push/apps/APP_ID \
-H "Authorization: Bearer mk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "require_encryption": true }'
# Response: the app, with "require_encryption": true. A device registered
# without encryption_public_key is now skipped (ENCRYPTION_KEY_MISSING), not charged.L'enveloppe
L'enveloppe est un objet JSON : v (2), kid (l'identifiant de la clé de l'appareil), enc (la clé encapsulée, un point P-256 de 65 octets) et ct (la charge scellée suivie de l'étiquette de 16 octets). enc et ct sont en base64url sans remplissage.
Il s'agit de HPKE (RFC 9180) en mode de base avec DHKEM(P-256, HKDF-SHA256), HKDF-SHA256 et AES-128-GCM, avec info valant le texte UTF-8 honkio-push-v2, sans données additionnelles et avec un seul message par enveloppe. Une fois ouverte, elle donne la notification en JSON : title, body, data, badge, sound et thread_id, chacun présent si vous l'avez envoyé. Toute bibliothèque HPKE qui prend en charge cette suite peut l'ouvrir; vérifiez la vôtre avec le vecteur de test plus bas.
Sur iOS, le bloc aps porte le texte de remplacement et mutable-content à 1, avec hk à côté. Votre Notification Service Extension déchiffre hk et remplace le titre et le corps. Si l'extension échoue ou dépasse le délai, iOS affiche le texte de remplacement.
Sur Android, le message ne contient que des données : data.hk porte l'enveloppe sous forme de chaîne JSON, sans bloc de notification. Votre FirebaseMessagingService la déchiffre dans onMessageReceived et affiche elle-même la notification, avec le thread_id qu'elle contient, s'il est présent, comme étiquette (tag) de la notification, comme le fait FCM pour une notification en clair.
iOS : Notification Service Extension
Ajoutez le paquet Swift HonkioPush à votre application et à la cible de votre Notification Service Extension (iOS 17 ou plus), et donnez aux deux cibles le même groupe d'accès au trousseau. L'application appelle HonkioPush.encryptionPublicKey(accessGroup:), qui crée la paire de clés la première fois, et envoie publicKey à votre serveur avec le jeton. L'extension passe son contenu et le même groupe d'accès à decryptNotificationContent, qui remplace le titre et le corps par les versions en clair; si quoi que ce soit échoue (aucune enveloppe, identifiant de clé inconnu, altération, téléphone pas encore déverrouillé depuis son redémarrage), iOS affiche le texte de remplacement. Les data en clair sont placées sous userInfo["data"]. La clé privée reste dans la Secure Enclave si l'appareil en a une, sinon dans le trousseau, lisible après le premier déverrouillage et jamais copiée vers un autre appareil par une sauvegarde.
.package(url: "https://github.com/somecorp-dev/honkio-swift", from: "0.1.0")import HonkioPush
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let token = HonkioDeviceToken.hexString(from: deviceToken)
#if DEBUG
let environment = "sandbox" // a build you run from Xcode
#else
let environment = "production" // TestFlight, App Store and ad hoc builds
#endif
// Created on first use, then the same key on every launch.
guard let key = try? HonkioPush.encryptionPublicKey(accessGroup: "group.com.example.app") else { return }
// sendTokenToYourBackend is your app's own function; your backend registers all three with HonkIO
// (key.publicKey as encryption_public_key).
Task { await sendTokenToYourBackend(token, apnsEnvironment: environment, encryptionPublicKey: key.publicKey) }
}import UserNotifications
import HonkioPush
final class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let content = request.content.mutableCopy() as? UNMutableNotificationContent else {
contentHandler(request.content)
return
}
// The same access group the app passes to HonkioPush.encryptionPublicKey.
contentHandler(HonkioPush.decryptNotificationContent(content, accessGroup: "group.com.example.app"))
}
}Sans le paquet
Depuis iOS 17, CryptoKit prend en charge HPKE. Sans le paquet, vous créez vous-même la paire de clés (P256.KeyAgreement.PrivateKey), la gardez dans un groupe du trousseau partagé avec l'extension et envoyez le x963Representation de sa clé publique, encodé en base64url, comme encryption_public_key. L'extension lit hk, retrouve sa clé privée par kid et ouvre ct avec un HPKE.Recipient créé à partir de enc.
import UserNotifications
import CryptoKit
enum PushDecryptError: Error { case badEnvelope, unknownKey }
final class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
// If anything below fails, iOS shows the placeholder that came with the push.
guard let content = request.content.mutableCopy() as? UNMutableNotificationContent,
let envelope = request.content.userInfo["hk"] as? [String: Any],
let plain = try? Self.decrypt(envelope, keys: loadPrivateKeys()) else {
contentHandler(request.content)
return
}
if let title = plain["title"] as? String { content.title = title }
if let body = plain["body"] as? String { content.body = body }
if let data = plain["data"] as? [String: Any] { content.userInfo["data"] = data }
contentHandler(content)
}
override func serviceExtensionTimeWillExpire() {
// Nothing to flush: the placeholder is shown if we run out of time.
}
/// kid -> this device's private key. The kid is the first 16 characters of
/// base64url(SHA-256(the 65 byte public key)), as the API reports it in encryption_key_id.
/// Keep the keys in a Keychain item in the shared group, saved with
/// kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Sample: returns nothing here.
private func loadPrivateKeys() -> [String: P256.KeyAgreement.PrivateKey] { [:] }
static func decrypt(_ envelope: [String: Any], keys: [String: P256.KeyAgreement.PrivateKey]) throws -> [String: Any] {
guard envelope["v"] as? Int == 2,
let kid = envelope["kid"] as? String,
let enc = (envelope["enc"] as? String).flatMap(base64URL),
let ct = (envelope["ct"] as? String).flatMap(base64URL) else { throw PushDecryptError.badEnvelope }
guard let key = keys[kid] else { throw PushDecryptError.unknownKey }
// HPKE base mode: DHKEM(P-256, HKDF-SHA256), HKDF-SHA256, AES-128-GCM, info "honkio-push-v2", no aad.
let suite = HPKE.Ciphersuite(kem: .P256_HKDF_SHA256, kdf: .HKDF_SHA256, aead: .AES_GCM_128)
var recipient = try HPKE.Recipient(privateKey: key, ciphersuite: suite,
info: Data("honkio-push-v2".utf8), encapsulatedKey: enc)
let plain = try recipient.open(ct)
guard let json = try JSONSerialization.jsonObject(with: plain) as? [String: Any] else {
throw PushDecryptError.badEnvelope
}
return json
}
static func base64URL(_ s: String) -> Data? {
var b64 = s.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
b64 += String(repeating: "=", count: (4 - b64.count % 4) % 4)
return Data(base64Encoded: b64)
}
}Android : FirebaseMessagingService
Ajoutez la bibliothèque honkio-push au module de votre application. Envoyez HonkioPush.encryptionPublicKey(context).publicKey à votre serveur avec le jeton FCM, au lancement et depuis onNewToken : la paire de clés est créée la première fois et conservée sous la protection d'une clé de l'Android Keystore. Dans onMessageReceived, un message sans clé hk n'est pas une notification honkio avec chiffrement : le service n'y touche pas. Si hk est présent, HonkioPush.decryptData l'ouvre; s'il est abîmé ou s'il a été envoyé à une clé que l'appareil n'a plus, affichez votre propre texte de remplacement générique. Les deux appels lisent le Keystore : faites-les hors du fil principal (onMessageReceived s'exécute déjà hors de celui-ci).
dependencies {
implementation(platform("com.google.firebase:firebase-bom:34.5.0"))
implementation("com.google.firebase:firebase-messaging")
implementation("ca.honkio:honkio-push:0.1.0")
}// sendTokenToBackend is your app's own function. onToken runs on the main thread, so move off it.
HonkioTokens.current(
onToken = { token -> thread { sendTokenToBackend(token, HonkioPush.encryptionPublicKey(applicationContext)) } },
onError = { e -> Log.w("push", "no FCM token", e) },
)
class PushService : FirebaseMessagingService() {
// onNewToken already runs off the main thread.
override fun onNewToken(token: String) = sendTokenToBackend(token, HonkioPush.encryptionPublicKey(applicationContext))
}class PushService : FirebaseMessagingService() {
// sendTokenToBackend is your app's own function.
override fun onNewToken(token: String) = sendTokenToBackend(token, HonkioPush.encryptionPublicKey(applicationContext))
override fun onMessageReceived(message: RemoteMessage) {
// No `hk` key: not an encrypted HonkIO push, so leave it alone.
if (!message.data.containsKey(PushEnvelope.DATA_KEY)) return
// `hk` present but unparseable or undecryptable: show the placeholder.
val plain = try {
HonkioPush.decryptData(applicationContext, message.data)
} catch (e: HonkioPushException) {
null // e.reason: UNSUPPORTED_VERSION, INVALID_ENVELOPE, UNKNOWN_KID, DECRYPTION_FAILED, INVALID_PAYLOAD, KEY_STORAGE
}
val title = plain?.optString("title")?.takeIf { it.isNotEmpty() }
?: getString(R.string.push_placeholder_title)
val body = plain?.optString("body")?.takeIf { it.isNotEmpty() }
?: getString(R.string.push_placeholder_body)
// Build and post the notification with NotificationCompat, as usual.
// plain?.optJSONObject("data") holds your custom data.
}
}Sans le paquet
Android n'a pas de HPKE intégré. Sans le paquet, utilisez une bibliothèque HPKE qui prend en charge la suite ci-dessus, comme Tink de Google, gardez la clé privée protégée par une clé de l'Android Keystore et vérifiez votre code avec le vecteur de test plus bas.
Vecteur de test
Servez-vous-en pour tester votre code de déchiffrement : ouvrir l'enveloppe avec la clé privée du destinataire redonne exactement le texte clair. honkio le génère avec le code de chiffrement du serveur, et les tests de honkio l'y comparent : il ne peut donc pas diverger. Les clés sont en base64url sans remplissage; la clé privée est le scalaire brut de 32 octets.
| Clé privée du destinataire | _nrSUmsffUCgaGb5gmqkRLXCc_-ls5Dh6sm2Bu6OA3M |
|---|---|
| Clé publique du destinataire | BFGHw33ou72Yj1eGoA53xqL29aB_tk5aVfF2PiySBv06hugrUUHAOoXyzC6xpLBnjBfu8DS3Phd3ascQSTTrYPc |
| Identifiant de clé (kid) | cHRL-UfvREIeEx3Q |
Enveloppe
{
"v": 2,
"kid": "cHRL-UfvREIeEx3Q",
"enc": "BPDblkoWAGabKxtOrs52tL2JaFkPPoqNVP8104Q3C7SwOj8mbh1bw7iLBpr7tWEPglEt4X_DX9Y8BVJnrHo1-wg",
"ct": "SK86q5n7JJTP_HjhHm_7WGk0g2mgJSjE4UyRTnjlQqObn8ntlLw8IJgQ5UasGWOTNuWwmlhl6o-wbXOB9lse8pkvaOlV7SNKs4p54P5nVzLtMoJsp8t8gD1t_m0Y3jSxhoCsRMV-pg"
}Texte clair
{"title":"Your code","body":"123456","data":{"order":"42"},"badge":1,"sound":"default"}Le vecteur complet, avec la clé éphémère et les charges APNs et FCM
{
"description": "HonkIO push encryption v2 (HPKE, RFC 9180). Generated by api/scripts/push-vectors.ts through the API's own seal; do not edit. A device decrypts envelope with recipient.private_key and gets plaintext_json exactly.",
"suite": {
"mode": "base",
"kem": "DHKEM(P-256, HKDF-SHA256)",
"kem_id": 16,
"kdf": "HKDF-SHA256",
"kdf_id": 1,
"aead": "AES-128-GCM",
"aead_id": 1,
"info": "honkio-push-v2",
"aad": ""
},
"encoding": "Keys, enc and ct are base64url without padding. Public keys are 65-byte uncompressed SEC1 points; private keys are the raw 32-byte scalar.",
"recipient": {
"ikm_hex": "499b258aa3aa87a03b95f73ffafc1ca2fec83581239dcb8bcce76d04963f60d0",
"private_key": "_nrSUmsffUCgaGb5gmqkRLXCc_-ls5Dh6sm2Bu6OA3M",
"public_key": "BFGHw33ou72Yj1eGoA53xqL29aB_tk5aVfF2PiySBv06hugrUUHAOoXyzC6xpLBnjBfu8DS3Phd3ascQSTTrYPc",
"kid": "cHRL-UfvREIeEx3Q",
"kid_rule": "first 16 characters of base64url(SHA-256(raw 65-byte public key))"
},
"ephemeral": {
"ikm_hex": "fa4e2613e4bf02ccd0ec037a5657a7c312c67f0ea9a91c04a634e08f2200922a",
"private_key": "caYVkHrwUWToc_5RTA3WOsBb5AyhoC3HVD1H955zz8Q",
"public_key": "BPDblkoWAGabKxtOrs52tL2JaFkPPoqNVP8104Q3C7SwOj8mbh1bw7iLBpr7tWEPglEt4X_DX9Y8BVJnrHo1-wg"
},
"plaintext": {
"title": "Your code",
"body": "123456",
"data": {
"order": "42"
},
"badge": 1,
"sound": "default"
},
"plaintext_json": "{\"title\":\"Your code\",\"body\":\"123456\",\"data\":{\"order\":\"42\"},\"badge\":1,\"sound\":\"default\"}",
"envelope": {
"v": 2,
"kid": "cHRL-UfvREIeEx3Q",
"enc": "BPDblkoWAGabKxtOrs52tL2JaFkPPoqNVP8104Q3C7SwOj8mbh1bw7iLBpr7tWEPglEt4X_DX9Y8BVJnrHo1-wg",
"ct": "SK86q5n7JJTP_HjhHm_7WGk0g2mgJSjE4UyRTnjlQqObn8ntlLw8IJgQ5UasGWOTNuWwmlhl6o-wbXOB9lse8pkvaOlV7SNKs4p54P5nVzLtMoJsp8t8gD1t_m0Y3jSxhoCsRMV-pg"
},
"apns_payload": {
"aps": {
"alert": {
"title": "New notification",
"body": ""
},
"mutable-content": 1,
"badge": 1,
"sound": "default"
},
"hk": {
"v": 2,
"kid": "cHRL-UfvREIeEx3Q",
"enc": "BPDblkoWAGabKxtOrs52tL2JaFkPPoqNVP8104Q3C7SwOj8mbh1bw7iLBpr7tWEPglEt4X_DX9Y8BVJnrHo1-wg",
"ct": "SK86q5n7JJTP_HjhHm_7WGk0g2mgJSjE4UyRTnjlQqObn8ntlLw8IJgQ5UasGWOTNuWwmlhl6o-wbXOB9lse8pkvaOlV7SNKs4p54P5nVzLtMoJsp8t8gD1t_m0Y3jSxhoCsRMV-pg"
}
},
"fcm_data": {
"hk": "{\"v\":2,\"kid\":\"cHRL-UfvREIeEx3Q\",\"enc\":\"BPDblkoWAGabKxtOrs52tL2JaFkPPoqNVP8104Q3C7SwOj8mbh1bw7iLBpr7tWEPglEt4X_DX9Y8BVJnrHo1-wg\",\"ct\":\"SK86q5n7JJTP_HjhHm_7WGk0g2mgJSjE4UyRTnjlQqObn8ntlLw8IJgQ5UasGWOTNuWwmlhl6o-wbXOB9lse8pkvaOlV7SNKs4p54P5nVzLtMoJsp8t8gD1t_m0Y3jSxhoCsRMV-pg\"}"
}
}Limites à connaître
- Le badge, le son et l'identifiant de fil restent en clair sur iOS même pour une notification avec chiffrement, car iOS les applique avant l'exécution de votre extension. La clé de regroupement reste en clair sur iOS et sur Android, car Apple et Google s'en servent pour remplacer une notification plus ancienne. N'y mettez rien de sensible.
- Les messages Android de données seulement sont envoyés par défaut en priorité haute (<code>priority</code> : high) pour réveiller l'application rapidement. Android peut déprioriser une application qui reçoit des messages à priorité haute sans afficher de notification : affichez-en toujours une.
- Le chiffrement et la base64url ajoutent environ un tiers à la taille, plus la clé encapsulée et l'étiquette de l'enveloppe. La limite de 4096 octets s'applique à la charge finale, et PUSH_PAYLOAD_TOO_LARGE indique combien d'octets retrancher. La taille est vérifiée de nouveau à l'envoi : si la charge chiffrée d'un appareil dépasse alors 4096 octets (par exemple, il a enregistré une clé après l'acceptation d'une notification planifiée), cette livraison est refusée avec PUSH_PAYLOAD_TOO_LARGE et remboursée.
- Les jetons d'appareil, votre identifiant de bundle ou de projet et le moment de l'envoi restent visibles pour Apple et Google, tout comme l'identifiant de clé, la clé encapsulée et la longueur de l'enveloppe : la charge scellée fait 16 octets de plus que le texte en clair, donc la taille approximative du contenu n'est pas cachée. Ils peuvent conserver une notification destinée à un téléphone hors ligne jusqu'à sa remise ou son expiration.
- Un appareil garde sa clé actuelle et la précédente : une notification envoyée à une clé d'il y a deux rotations ne peut pas être ouverte et affiche le texte de remplacement. Si la clé conservée ne peut plus être lue, le paquet en crée une nouvelle avec un nouvel identifiant; l'enregistrer à chaque lancement tient honkio à jour.
HonkIO