Qué es el cifrado homomórfico híbrido y sus aplicaciones
9.2.2023 10:03
TL;DR
Introducir el concepto de cifrado homomórfico híbrido, sus casos de uso, una breve formulación y algo decódigo de demostración en C++.
Introducción
Las aplicaciones de preservación de la privacidad se han convertido en un tema importante hoy en día debido a la creciente preocupación de las personas por la privacidad de sus datos, la prevalencia de aplicaciones de aprendizaje automático que requieren acceso a una gran cantidad de datos y nuevas regulaciones como el Reglamento General de Protección de Datos (GDPR), por no mencionar otras preocupaciones éticas y financieras. Hoy conoceremos una novedosa técnica de mejora de la privacidad llamada Cifrado Homomórfico Híbrido (HHE), que es una expansión del Cifrado Homomórfico (HE).
HE es una técnica de cifrado que nos permite realizar cálculos sobre datos cifrados. Sin embargo, uno de los problemas de HE es que sus textos cifrados son varios órdenes de magnitud mayores que los correspondientes textos planos. HHE pretende resolver este problema combinando cifrados simétricos con HE para reducir el tamaño de los textos cifrados y los recursos computacionales necesarios para la parte que cifra y envía los datos (por ejemplo, un cliente / propietario de los datos) a costa de cálculos más caros para la parte que realiza cálculos sobre los datos cifrados (por ejemplo, un servidor, un proveedor de servicios en la nube o CSP). Por lo tanto, HHE puede ser más adecuado que HE cuando se trata del modelo cliente-servidor de cálculos cifrados, especialmente cuando el cliente tiene recursos computacionales y ancho de banda de Internet limitados, por ejemplo, teléfonos, dispositivos IoT, etc.
Ventajas:
- Permitir cálculos sobre datos cifrados y, por tanto, análisis y aplicaciones de datos que preserven la privacidad.
- Reducir el tamaño del texto cifrado y, por tanto, reducir los recursos informáticos y de ancho de banda necesarios para la parte que posee, cifra y envía los datos.
Desventajas:
- Más costoso computacionalmente en el dominio de cálculo encriptado
- Actualmente sigue restringido a determinados tipos de datos y cálculos
Casos prácticos
Al igual que la ES, la HHE puede admitir aplicaciones en sectores en los que la privacidad de los datos es una preocupación importante, como las finanzas, la sanidad, la normativa, etc. Además, HHE puede potenciar las aplicaciones en dispositivos con potencia de cálculo, memoria y ancho de banda de red limitados, como los dispositivos integrados y de IoT.
Ejemplo de aplicación: Una aplicación de vigilancia doméstica para la atención sanitaria, en la que los dispositivos IoT equipados en un hogar toman fotografías (u otras señales), las cifran y envían las señales cifradas al servidor. El servidor ejecuta un algoritmo de inteligencia artificial en los datos cifrados recibidos y detecta ocasiones como personas que sufren un ictus; a continuación, envía los resultados cifrados al dispositivo del hogar, que se encarga de descifrar el resultado y provocar una alarma sólo cuando el resultado descifrado es positivo, por ejemplo, si hay personas que sufren ictus. De este modo, el hogar puede utilizar el servicio del servidor mientras el proveedor de servicios no ve ninguna foto o dato sensible del hogar.
Entremos en materia
A continuación se presentan algunas formulaciones breves de la HE y la HHE.
Cifrado homomórfico
Antes de llegar a la HHE, tenemos que entender primero la HE. Con HE, podemos cifrar los datos y realizar operaciones con los datos cifrados. El resultado al descifrar será equivalente al resultado al realizar operaciones similares en los datos de texto plano correspondientes. Para comprender mejor a HE, le remito a esto entrada del blog de OpenMined.
Veamos aquí la definición de un esquema de cifrado homomórfico de clave pública que se adopta de [1] y consta de 4 algoritmos:
- HE.KeyGen(1ⁿ) → (pk, sk, evk): El algoritmo de generación de claves. Toma, n es un parámetro de seguridad; pk, sk y evk son la clave pública, la clave secreta y la clave de evaluación, respectivamente. Utilizamos pk para encriptar los datos, sk para descifrar los datos cifrados, y evk realizar cálculos sobre datos cifrados
- HE.Enc(pk, m) → c: El algoritmo de cifrado HE donde m son los datos en texto plano y c son los datos cifrados HE.
- HE.Eval(evk, f, c₁, c₂, … cᵢ) → c’: El algoritmo de evaluación. donde f es una función como la suma o la multiplicación, y c‘ es el resultado cifrado HE. Deberíamos tener HE.Dec(sk, c’) = f(m₁, m₂, …, mᵢ)
- HE.Dec(sk, c) → m: El algoritmo de descifrado HE. que lleva sk y el texto cifrado c para crear el mensaje en texto plano m
Cifrado homomórfico híbrido
En lugar de cifrar los datos con un esquema HE que produce un texto cifrado muy grande (expansión de múltiples órdenes en comparación con el texto plano), HHE los cifra con un cifrado simétrico con el factor de expansión de 1 y envía los textos cifrados simétricos al servidor. Además, el cliente también debe enviar una versión cifrada homomórfica de su clave simétrica. Tras la recepción, el servidor realiza el algoritmo de descifrado simétrico homomórfico para transformar el texto cifrado simétrico en un texto cifrado homomórfico. Después, el servidor puede realizar cálculos sobre los datos encriptados. Más formalmente, podemos definir un esquema HHE (según [2]) que consta de 5 algoritmos como sigue
- HHE.KeyGen(1ⁿ) →(pk, sk, evk): Se trata simplemente del algoritmo HE.KeyGen que produce la clave pública HE(pk), la clave secreta(sk) y la clave de evaluación(evk)
- HHE.Enc(1ⁿ, pk, m): El algoritmo de cifrado HHE.
En primer lugar, crea una clave simétrica: SYM.KGen(1ⁿ) → k
A continuación, utilizando esta clave simétrica, cifra el mensaje en texto plano m: SYM.Enc(k, m) → cₛ. Toma, cₛ es el texto cifrado simétrico que se enviará al servidor. Tenga en cuenta que cₛ tiene el mismo tamaño que m.
Además, HHE.Enc también encripta homomórficamente la clave simétrica k utilizando HE.Enc(pk, k) → cₖ. Por lo tanto, cₖ es el texto cifrado HE de la clave simétrica k, y también se enviará al servidor junto con cₛ - HHE.Decomp(evk, cₖ, cₛ) → c: El algoritmo de descomposición HHE que transforma el texto cifrado simétrico cₛ en el texto cifrado HE c evaluando homomórficamente el algoritmo de descifrado simétrico mediante cₖ y cₛ: HE.Eval(evk, f=SYM.Dec, cₖ, cₛ) → c
- HHE.Eval(evk, f, c₁, . . . , cᵢ) → c’: El algoritmo de evaluación HHE que simplemente devuelve HE.Eval(evk, f, c₁, .. . , cᵢ).
- HHE.Dec(sk, c): El algoritmo de descifrado HHE. Simplemente devuelve HE.Dec(sk, c)
Tenga en cuenta que en el paso 2, tenemos que enviar cₖ y cₛ al servidor. Toma, cₖ es el texto cifrado HE y puede ser de gran tamaño. Sin embargo, sólo tenemos que enviar cₖ al servidor una vez, por ejemplo, en una fase de configuración. El servidor puede utilizarlo repetidamente en el HHE.Decomp para convertir nuevos textos cifrados simétricos en los correspondientes textos cifrados HE. Esta es la diferencia clave entre HHE y HE: en lugar de enviar textos cifrados HE cada vez al servidor, lo que puede consumir mucho ancho de banda, HHE envía textos cifrados simétricos ligeros. Este truco hace que HHE pueda funcionar con dispositivos de recursos limitados, ya que los cifrados simétricos también son muy ligeros de ejecutar.
¿Estás listo para el código?
Antes de sumergirnos en el código, repasemos el protocolo que vamos a construir: Tenemos 2 partes (un cliente y un servidor) cuyas acciones pueden resumirse en 3 pasos principales:
- El cliente crea las claves con HHE.KeyGen, cifra los datos con HHE.Enc y envía al servidor el texto cifrado simétrico de sus datos(cₛ), el texto cifrado HE de su clave simétrica(cₖ), las claves HE excepto la clave secreta sk .
- El servidor ejecuta el algoritmo HHE.Decomp y una transformación lineal sobre los datos encriptados HE del cliente utilizando HHE.Eval, obtiene el resultado encriptado y lo devuelve al cliente.
- Tras la recepción, el cliente descifra el resultado con HHE.Dec y obtiene la salida final en texto plano.
El pleno código de demostración está en C++ y se basa en la arquitectura El SEAL de Microsoft y Biblioteca PASTA. En primer lugar, vamos a hacer 2 structs que representan el cliente y el servidor:
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;
Primer paso
El cliente crea el contexto SEAL que es responsable de crear las claves HE y también otros objetos SEAL para codificar, encriptar y desencriptar los datos (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);
A continuación, el cliente ejecuta el algoritmo de cifrado(HHE.Enc) para crear la clave simétrica(client.k) y el texto cifrado simétrico(client.c_s).
client.k = pastahelper::get_symmetric_key();
pasta::PASTA SymmetricEncryptor(client.k, configs::plain_mod);
client.c_s = SymmetricEncryptor.encrypt(client.m);
Si imprimimos los valores de client.c_s, veremos un vector de valores aleatorios como [30446, 62410, 62969, 38863, 43376], frente a los datos de texto plano del cliente [0, 5, 255, 100, 255]. El cliente sólo enviará el vector de valores aleatorios al servidor, y nunca sus datos en texto plano.
A continuación, el cliente cifra su clave simétrica(client.k) utilizando HE para crear client.c_k.
client.c_k = pastahelper::encrypt_symmetric_key(client.k,
configs::USE_BATCH,
he_benc,
he_enc);
Después de esto, el cliente envía client.c_k, client.c_s y las claves HE excepto la clave secreta al servidor.
Paso 2
Tras recibir el client.c_k, el servidor crea su propia clave secreta HE, el objeto HHE y realiza el algoritmo de descomposición que da como resultado server.c que es el texto cifrado HE del mensaje de texto plano del cliente. m. Tenga en cuenta que el cliente nunca envía su clave secreta he_sk al servidor, por lo que el servidor no será capaz de descifrar 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);
A continuación, el servidor codifica sus pesos w y sus sesgos b y realiza una multiplicación vectorial por elementos, así como una suma, de sus pesos y sesgos en texto plano con los datos codificados HE servidor.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);
Podemos ver que el resultado final es client.c_res que es el texto cifrado SEAL que recibirá el cliente.
Paso 3
Por último, el cliente descifra su c_res utilizando su clave secreta:
std::vector<int64_t> decrypted_res = sealhelper::decrypt(client.c_res,
client.he_sk,
he_benc,
*context,
client.m.size());
Imprimiendo decrypted_res, veremos que el resultado será [-5 5 -770 395 1270], lo cual es correcto porque
[0, 5, 255, 100, 255]
⊙
[-1, 2, -3, 4, 5]
⊕
[-5, -5, -5, -5, -5]
=
[-5, 5, -770, 395, 1270]
donde ⊙, ⊕ son la multiplicación y la suma de vectores por elementos, respectivamente.
El resultado al ejecutar el código de demostración puede verse en la siguiente imagen
Orientaciones futuras y conclusiones
En este artículo, aprendimos sobre el cifrado homomórfico híbrido, sus ventajas sobre el cifrado homomórfico simple, un ejemplo de uso de HHE y también recorrimos un protocolo de demostración muy simple en C++. En la práctica, este protocolo puede ampliarse a 3 partes, lo que resulta adecuado para el análisis de datos cifrados o el aprendizaje automático. Puede obtener más información sobre el protocolo HHE tripartito en un documento publicado recientemente [3] en nuestro Laboratorio NISEC en la Universidad de Tampere. Espero que este artículo le resulte útil y que, mientras tanto, se divierta aprendiendo algo nuevo.
Agradecimiento
Este trabajo ha sido financiado por elproyecto HARPOCRATES de la UE .
Referencia
[1] Brakerski, Zvika, y Vinod Vaikuntanathan. “Cifrado totalmente homomórfico eficiente a partir de LWE (estándar)”. SIAM Journal on computing 43.2 (2014): 831-871.
[2] Dobraunig, Christoph, et al. “Pasta: un caso de cifrado homomórfico híbrido”. Archivo Cryptology ePrint (2021).
[3] Alexandros Bakas, Eugene Frimpong, Antonis Michalas. “Disfraz simétrico: Realizing Homomorphic Encryption Services from Symmetric Primitives”. EAI SECURECOMM (2022).
Escrito por: Khoa Nguyen, Universidad de Tampere