KEMBAR78
What This Code Does | PDF | Encryption | Secrecy
0% found this document useful (0 votes)
33 views15 pages

What This Code Does

The document outlines a process for establishing a CKKS homomorphic encryption context using TenSEAL, preparing a one-hot query vector, and communicating with a TCP server to perform private information retrieval. It details common pitfalls such as handling secret keys, deserialization, and ensuring proper context and key management. Additionally, it provides a minimal client version and server outline for implementing the homomorphic operations securely and efficiently.

Uploaded by

subhojitsaha792
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
33 views15 pages

What This Code Does

The document outlines a process for establishing a CKKS homomorphic encryption context using TenSEAL, preparing a one-hot query vector, and communicating with a TCP server to perform private information retrieval. It details common pitfalls such as handling secret keys, deserialization, and ensuring proper context and key management. Additionally, it provides a minimal client version and server outline for implementing the homomorphic operations securely and efficiently.

Uploaded by

subhojitsaha792
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 15

What this code does

Establishes a CKKS homomorphic encryption context using TenSEAL and prepares a one-
hot query vector of length 5 with a 1 at index 3.
Connects to a TCP server on localhost:9990.
Sends the serialized TenSEAL context and the serialized encrypted query to the server
using a 4-byte length-prefixed framing protocol.
Receives a length-prefixed encrypted response, reconstructs a CKKS vector from it with the
same context, and decrypts to print the plaintext result.
This is a typical setup for a private information retrieval (PIR)-like toy demo: the client hides
which index it’s querying by encrypting a one-hot vector, the server homomorphically computes
a selection against its database, and returns an encrypted answer that only the client can
decrypt.

Common pitfalls and how to fix them


1. Missing secret keys for decryption on the client
TenSEAL’s context must include the secret key for decryption on the client and must NOT
be sent to the server.
By default, context.serialize() includes everything, including secret key, unless you explicitly
strip it. For privacy, you should:
Send to the server a copy of the context with only public data (no secret key).
Keep locally a full context (with secret key) for decryption.
Example change:

# After creating the context and before sending it:


context.make_context_public() # removes secret key from this context instance

# If you need to keep a decryptable context locally, create a separate one first:
# full_context = context # keep this for local decrypt()
# server_context = ts.context_from(full_context.serialize()) # or deepcopy
# server_context.make_context_public()
# send_msg(s, server_context.serialize())

If you call make_context_public() on the only context you have, you won’t be able to decrypt
later. So either:
Keep two contexts: one with secret key for local decrypt, one public-only to send; or
Serialize a public copy via context.serialize(save_public_key=True, save_secret_key=False,
save_galois_keys=True, save_relin_keys=True) if your TenSEAL version supports keyword
options.
2. Deserialization on receive
You’re doing:
response = ts.ckks_vector_from(context, response_enc)

That expects response_enc to be the raw bytes of a serialized CKKS vector. Ensure the
server sends exactly that (vector.serialize()). If the server sends a ciphertext or some other
structure, you must match it on the client. A common alternative is:
response = ts.ckks_vector_from(context, response_enc)
plaintext = response.decrypt()

which is fine if response_enc is a serialized CKKS vector.


3. Galois/relinearization keys
You generate Galois keys (context.generate_galois_keys()) locally. The server will need them
to perform rotations on CKKS vectors when computing the selection. Ensure the server has
them:
Include Galois keys in the public context you send.
If the server also needs relinearization (e.g., after multiplications), generate and include
relinearization keys:
context.generate_relin_keys()

Then ensure they’re preserved in the public serialization you send.


4. Sending too much: avoid leaking the secret key
Never send the secret key to the server. Verify by checking TenSEAL’s context flags after
deserialization on the server that the secret key is absent.
5. Protocol framing correctness
You’re using a 4-byte big-endian length prefix; this is correct and typical. Ensure the server
mirrors the same framing for both messages.
recv_msg returns None on EOF; handle that gracefully.
6. Server expectation for the context first
Your send order is: context, then query. Make sure the server reads in that order.
7. CKKS scale and parameters
CKKS global scale is set to 2**40, which is common for coeff_mod_bit_sizes. Ensure the
server operates without rescale mismatch and returns a ciphertext at a compatible level for
client decryption. If the server does multiple multiplications/rescales, levels may drop; plan
modulus chain accordingly.
Minimal, safer client version
This variant keeps a private context for decryption and sends a public-only context to the
server. It also includes relinearization keys.

import tenseal as ts
import socket
import struct

def send_msg(sock, obj_bytes):


length = struct.pack("!I", len(obj_bytes))
sock.sendall(length + obj_bytes)

def recvall(sock, n):


data = b''
while len(data) < n:
packet = sock.recv(n - len(data))
if not packet:
return None
data += packet
return data

def recv_msg(sock):
raw_len = recvall(sock, 4)
if not raw_len:
return None
msg_len = struct.unpack("!I", raw_len)[0]
return recvall(sock, msg_len)

database_size = 5
desired_index = 3

# Build a private context for local encryption/decryption


private_ctx = ts.context(
ts.SCHEME_TYPE.CKKS,
poly_modulus_degree=8192,
coeff_mod_bit_sizes=[60, 40, 40, 60]
)
private_ctx.global_scale = 2**40
private_ctx.generate_galois_keys()
private_ctx.generate_relin_keys()

# Build the query


query_vector = *database_size
query_vector[desired_index] = 1
query_enc = ts.ckks_vector(private_ctx, query_vector)

# Create a public copy to send to server (no secret key)


public_ctx = ts.context_from(private_ctx.serialize())
public_ctx.make_context_public() # strips secret key

# Connect and exchange


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 9990))
print("Connected to server")
send_msg(s, public_ctx.serialize())
send_msg(s, query_enc.serialize())

print("Query sent")
response_enc = recv_msg(s)
if response_enc is None:
raise RuntimeError("No response from server")

# Reconstruct and decrypt using the private context


response_vec = ts.ckks_vector_from(private_ctx, response_enc)
print("Decrypted response:", response_vec.decrypt())
s.close()

What the server should do (outline)


Read the context bytes, load as TenSEAL context: ctx = ts.context_from(buf)
Confirm no secret key is present; ensure Galois/relin keys are available if needed.
Read the encrypted query vector.
Compute the homomorphic selection:
If the database is in plaintext vector d of length N, do an element-wise multiply and sum
via rotations:
acc = enc_query * d
For i in 1..N-1: acc += rotate(enc_query, i) * d[i]
Or use a method that packs the DB and performs a dot product.
Serialize the result vector and send as a length-prefixed message.
Note: Real PIR protocols avoid leaking access patterns and optimize bandwidth/compute; this
example is a didactic pattern.

Troubleshooting
If decryption returns large noise or wrong values, check:
Scale alignment and rescale steps on the server.
Modulus levels remaining after operations; adjust coeff_mod_bit_sizes or reduce
multiplications.
Ensure both sides use identical poly_modulus_degree and modulus chain.
If deserialization fails:
Confirm the server sends the exact serialized CKKS vector bytes.
Verify no extra framing or JSON wrapping is added server-side.
If the server needs rotations but fails:
Ensure Galois keys are present in the public context; regenerate and resend if missing.
If sharing the server code or error messages, it’s possible to pinpoint the exact fix.
Line-by-line explanation of the code
Below is a concise walkthrough of what each line or block is doing, in order.

Imports
import tenseal as ts
Imports the TenSEAL library (homomorphic encryption in Python) and aliases it as ts.
import socket
Imports Python’s socket module for TCP/IP networking.
import struct
Imports struct to pack/unpack binary data (used here for length-prefixing messages).

Helper functions for framed I/O


def send_msg(sock, obj_bytes):
Defines a function to send a single binary message with a 4-byte length prefix.
length = struct.pack("!I", len(obj_bytes))
Packs the length of obj_bytes as an unsigned 32-bit integer in network (big-endian) byte
order. "!I" means network-endian unsigned int.
sock.sendall(length + obj_bytes)
Sends the 4-byte length followed by the actual bytes; sendall ensures all bytes are
transmitted.
def recvall(sock, n):
Defines a helper to read exactly n bytes from the socket, looping until all are received or
connection closes.
data = b''
Initializes an empty bytes buffer.
while len(data) < n:
Loops until n bytes are collected.
packet = sock.recv(n - len(data))
Receives the remaining number of bytes required.
if not packet:
return None
If the peer closed the connection or no data, abort and return None.
data += packet
Appends received bytes to the buffer.
return data
Returns the accumulated bytes once n bytes have been read.
def recv_msg(sock):
Defines a function to receive a single framed message using the 4-byte length prefix.
raw_len = recvall(sock, 4)
Reads the 4-byte length header.
if not raw_len:
return None
If header not received, return None.
msg_len = struct.unpack("!I", raw_len)
Unpacks the 4 bytes into an integer message length (big-endian unsigned int).
return recvall(sock, msg_len)
Reads and returns exactly msg_len bytes of the message payload.

Homomorphic encryption setup


database_size = 5
Sets the toy database length to 5 elements.
desired_index = 3
Chooses the target index to query (0-based), i.e., the 4th element.
context = ts.context(
ts.SCHEME_TYPE.CKKS,
poly_modulus_degree=8192,
coeff_mod_bit_sizes=
)
Creates a TenSEAL encryption context for the CKKS scheme with given parameters:
poly_modulus_degree=8192 controls polynomial ring dimension (security/performance
tradeoff).
coeff_mod_bit_sizes defines the modulus chain with 4 primes of bit sizes 60,40,40,60,
enabling a few multiplications/rescales.
context.global_scale = 2**40
Sets the CKKS scaling factor used for encoding/operations; 2^40 matches the 40-bit middle
primes.
context.generate_galois_keys()
Generates Galois keys required for ciphertext rotations (useful for vector operations on the
server side).

Build the encrypted query


query_vector = *database_size
Initializes a length-5 vector of zeros.
query_vector[desired_index] = 1
Turns it into a one-hot vector with a 1 at index 3. This is a private selection vector.
query_enc = ts.ckks_vector(context, query_vector)
Encrypts the query vector under the given CKKS context, producing an encrypted vector.
Networking: connect and exchange
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Creates a TCP socket (IPv4, stream-oriented).
s.connect(('localhost', 9990))
Connects to a server on the same machine at port 9990.
print("Connected to server")
Logs that the connection was established.
send_msg(s, context.serialize())
Serializes the TenSEAL context to bytes and sends it to the server with a length prefix.
Note: this serialization may include secret key unless made public-only; in real deployments,
the secret key should not be sent.
send_msg(s, query_enc.serialize())
Serializes and sends the encrypted query vector to the server.
print("Query sent")
Logs that the query has been transmitted.

Receive, reconstruct, and decrypt response


response_enc = recv_msg(s)
Receives the server’s length-prefixed response bytes, expected to be a serialized CKKS
vector.
response = ts.ckks_vector_from(context, response_enc)
Reconstructs a TenSEAL CKKS vector object from the received serialized bytes using the
same context.
print("Decrypted response:", response.decrypt())
Decrypts the response using the secret key in the context and prints the plaintext vector (or
values). This assumes the context still contains the secret key and that the server returned a
compatible ciphertext.
s.close()
Closes the TCP connection.

What the overall program does


Prepares a homomorphically encrypted one-hot vector selecting index 3 from a length-5
database.
Sends the HE context and encrypted query to a server over TCP with a simple length-
prefixed protocol.
Receives an encrypted result, reconstructs it, and decrypts locally to reveal the plaintext
answer.
Important caveats and best practices
Secret key safety: Serializing and sending the raw context might include the secret key. In
practice, send only a public context (strip the secret key) and keep a private context locally
for decryption.
Server-side operations: The server must understand TenSEAL’s serialized formats and use
the provided public context and keys (e.g., Galois keys) to compute on the encrypted
query.
Parameter/scale alignment: Ensure the server’s computation respects CKKS scaling and
modulus levels so the client can decrypt successfully.
Framing symmetry: The server must read context first, then query; and respond with a
serialized CKKS vector using the same 4-byte length prefix.

Explanation of the server code


Below is a clear walkthrough of what each part of the server does and why, followed by
important correctness and security notes for TenSEAL/CKKS in this setup.

What the server is set up to do


Waits for a client connection on localhost:9990.
Receives a serialized TenSEAL context, then a serialized encrypted query vector.
Homomorphically computes a private information retrieval (PIR)-style dot product between a
plaintext database and the encrypted one-hot query.
Sends back the encrypted result to the client.

Line-by-line explanation

Imports and data


import tenseal as ts
Loads TenSEAL for homomorphic encryption operations.
import socket
Provides TCP networking primitives.
import struct
Used for packing/unpacking fixed-size binary headers (length prefix).
database =
A small plaintext database stored server-side; the goal is to return the element selected by
the client’s encrypted one-hot query.
Length-prefixed messaging helpers
def send_msg(sock, obj_bytes):
Sends a single message with a 4-byte big-endian length header, followed by the payload.
length = struct.pack("!I", len(obj_bytes))
Packs payload length as an unsigned 32-bit integer in network byte order.
sock.sendall(length + obj_bytes)
Writes the header and then the entire payload reliably.
def recvall(sock, n):
Utility to read exactly n bytes from the socket, looping until all bytes are received or the
connection is closed.
def recv_msg(sock):
Reads one length-prefixed message:
raw_len = recvall(sock, 4) reads the 4-byte header.
msg_len = struct.unpack("!I", raw_len) converts it to an int.
return recvall(sock, msg_len) reads the exact payload.

Socket server setup


server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Creates a TCP socket.
server_socket.bind(('localhost', 9990))
Binds to localhost:9990.
server_socket.listen(1)
Starts listening; backlog 1 is sufficient for a single client demo.
print("Server listening on port 9990...")
Log: server is ready.
conn, addr = server_socket.accept()
Blocks until a client connects; returns connection socket and address.
print(f"Connected by {addr}")
Log: client connected.

Receive TenSEAL context and query


context_bytes = recv_msg(conn)
Receives the serialized TenSEAL context bytes from client.
context = ts.context_from(context_bytes)
Reconstructs a TenSEAL context object from bytes. This context must include at least
public keys and evaluation keys (e.g., Galois/relin keys) needed for server-side operations; it
must not include the client’s secret key in a real deployment.
print("Context received")
Log.
query_bytes = recv_msg(conn)
Receives the serialized encrypted query vector.
query = ts.ckks_vector_from(context, query_bytes)
Deserializes an encrypted CKKS vector using the received context. This is the encrypted
one-hot vector indicating the client’s desired index.
print("Query received")
Log.

PIR computation: multiply then sum


db_vector = ts.plain_tensor(database)
Converts the Python list into a TenSEAL plaintext tensor suitable for element-wise ops with
the encrypted vector.
response_enc = query * db_vector
Homomorphic element-wise multiplication of the encrypted query with the plaintext
database. Because the query is one-hot, this produces an encrypted vector where only the
selected index holds the database value (other slots are 0, all under encryption).
dot_enc = response_enc.sum()
Homomorphically sums across all slots to obtain a single encrypted scalar equal to
database[desired_index]. This relies on TenSEAL providing rotation/additions internally when
calling sum() with available Galois keys; otherwise, explicit rotations would be required.

Send back the encrypted result


send_msg(conn, dot_enc.serialize())
Serializes the encrypted result and sends it back to the client.
print("Response sent")
Log.
conn.close(); server_socket.close()
Closes the connection and the listening socket.

Important correctness and security notes


Do not accept secret keys from client:
The client should never transmit the secret key. The context sent to the server must be
public-only (no secret key). The client should keep a private context locally for
encryption/decryption and send a context copy with only public/evaluation keys. If the
client mistakenly sends the secret key, the server can decrypt queries/results, defeating
privacy.
Keys required for operations:
Galois keys are required for rotations used during the sum over slots. If sum() is used,
ensure the client generated and included Galois keys in the context it sent; otherwise,
sum() will fail at runtime.
If the server performs multiplications between ciphertexts, relinearization keys would be
needed, but here the multiplication is ciphertext-plaintext, so relinearization keys are
typically not required.
Scale and modulus chain:
The client and server must share compatible CKKS parameters. The client’s global_scale and
coeff_mod_bit_sizes must align with operations the server performs. sum() may involve
additions and internal operations that keep scale consistent; avoid extra multiplications that
could exhaust levels.
Serialization/format matching:
The server expects the client to send:
1. context bytes, then
2. a serialized CKKS vector.
If the client sends any other format/order, deserialization will fail.
Plaintext packing assumptions:
ts.plain_tensor(database) works for element-wise multiply with a CKKS vector of matching
length. Ensure the client’s query vector has the same length as the database.
Single-client, single-request demo:
This server handles exactly one connection and then exits. For multiple clients or repeated
queries, loop over accept() and consider concurrent handling.

Minimal hardening suggestions


Verify context does not contain a secret key:
After deserialization, check context to ensure no secret key is present (TenSEAL exposes
flags/methods in some versions).
Validate lengths and handle None returns:
Add checks for recv_msg returning None and handle exceptions with try/except, closing
sockets cleanly.
Parameter sanity checks:
Confirm query size matches len(database). If not, return an error rather than proceeding.
Keep-alive or multi-request:
If needed, support multiple queries per connection by looping on recv until EOF.
If any errors arise during sum() or deserialization, share the stack trace and the exact client
parameters used; that makes it straightforward to pinpoint issues such as missing Galois keys or
parameter mismatches.

Explanation of the Paillier-based PIR toy example


This script demonstrates a simple, didactic private information retrieval pattern using Paillier
additively homomorphic encryption. The client encrypts a one-hot query vector; the server
multiplies each query component with the corresponding database element and
homomorphically sums the results; the client then decrypts to obtain exactly the selected
record.
What each part does
from phe import paillier
Imports the Paillier cryptosystem implementation from the phe library.
public_key, private_key = paillier.generate_paillier_keypair()
Generates a fresh Paillier keypair. The public key is used to encrypt and to homomorphically
add; the private key is used to decrypt.
database =
A small plaintext database (held by the server in this toy example).
k = 2 # required data of 2nd index
Chooses the index to retrieve (0-based). Here, index 2 corresponds to the value 42.
query_vector = * len(database)
Initializes a zero vector the same length as the database.
for i in range(len(database)):
if i == k:
query_vector[i] = 1
Converts the vector into a one-hot vector with a 1 at position k and 0 elsewhere:.
encrypted_query = * len(query_vector)
Prepares a list to hold encrypted query elements.
for i in range(len(query_vector)):
encrypted_query[i] = public_key.encrypt(query_vector[i])
Encrypts each entry of the query vector under the public key. Because entries are 0 or 1,
each encrypted value is a Paillier ciphertext.
encrypted_response = None
Initializes an accumulator for the encrypted response.
for i in range(len(database)):
term = encrypted_query[i] * database[i]
if encrypted_response is None:
encrypted_response = term
else:
encrypted_response += term
Core homomorphic computation:
Paillier supports homomorphic addition of ciphertexts and multiplication of a ciphertext
by a plaintext scalar.
Multiplying encrypted_query[i] (cipher) by database[i] (plaintext integer) yields an
encryption of query_vector[i] * database[i].
Summing these terms homomorphically accumulates an encryption of the dot product:
sum_i (query[i] * database[i]).
Because the query is one-hot, this dot product equals database[k].
retrieved_record = private_key.decrypt(encrypted_response)
Decrypts the accumulated ciphertext to recover the selected database value in plaintext.
print("Client requested index:", k)
print("Retrieved record:", retrieved_record)
print("Actual record in DB:", database[k])
Prints the requested index, the decrypted result, and the ground-truth value from the
database for verification. Expect both values to match (42 for k=2).

Why this works (homomorphic properties)


Paillier is additively homomorphic:
Dec(enc(a) + enc(b)) = a + b
Dec(enc(a) * m) = a * m for plaintext scalar m
Using a one-hot vector, the dot product with the database selects exactly one record:
sum_i (one_hot[i] * db[i]) = db[k]

Important considerations and best practices


Privacy boundary:
In a realistic setting, the client should keep the private key secret and only send encrypted
query components to the server. The server should not see k or any plaintext query values.
The server computes on ciphertexts and returns only an encrypted result.
Data domains:
Paillier natively handles integers. If non-integer data or negatives/large ranges are needed,
one must use encoding schemes (e.g., fixed-point scaling) and ensure values remain within
the plaintext modulus bounds.
Efficiency:
This toy approach is O(N) in database size per query and requires N ciphertext operations.
Practical PIR or PSI protocols use more efficient techniques and batching.
Integrity/validation:
In real deployments, consider input validation, ciphertext re-randomization, and potential
safeguards against malformed ciphertexts or chosen-ciphertext issues, depending on the
threat model.
Key and parameter management:
Use appropriate key sizes for security. The phe library defaults are for demonstration;
confirm parameters meet current security requirements for production use.
This example is a clear, minimal illustration of homomorphic selection using Paillier: encrypt a
one-hot query, compute a ciphertext-plaintext dot product on the server, and decrypt to get the
requested record.
expalain homo morphic encryption in simple tem
Of course. Here is an explanation of homomorphic encryption in simple terms.
Homomorphic encryption is a method of encryption that allows for mathematical computations to
be performed on encrypted data without decrypting it first. The result of the computation
remains in an encrypted format, and once decrypted, the output is identical to what it would
have been if the operations were performed on the original, unencrypted data. [1] [2]

An analogy
Imagine you want a jeweler to create a beautiful necklace for you, but you don't want them to
know the raw materials you're using. You place your diamonds, gold, and other precious
materials inside a locked, transparent box. The jeweler can't open the box, but they can use
special gloves to work with the materials inside. Once the necklace is complete, the jeweler
returns the locked box to you. You are the only one with the key, so only you can open the box
and retrieve the finished necklace. [2]
In this analogy:
The raw materials are your data.
The locked box is the encryption.
The special gloves are the homomorphic encryption scheme.
The jeweler is the third-party service (e.g., a cloud provider).
The finished necklace is the result of the computation.

Types of homomorphic encryption


There are two main types of homomorphic encryption : [3] [4]
Partially homomorphic encryption (PHE) This type of encryption allows for one type of
mathematical operation, such as addition or multiplication, to be performed on the
encrypted data an unlimited number of times. [3]
Fully homomorphic encryption (FHE) This is the most powerful type of homomorphic
encryption. It allows for any kind of computation to be performed on encrypted data for an
unlimited number of times. [3]

Why is it useful?
Homomorphic encryption is particularly useful for industries that handle sensitive data, such as
healthcare and finance. For instance, a hospital could use a third-party cloud service to analyze
patient data without ever exposing the raw, unencrypted information to the service provider.
This allows the hospital to leverage advanced analytics while maintaining patient privacy. [1]
In essence, homomorphic encryption enables data to be processed in a secure manner, ensuring
that sensitive information remains confidential even when it is being analyzed or manipulated by
external services. [5] [6]

1. https://en.wikipedia.org/wiki/Homomorphic_encryption
2. https://www.freecodecamp.org/news/homomorphic-encryption-in-plain-english/
3. https://www.geeksforgeeks.org/ethical-hacking/homomorphic-encryption/
4. https://brilliant.org/wiki/homomorphic-encryption/
5. https://www.splunk.com/en_us/blog/learn/homomorphic-encryption.html
6. https://www.techtarget.com/searchsecurity/definition/homomorphic-encryption
7. https://www.zama.ai/introduction-to-homomorphic-encryption
8. https://chain.link/education-hub/homomorphic-encryption
9. https://www.youtube.com/watch?v=lNw6d05RW6E

You might also like