Cos’è la crittografia ibrida omomorfa e le sue applicazioni
9.2.2023 10:03
TL;DR
Introdurre il concetto di crittografia omomorfa ibrida, i suoi casi d’uso, una breve formulazione e uncodice dimostrativo in C++.
Introduzione
Le applicazioni che preservano la privacy sono diventate un argomento importante al giorno d’oggi a causa delle crescenti preoccupazioni delle persone sulla privacy dei loro dati, della prevalenza di applicazioni di apprendimento automatico che richiedono l’accesso a una vasta quantità di dati e di nuove normative come il Regolamento generale sulla protezione dei dati (GDPR), per non parlare di altre preoccupazioni etiche e finanziarie. Oggi conosceremo una nuova tecnica di miglioramento della privacy chiamata Hybrid Homomorphic Encryption (HHE), che è un’espansione della crittografia omomorfa (HE).
HE è una tecnica di crittografia che ci permette di eseguire calcoli su dati criptati. Tuttavia, uno dei problemi di HE è che i testi cifrati sono di diversi ordini di grandezza più grandi dei corrispondenti testi in chiaro. HHE mira a risolvere questo problema combinando i cifrari simmetrici con HE per ridurre le dimensioni dei cifrari e le risorse computazionali necessarie per la parte che cripta e invia i dati (ad esempio, un cliente/proprietario dei dati) al costo di calcoli più costosi per la parte che esegue calcoli sui dati crittografati (ad esempio, un server, un Cloud Service Provider o CSP). Pertanto, HHE può essere più adatto di HE quando si tratta del modello client-server di calcoli crittografati, soprattutto quando il client dispone di risorse computazionali e larghezza di banda Internet limitate, ad esempio telefoni, dispositivi IoT, ecc.
Vantaggi:
- Consentire calcoli su dati crittografati e quindi analisi e applicazioni di dati che rispettano la privacy.
- Ridurre le dimensioni del testo cifrato, quindi ridurre le risorse di calcolo e di banda necessarie per chi possiede, cripta e invia i dati.
Svantaggi:
- Più costoso dal punto di vista computazionale sul dominio di calcolo criptato
- Attualmente è ancora limitato ad alcuni tipi di dati e calcoli.
Casi d’uso
Come l’HE, l’HHE può supportare applicazioni in settori in cui la privacy dei dati è una preoccupazione importante, come la finanza, la sanità, le normative, ecc. Inoltre, HHE può potenziare le applicazioni su dispositivi con potenza di calcolo, memoria e larghezza di banda di rete limitate, come i dispositivi embedded e IoT.
Esempio di applicazione: Un’applicazione di sorveglianza domestica per l’assistenza sanitaria, in cui i dispositivi IoT installati in una casa scattano foto (o altri segnali), le criptano e inviano i segnali criptati al server. Il server esegue un algoritmo di intelligenza artificiale sui dati criptati ricevuti e rileva occasioni come la presenza di persone colpite da ictus, quindi invia i risultati criptati al dispositivo della famiglia, che ha il compito di decifrare il risultato e di lanciare un allarme solo quando il risultato decifrato è positivo, ad esempio se ci sono persone colpite da ictus. In questo modo, la famiglia può utilizzare il servizio del server, mentre il fornitore del servizio non vede le immagini o i dati sensibili della famiglia.
Entriamo nel merito della matematica
Di seguito sono riportate alcune brevi formulazioni di HE e HHE.
Crittografia omomorfa
Prima di arrivare a HHE, dobbiamo prima capire HE. Con HE, possiamo crittografare i dati ed eseguire operazioni sui dati crittografati. Il risultato della decrittazione sarà equivalente a quello ottenuto eseguendo operazioni simili sui dati in chiaro corrispondenti. Per capire meglio HE, vi rimando a questo articolo post sul blog da OpenMined.
Vediamo la definizione di schema di crittografia a chiave pubblica omomorfa, tratta da [1] e composta da 4 algoritmi:
- HE.KeyGen(1ⁿ) → (pk, sk, evk): L’algoritmo di generazione delle chiavi. Qui, n è un parametro di sicurezza; pk, sk e evk sono rispettivamente la chiave pubblica, la chiave segreta e la chiave di valutazione. Utilizziamo pk per criptare i dati, sk per decifrare i dati criptati e evk per eseguire calcoli su dati crittografati
- HE.Enc(pk, m) → c: L’algoritmo di cifratura HE dove m è il dato in chiaro e c è il dato cifrato HE
- HE.Eval(evk, f, c₁, c₂, … cᵢ) → c’: L’algoritmo di valutazione dove f è una funzione come l’addizione o la moltiplicazione, e c‘ è il risultato criptato HE. Dovremmo avere HE.Dec(sk, c’) = f(m₁, m₂, …, mᵢ)
- HE.Dec(sk, c) → m: L’algoritmo di decrittazione HE che prende sk e il testo cifrato c per creare il messaggio in chiaro m
Crittografia ibrida omomorfa
Invece di criptare i dati con uno schema HE che produce un testo cifrato molto grande (espansione di ordine multiplo rispetto al testo in chiaro), HHE li cripta con un cifrario simmetrico con fattore di espansione pari a 1 e invia i testi cifrati simmetrici al server. Inoltre, il cliente deve inviare una versione crittografata in modo omomorfico della propria chiave simmetrica. Alla ricezione, il server esegue l’algoritmo di decrittazione simmetrica in modo omomorfico per trasformare il testo cifrato simmetrico in un testo cifrato omomorfico. Successivamente, il server può eseguire calcoli sui dati criptati HE. Più formalmente, possiamo definire uno schema HHE (secondo [2]) che consiste in 5 algoritmi come segue
- HHE.KeyGen(1ⁿ) →(pk, sk, evk): È semplicemente l’algoritmo HE.KeyGen che produce la chiave pubblica HE(pk), la chiave segreta(sk) e la chiave di valutazione(evk).
- HHE.Enc(1ⁿ, pk, m): L’algoritmo di crittografia HHE.
Innanzitutto, crea una chiave simmetrica: SYM.KGen(1ⁿ) → k
Quindi, utilizzando questa chiave simmetrica, cripta il messaggio di testo in chiaro m: SYM.Enc(k, m) → cₛ. Qui, cₛ è il testo cifrato simmetrico che verrà inviato al server. Si noti che cₛ ha la stessa dimensione rispetto a m.
Inoltre, HHE.Enc cripta in modo omomorfico anche la chiave simmetrica k utilizzando HE.Enc(pk, k) → cₖ. Quindi, cₖ è il testo cifrato HE della chiave simmetrica k, e sarà anche inviato al server insieme a cₛ - HHE.Decomp(evk, cₖ, cₛ) → c: L’algoritmo di decomposizione HHE che trasforma il testo cifrato simmetrico cₛ nel testo cifrato HE c valutando in modo omomorfico l’algoritmo di decrittazione simmetrica usando cₖ e cₛ: HE.Eval(evk, f=SYM.Dec, cₖ, cₛ) → c
- HHE.Eval(evk, f, c₁, . . . , cᵢ) → c’: L’algoritmo di valutazione HHE che restituisce semplicemente HE.Eval(evk, f, c₁, . . . , cᵢ)
- HHE.Dec(sk, c): L’algoritmo di decrittazione HHE. Restituisce semplicemente HE.Dec(sk, c)
Si noti che nel passo 2 si deve inviare cₖ e cₛ al server. Qui, cₖ è il testo cifrato HE e può essere di grandi dimensioni. Tuttavia, è sufficiente inviare cₖ al server una volta, ad esempio in una fase di configurazione. Il server può utilizzarlo ripetutamente nel file HHE.Decomp algoritmo per trasformare i nuovi cifrari simmetrici nei corrispondenti cifrari HE. Questa è la differenza chiave tra HHE e HE: invece di inviare ogni volta al server i testi cifrati HE, che possono richiedere molta larghezza di banda, HHE invia testi cifrati simmetrici leggeri. Questo trucco rende HHE in grado di funzionare con dispositivi a risorse limitate, poiché i cifrari simmetrici sono anche molto leggeri da eseguire.
Siete pronti per un po’ di codice?
Prima di immergerci nel codice, rivediamo il protocollo che costruiremo: Abbiamo due parti (un client e un server) le cui azioni possono essere riassunte in 3 fasi principali:
- Il client crea le chiavi con HHE.KeyGen, cripta i dati con HHE.Enc e invia al server il testo cifrato simmetrico dei suoi dati(cₛ), il testo cifrato HE della sua chiave simmetrica(cₖ), le chiavi HE tranne la chiave segreta sk .
- Il server esegue l’algoritmo HHE.Decomp e una trasformazione lineare sui dati crittografati HE del client utilizzando HHE.Eval, ottiene il risultato crittografato e lo invia nuovamente al client.
- Alla ricezione, il client decifra il risultato con HHE.Dec e ottiene l’output finale in chiaro.
L’intero codice dimostrativo è in C++ e si basa sul sistema Il SEAL di Microsoft e Biblioteca della PASTA. Per prima cosa, creiamo due strutture che rappresentano il client e il server:
struct Client
{
// the HE keys
seal::PublicKey he_pk; // HE public key
seal::SecretKey he_sk; // HE secret key
seal::RelinKeys he_rk; // HE relinearization key (you don't have to care about this)
seal::GaloisKeys he_gk; // HE galois key (you don't have to care about this)
// client's symmetric keys
std::vector<uint64_t> k; // the secret symmetric keys
std::vector<seal::Ciphertext> c_k; // the HE encrypted symmetric keys
// client's data
std::vector<uint64_t> m{0, 5, 255, 100, 255}; // the client's secret data
std::vector<uint64_t> c_s; // the symmetric encrypted data
seal::Ciphertext c_res; // the HE encrypted result received from the server
};
struct Server
{
std::vector<int64_t> w{-1, 2, -3, 4, 5}; // dummy weights
std::vector<int64_t> b{-5, -5, -5, -5, -5}; // dummy biases
std::vector<seal::Ciphertext> c; // the HE encrypted ciphertext of client's data
seal::SecretKey he_sk; // the server's HE secret key
seal::Ciphertext c_res; // the HE encrypted results that will be sent to the client
};
Client client;
Server server;
Passo 1
Il client crea il contesto SEAL, responsabile della creazione delle chiavi HE e di altri oggetti SEAL per la codifica, la crittografia e la decodifica dei dati (BatchEncoder, Encryptor, Decryptor, Evaluator).
std::shared_ptr<seal::SEALContext> context = sealhelper::get_seal_context();
sealhelper::print_parameters(*context);
seal::KeyGenerator keygen(*context);
keygen.create_public_key(client.he_pk);
client.he_sk = keygen.secret_key();
keygen.create_relin_keys(client.he_rk);
seal::BatchEncoder he_benc(*context);
seal::Encryptor he_enc(*context, client.he_pk);
seal::Evaluator he_eval(*context);
seal::Decryptor he_dec(*context, client.he_sk);
bool use_bsgs = false;
std::vector<int> gk_indices = pastahelper::add_gk_indices(use_bsgs, he_benc);
keygen.create_galois_keys(gk_indices, client.he_gk);
Il client esegue quindi l’algoritmo di crittografia(HHE.Enc) per creare la chiave simmetrica(client.k) e il testo cifrato simmetrico(client.c_s).
client.k = pastahelper::get_symmetric_key();
pasta::PASTA SymmetricEncryptor(client.k, configs::plain_mod);
client.c_s = SymmetricEncryptor.encrypt(client.m);
Se stampiamo i valori di client.c_s, vedremo un vettore di valori casuali come [30446, 62410, 62969, 38863, 43376], in contrapposizione ai dati in chiaro del client [0, 5, 255, 100, 255]. Il client invia al server solo il vettore di valori casuali e mai i suoi dati in chiaro.
Successivamente, il cliente cripta la sua chiave simmetrica(client.k) utilizzando HE per creare client.c_k.
client.c_k = pastahelper::encrypt_symmetric_key(client.k,
configs::USE_BATCH,
he_benc,
he_enc);
Successivamente, il client invia al server client.c_k, client.c_s e le chiavi HE, ad eccezione della chiave segreta.
Passo 2
Dopo aver ricevuto il client.c_k, il server crea la propria chiave segreta HE, l’oggetto HHE ed esegue l’algoritmo di decomposizione che risulta in server.c che è il testo cifrato HE del messaggio in chiaro del cliente m. Si noti che il client non invia mai la sua chiave segreta he_sk al server, in modo che il server non sia in grado di decifrare server.c.
seal::KeyGenerator csp_keygen(*context);
server.he_sk = csp_keygen.secret_key();
pasta::PASTA_SEAL HHE(context, client.he_pk, server.he_sk, client.he_rk, client.he_gk);
server.c = HHE.decomposition(client.c_s, client.c_k, configs::USE_BATCH);
Il server codifica quindi i suoi pesi w e le sue polarizzazioni b ed esegue una moltiplicazione vettoriale element-wise e un’addizione sui suoi pesi e polarizzazioni in chiaro con i dati criptati HE server.c.
seal::Plaintext plain_w, plain_b;
he_benc.encode(server.w, plain_w);
he_benc.encode(server.b, plain_b);
server.c_res = sealhelper::he_mult(he_eval, server.c[0], plain_w);
client.c_res = sealhelper::he_add(he_eval, server.c_res, plain_b);
Si può notare che il risultato finale è client.c_res, che è il testo cifrato SEAL che il client riceverà.
Passo 3
Infine, il client decifra il suo c_res utilizzando la sua chiave segreta:
std::vector<int64_t> decrypted_res = sealhelper::decrypt(client.c_res,
client.he_sk,
he_benc,
*context,
client.m.size());
Stampando decrypted_res, vedremo che il risultato sarà [-5 5 -770 395 1270], che è corretto perché
[0, 5, 255, 100, 255]
⊙
[-1, 2, -3, 4, 5]
⊕
[-5, -5, -5, -5, -5]
=
[-5, 5, -770, 395, 1270]
dove ⊙, ⊕ sono rispettivamente la moltiplicazione e l’addizione vettoriale elementare.
Il risultato dell’esecuzione del codice dimostrativo è visibile nell’immagine seguente
Direzioni future e conclusioni
In questo articolo abbiamo imparato a conoscere la crittografia omomorfa ibrida, i suoi vantaggi rispetto alla crittografia omomorfa semplice, un caso d’uso esemplificativo di HHE e abbiamo anche assistito a un protocollo dimostrativo molto semplice in C++. In pratica, questo protocollo può essere esteso a 3 parti, il che è adatto all’analisi dei dati criptati o all’apprendimento automatico. Per saperne di più sul protocollo HHE a 3 parti, è possibile consultare un documento pubblicato di recente [3] presso il nostro sito web Laboratorio NISEC all’Università di Tampere. Spero che questo articolo vi sia utile e che nel frattempo vi divertiate a imparare qualcosa di nuovo!
Riconoscimento
Questo lavoro è stato finanziato dalprogetto HARPOCRATES dell’UE .
Riferimento
[1] Brakerski, Zvika e Vinod Vaikuntanathan. “Crittografia completamente omomorfa efficiente da LWE (standard)”. Rivista SIAM sull’informatica 43.2 (2014): 831-871.
[2] Dobraunig, Christoph, et al. “Pasta: un caso di crittografia omomorfa ibrida”. Archivio ePrint di crittologia (2021).
[3] Alexandros Bakas, Eugene Frimpong, Antonis Michalas. “Travestimento simmetrico: Realizzazione di servizi di crittografia omomorfica da primitivi simmetrici”. EAI SECURECOMM (2022).
Scritto da: Khoa Nguyen, Università di Tampere