Push cifrate (E2EE) – Guida semplice

Spiegazione per tutti: cosa fa ogni cosa, a cosa serve quello che scriviamo. Poi esempi di integrazione in più linguaggi.

← Torna a Push E2EE · API complete (riferimento tecnico)

In una frase

Le notifiche push cifrate sono messaggi che solo il tuo dispositivo può leggere. Il server e Google trasportano solo dati illeggibili; il titolo e il testo tornano visibili solo sul tuo browser o telefono, grazie a una chiave privata che resta solo lì. Nessuno in mezzo può leggere il contenuto.

Cosa fa ogni cosa (glossario semplice)

Ogni termine che incontri ha un ruolo preciso. Ecco cosa significa, in parole povere.

  • Dominio (domain)
    È l’indirizzo del sito (es. willypush.it). Il dominio è gestito interamente lato server: viene ricavato automaticamente dall’appId nel database. Il client non invia mai un parametro domain. Ogni appId corrisponde a un dominio specifico; il server lo risolve in autonomia.
  • Token FCM (push_token)
    È un “codice di recapito” che Google assegna al tuo browser quando attivi le notifiche. Senza questo codice il server non saprebbe dove inviare la push. È come un indirizzo di casa per le notifiche.
  • Chiave pubblica e chiave privata
    Sono una coppia: la pubblica la invii al server (serve per cifrare i messaggi che ti manderanno). La privata resta solo sul tuo dispositivo e serve per decifrare. Chi ha solo la chiave pubblica non può leggere i messaggi; solo chi ha la chiave privata può.
  • Registrazione (register)
    È il momento in cui il tuo browser dice al server: “Sono questo dispositivo, questo è il mio token FCM e questa è la mia chiave pubblica; da ora puoi inviarmi notifiche cifrate”. Il server salva solo ciò che serve per inviare (token + chiave pubblica), mai la chiave privata.
  • Invio cifrato (send_encrypted)
    È l’operazione con cui qualcuno (tu dal pannello, o un’applicazione tramite API) manda una notifica. Il messaggio viene cifrato con la chiave pubblica di ogni destinatario; il server e Google vedono solo dati illeggibili. Solo il dispositivo con la chiave privata può decifrare e mostrare titolo e testo.
  • Server
    È il computer che ospita le API (register, send_encrypted, ecc.). Non decifra mai i messaggi: riceve dati già cifrati o titolo/corpo solo per cifrarli subito e inoltrarli. In nessun punto il server “legge” il contenuto della notifica.
  • Google (FCM)
    È il servizio che consegna materialmente la notifica al dispositivo. Riceve dal server solo dati cifrati (enc, ephem) e li recapita. Anche Google non può leggere il contenuto.
  • Service Worker (sw-cifrato.php)
    È un piccolo programma che gira in background nel browser. Quando arriva una push cifrata, la riceve, la decifra con la chiave privata (che è salvata in modo sicuro nel browser) e mostra la notifica all’utente. Senza di lui il browser non saprebbe come decifrare.
  • crypto-client.js
    È lo script che genera la coppia di chiavi, salva la chiave privata in modo sicuro e fornisce le funzioni per cifrare/decifrare. Lo carichi nella pagina dove l’utente clicca “Attiva notifiche cifrate”.
  • X-Willy-App-Id + X-Willy-App-Key
    Sono i due header di autenticazione. X-Willy-App-Id identifica la tua applicazione (account/tenant); X-Willy-App-Key è la chiave segreta associata. Il server verifica la coppia appId + appKey e ricava automaticamente il dominio dal database. Le operazioni sensibili (invio, lista dispositivi cifrati, rimozione dispositivo) richiedono entrambi gli header; se la coppia non è valida la richiesta viene rifiutata (403).
  • Per le richieste web, il server valida anche l'header Origin inviato automaticamente dal browser — questo impedisce l'uso delle credenziali da un sito diverso da quello registrato.

    A cosa serve quello che scriviamo

    Quando integri le push cifrate, inserisci script e chiamate. Ecco a cosa serve ogni pezzo.

    Esempi di integrazione (tutti i linguaggi)

    Sostituisci BASE con l’URL della tua installazione (es. https://TUO_SITO.it/api-push), IL_TUO_APP_ID e LA_TUA_APP_KEY con le credenziali della tua app. Per le push cifrate, la registrazione deve includere public_key e firebase_project_id.

    Dominio gestito lato server: il client non invia mai un parametro domain. Il server ricava il dominio dall’appId nel proprio database. L’autenticazione avviene tramite gli header X-Willy-App-Id e X-Willy-App-Key.

    1. Registrazione device con chiave pubblica (E2EE)

    A cosa serve: dire al server che questo dispositivo vuole ricevere notifiche cifrate e fornire la chiave pubblica con cui cifrare i messaggi.

    cURL

    curl -X POST "BASE/api/register.php" \
      -H "Content-Type: application/json" \
      -H "X-Willy-App-Id: IL_TUO_APP_ID" \
      -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
      -d '{"push_token":"FCM_TOKEN","platform":"web","device_type":"web_browser","public_key":"BASE64URL_CHIAVE_PUBBLICA","firebase_project_id":"tuo-project-id"}' 

    JavaScript (fetch)

    await fetch(BASE + '/api/register.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Willy-App-Id': appId,
        'X-Willy-App-Key': appKey
      },
      body: JSON.stringify({
        push_token: fcmToken,
        platform: 'web',
        device_type: 'web_browser',
        public_key: publicKeyBase64,
        firebase_project_id: window.firebase?.app()?.options?.projectId || ''
      })
    });

    PHP

    $payload = [
      'push_token' => $fcmToken,
      'platform' => 'web',
      'device_type' => 'web_browser',
      'public_key' => $publicKeyBase64,
      'firebase_project_id' => $projectId
    ];
    $ch = curl_init($base . '/api/register.php');
    curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => json_encode($payload),
      CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-Willy-App-Id: ' . $appId,
        'X-Willy-App-Key: ' . $appKey
      ],
      CURLOPT_RETURNTRANSFER => true
    ]);
    $res = curl_exec($ch);

    Python

    import requests
    r = requests.post(BASE + '/api/register.php',
        json={
            'push_token': fcm_token,
            'platform': 'web',
            'device_type': 'web_browser',
            'public_key': public_key_base64,
            'firebase_project_id': project_id
        },
        headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
    data = r.json()

    C# (HttpClient)

    var payload = new {
        push_token = fcmToken,
        platform = "web",
        device_type = "web_browser",
        public_key = publicKeyBase64,
        firebase_project_id = projectId
    };
    using var client = new HttpClient();
    client.DefaultRequestHeaders.Add("X-Willy-App-Id", appId);
    client.DefaultRequestHeaders.Add("X-Willy-App-Key", appKey);
    var json = JsonSerializer.Serialize(payload);
    var content = new StringContent(json, Encoding.UTF8, "application/json");
    var response = await client.PostAsync(baseUrl + "/api/register.php", content);

    2. Invio notifica cifrata (send_encrypted)

    A cosa serve: inviare una notifica il cui titolo e corpo sono cifrati; solo i dispositivi con la chiave privata possono leggerla.

    cURL

    curl -X POST "BASE/api/send_encrypted.php" \
      -H "Content-Type: application/json" \
      -H "X-Willy-App-Id: IL_TUO_APP_ID" \
      -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
      -d '{"title":"Titolo","body":"Solo il destinatario può leggere.","url":"/","target":{}}' 

    JavaScript

    const res = await fetch(BASE + '/api/send_encrypted.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Willy-App-Id': appId,
        'X-Willy-App-Key': appKey
      },
      body: JSON.stringify({
        title: 'Titolo',
        body: 'Solo il destinatario può leggere.',
        url: '/',
        target: {}
      })
    });
    const data = await res.json();
    // data.data.sent, data.data.failed, data.data.total

    PHP

    $body = [
      'title' => 'Titolo',
      'body' => 'Solo il destinatario può leggere.',
      'url' => '/',
      'target' => []
    ];
    $ch = curl_init($base . '/api/send_encrypted.php');
    curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => json_encode($body),
      CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
      CURLOPT_RETURNTRANSFER => true
    ]);
    $res = curl_exec($ch);

    Python

    r = requests.post(BASE + '/api/send_encrypted.php',
        json={
            'title': 'Titolo',
            'body': 'Solo il destinatario può leggere.',
            'url': '/',
            'target': {}
        },
        headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
    print(r.json()['data']['sent'], r.json()['data']['failed'])

    Go

    payload := map[string]interface{}{
        "title":  "Titolo",
        "body":   "Solo il destinatario può leggere.",
        "url":    "/",
        "target": map[string]interface{}{},
    }
    body, _ := json.Marshal(payload)
    req, _ := http.NewRequest("POST", base+"/api/send_encrypted.php", bytes.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-Willy-App-Id", appId)
    req.Header.Set("X-Willy-App-Key", appKey)
    resp, _ := http.DefaultClient.Do(req)

    3. Lista dispositivi con chiave (list_encrypted)

    A cosa serve: sapere quali dispositivi sono iscritti alle push cifrate (per inviare loro messaggi o mostrarli in un pannello).

    cURL

    curl -s -H "X-Willy-App-Id: IL_TUO_APP_ID" -H "X-Willy-App-Key: LA_TUA_APP_KEY" "BASE/api/list_encrypted.php"

    JavaScript

    const res = await fetch(BASE + '/api/list_encrypted.php', {
      headers: { 'X-Willy-App-Id': appId, 'X-Willy-App-Key': appKey }
    });
    const data = await res.json();
    // data.data.devices → array con push_token, public_key, token_hash

    PHP

    $ch = curl_init($base . '/api/list_encrypted.php');
    curl_setopt_array($ch, [
      CURLOPT_HTTPHEADER => ['X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
      CURLOPT_RETURNTRANSFER => true
    ]);
    $data = json_decode(curl_exec($ch), true);

    Python

    r = requests.get(BASE + '/api/list_encrypted.php',
        headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})
    devices = r.json()['data']['devices']

    4. Rimuovi dispositivo (delete_encrypted_device)

    A cosa serve: togliere un dispositivo dalla lista delle push cifrate (es. se l’utente si disiscrive). Serve il token_hash (lo trovi in list_encrypted).

    cURL

    curl -X POST "BASE/api/delete_encrypted_device.php" \
      -H "Content-Type: application/json" \
      -H "X-Willy-App-Id: IL_TUO_APP_ID" \
      -H "X-Willy-App-Key: LA_TUA_APP_KEY" \
      -d '{"token_hash":"abc123def456"}' 

    JavaScript

    await fetch(BASE + '/api/delete_encrypted_device.php', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Willy-App-Id': appId,
        'X-Willy-App-Key': appKey
      },
      body: JSON.stringify({ token_hash: tokenHash })
    });

    PHP

    $ch = curl_init($base . '/api/delete_encrypted_device.php');
    curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => json_encode(['token_hash' => $tokenHash]),
      CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Willy-App-Id: ' . $appId, 'X-Willy-App-Key: ' . $appKey],
      CURLOPT_RETURNTRANSFER => true
    ]);
    curl_exec($ch);

    Python

    requests.post(BASE + '/api/delete_encrypted_device.php',
        json={'token_hash': token_hash},
        headers={'X-Willy-App-Id': app_id, 'X-Willy-App-Key': app_key})

    File da mettere nel sito

    Per un esempio minimo di pagina “Attiva notifiche cifrate” e per la demo live del flusso (log STEP 1–6), vedi API ed esempi di integrazione e E2EE_Willy/demo.html.