WellAlly Logo
WellAlly康心伴
Development

Homomorphic Encryption in Practice: A Python Tutorial for Performing Calculations on Encrypted Health Data

Dive into Homomorphic Encryption, a powerful cryptographic technique that lets you compute on encrypted data. This hands-on Python tutorial uses the TenSEAL library to perform analytics on sensitive health data without ever decrypting it.

W
2025-12-12
10 min read

Imagine you're building a revolutionary health tech application. You want to provide users with insightful analytics about their biometric data, like their average resting heart rate over the past week. There's just one major hurdle: this data is incredibly sensitive. A data breach could expose personal health information, leading to a catastrophic loss of user trust and significant legal consequences.

Traditionally, to perform a calculation like an average, a server would need to decrypt the data. During that moment of computation, the plaintext data is vulnerable. What if you could perform calculations directly on the encrypted data? This is the promise of Homomorphic Encryption (HE), a cryptographic technique that feels like magic. It allows a third party (like a cloud server) to process and compute on ciphertext, producing an encrypted result that, when decrypted, is identical to the result of operations performed on the plaintext.

In this tutorial, we'll demystify Homomorphic Encryption by building a practical Python example. We will:

  1. Take a list of sensitive health data (e.g., daily heart rate measurements).
  2. Encrypt this data on the "client-side" using a powerful Python library called TenSEAL.
  3. Send the encrypted data to a "server" which will calculate the average heart rate without having the decryption key.
  4. Return the encrypted average to the client for secure decryption.

This hands-on approach will show you how to leverage advanced cryptography to build privacy-preserving features, a crucial skill in the age of data privacy.

Prerequisites:

  • Knowledge: A solid understanding of Python. No prior cryptography knowledge is required.
  • Tools: Python 3.7+ and the tenseal library.

Understanding the Problem: The Privacy Paradox in Health Tech

The health tech industry faces a paradox: the more data it collects, the more personalized and effective its services can be. However, this wealth of data, which includes everything from heart rates and sleep patterns to genomic sequences, is a prime target for attackers.

Current Solutions and Their Limitations:

  • Encryption at Rest and in Transit: Standard practice involves encrypting data when it's stored in a database (at rest) and when it's being transmitted over a network (in transit). This is essential, but it leaves a critical vulnerability: data must be decrypted for computation.
  • Anonymization: This involves stripping personally identifiable information (PII) from datasets. While useful, re-identification attacks are becoming increasingly sophisticated, and for personalized analytics, you often need to link data back to a specific user.

Our Approach: Computation on Ciphertext Homomorphic Encryption solves the "computation vulnerability." A server can run analytics, train machine learning models, and derive insights from user data without ever seeing the raw, sensitive information. This opens up new possibilities for secure cloud computing, multi-party collaboration in medical research, and truly private personal analytics. For our tutorial, we'll use the CKKS (Cheon-Kim-Kim-Song) scheme, which is specifically designed for computations on real (or approximate) numbers, making it perfect for biometric data.

Prerequisites: Setting Up Your Environment

Before we dive in, let's get our environment ready. You'll need to install the tenseal library.

Open your terminal and run the following command:

code
pip install tenseal
Code collapsed

To verify the installation, start a Python interpreter and import the library:

code
import tenseal as ts
print(ts.__version__)
Code collapsed

You should see the installed version number without any errors.

Step 1: Creating the Homomorphic Encryption Context

The first step in any TenSEAL application is to create a TenSEALContext. This object holds all the crucial parameters for our encryption scheme, including the public key, the private (secret) key, and settings that define the security level and computational capabilities.

What we're doing

We'll configure a context for the CKKS encryption scheme. We need to define three key parameters:

  • poly_modulus_degree: This parameter influences the security level and the number of values we can encrypt in a single vector. It must be a power of two.
  • coeff_mod_bit_sizes: This defines the size of the prime numbers used in the coefficient modulus. It affects the "multiplicative depth" – how many multiplications we can perform on the encrypted data before the noise growth makes the ciphertext undecryptable.
  • global_scale: This determines the precision of our encrypted real numbers. We are essentially working with fixed-point numbers, and this scale factor defines where the decimal point is.

Implementation

code
# src/create_context.py
import tenseal as ts

def create_context():
    """
    Creates and configures a TenSEAL context for Homomorphic Encryption.
    """
    context = ts.context(
        ts.SCHEME_TYPE.CKKS,
        poly_modulus_degree=8192,
        coeff_mod_bit_sizes=
    )
    context.generate_galois_keys()
    context.global_scale = 2**40
    
    return context

if __name__ == '__main__':
    ctx = create_context()
    print("✅ TenSEAL context created successfully.")
    print(f"    Parameters: poly_modulus_degree={ctx.poly_modulus_degree()}, global_scale=2^{ctx.global_scale.bit_length()-1}")

    # The context holds the private key
    secret_key = ctx.secret_key()
    
    # We can also create a public version of the context that doesn't contain the secret key
    public_ctx = ctx.copy()
    public_ctx.make_context_public() # Drops the secret key
    
    print("🔑 Context contains private key:", ctx.is_private())
    print("🔑 Public context contains private key:", public_ctx.is_private())
Code collapsed

How it works

We initialize the context with the CKKS scheme. A poly_modulus_degree of 8192 is a common and secure choice. The coeff_mod_bit_sizes list allows for a certain number of chained multiplications. We also generate galois_keys, which are necessary for performing vector rotation operations—a key step for summing up all the elements in our encrypted vector. Finally, make_context_public() creates a version of the context that can be sent to the server; it contains the public key and evaluation keys but crucially lacks the secret key needed for decryption.

Step 2: Client-Side Encryption

Now that we have a context, let's simulate the client's role. A user has a week's worth of resting heart rate data that they want to get an average for, without revealing the individual numbers to the server.

What we're doing

We will take a list of floating-point numbers representing heart rates and encrypt them into a single CKKSVector. This vector object bundles all the data points into one encrypted entity.

Implementation

code
# src/client.py
import tenseal as ts
from create_context import create_context

def encrypt_health_data(context, data):
    """Encrypts a list of numbers into a CKKS vector."""
    return ts.ckks_vector(context, data)

if __name__ == '__main__':
    # (Client-side)
    # 1. Create context with a secret key
    context = create_context()

    # 2. Sample sensitive health data
    heart_rate_data = [72.5, 71.0, 73.2, 69.8, 70.5, 74.1, 71.7]
    print(f"Plaintext data (client): {heart_rate_data}")
    
    # 3. Encrypt the data
    encrypted_heart_rates = encrypt_health_data(context, heart_rate_data)
    print(f"🔒 Data encrypted successfully into a CKKS vector.")
    
    # 4. Serialize the encrypted data and the public context for sending
    serialized_encrypted_vector = encrypted_heart_rates.serialize()
    
    # Create and serialize the public context
    public_context = context.copy()
    public_context.make_context_public()
    serialized_public_context = public_context.serialize()
    
    # This is what we would send to the server
    # For this tutorial, we'll pass the objects directly
    # In a real app, this would be an API call
    #
    # server_receives(serialized_public_context, serialized_encrypted_vector)
Code collapsed

How it works

The ts.ckks_vector() function takes our context and the list of numbers. It encodes the numbers into a polynomial and then encrypts it using the public key stored within the context. The result is a single CKKSVector object that holds all our heart rate data in an encrypted state.

Step 3: Server-Side Computation on Encrypted Data

This is where the magic happens. The "server" receives the encrypted vector and the public context. Its task is to compute the average of the numbers inside the vector without the secret key.

An average is calculated as sum(values) / count(values). We can do this in two steps:

  1. Sum: Homomorphically sum the elements of the encrypted vector.
  2. Divide: Homomorphically divide the encrypted sum by the public count of elements (which is just multiplication by the inverse).

What we're doing

To sum the elements of the vector, we use a "rotate and sum" strategy. Imagine our vector is [v1, v2, v3, v4]. We can rotate it by one position to get [v2, v3, v4, v1], then add this to the original. We repeat this process, doubling the number of elements being summed in each step, until all elements have been accumulated into each slot of the vector. Finally, we multiply the resulting vector by 1/n to get the average.

Implementation

code
# src/server.py
import math

def calculate_encrypted_average(public_context, encrypted_vector):
    """
    Performs homomorphic operations to calculate the average of an encrypted vector.
    """
    # The server can get the size of the vector without decrypting
    n_values = len(encrypted_vector)

    # 1. Homomorphically sum all elements in the vector
    # This is the most complex step. We use rotations to achieve a horizontal sum.
    encrypted_sum = encrypted_vector.sum()
        
    print("🖥️  Server: Encrypted sum calculated.")

    # 2. Multiply by the inverse of the number of values to get the average
    # Division by a plaintext number is equivalent to multiplication by its inverse.
    encrypted_average = encrypted_sum * (1 / n_values)
    print(f"🖥️  Server: Encrypted average calculated by multiplying with {1/n_values:.4f}.")
    
    return encrypted_average

if __name__ == '__main__':
    # This block simulates the full pipeline
    
    # ====== CLIENT SIDE ======
    from create_context import create_context
    import tenseal as ts

    # 1. Setup
    context = create_context()
    heart_rate_data = [72.5, 71.0, 73.2, 69.8, 70.5, 74.1, 71.7]
    print(f"Plaintext data (client): {heart_rate_data}")
    print(f"Plaintext average: {sum(heart_rate_data) / len(heart_rate_data):.2f}")

    # 2. Encrypt
    encrypted_heart_rates = ts.ckks_vector(context, heart_rate_data)
    print("🔒 Client: Data encrypted.")
    
    # ====== DATA TRANSFER (Client -> Server) ======
    # In a real app, you'd serialize and send over HTTP/RPC
    serialized_vector = encrypted_heart_rates.serialize()
    public_context = context.copy()
    public_context.make_context_public()
    serialized_context = public_context.serialize()

    # ====== SERVER SIDE ======
    # The server only has the public context and the encrypted data
    server_context = ts.context_from(serialized_context)
    server_encrypted_vector = ts.ckks_vector_from(server_context, serialized_vector)
    
    print("☁️  Server: Received encrypted data and public context.")
    
    # 3. Compute
    encrypted_average = calculate_encrypted_average(server_context, server_encrypted_vector)
    
    # ====== DATA TRANSFER (Server -> Client) ======
    serialized_encrypted_average = encrypted_average.serialize()
    
    # ====== CLIENT SIDE ======
    # 4. Decrypt
    print("🔙 Client: Received encrypted average.")
    result_vector = ts.ckks_vector_from(context, serialized_encrypted_average) # Use original context with secret key
    decrypted_average = result_vector.decrypt() # The result is in the first slot
    
    print("\n======== RESULTS ========")
    print(f"✅ Decrypted Average Heart Rate: {decrypted_average:.2f}")
    print("=========================")

Code collapsed

How it works

The encrypted_vector.sum() method in TenSEAL abstracts away the complex "rotate and sum" logic. It uses the Galois keys we generated in Step 1 to perform efficient rotations and additions, ultimately producing a new encrypted vector where each element is the sum of all original elements.

Next, encrypted_sum * (1 / n_values) performs an element-wise multiplication between the encrypted sum and a plain (unencrypted) number. The CKKS scheme supports this operation seamlessly. The server now has an encrypted vector representing the average, which it can send back to the client.

Upon receiving the result, the client uses its original context (which still contains the secret key) to decrypt the message and retrieve the final, accurate average.

Performance Considerations

Homomorphic Encryption is powerful, but it's not free. The computations are significantly slower and more memory-intensive than their plaintext counterparts.

  • Latency: Encrypting, decrypting, and especially computing on encrypted data takes time. For our simple average, it's milliseconds, but for complex machine learning models, it could be seconds or minutes.
  • Ciphertext Size: The serialized encrypted vector is much larger than the original plaintext data. A list of 7 floats (56 bytes) might become a ciphertext of several hundred kilobytes.
  • Parameter Tuning: The security and performance of the CKKS scheme depend heavily on the context parameters (poly_modulus_degree, coeff_mod_bit_sizes). Larger parameters provide more security and allow for more complex computations (more multiplications) but are slower. Choosing the right parameters for your use case is a critical balancing act.

Security Best Practices

  • Key Management: The secret key is the most critical piece of this system. It should never leave the client's device. Standard secure key management practices are paramount.
  • Parameter Selection: Use well-established, secure parameters. The TenSEAL defaults are a good starting point, but for production systems, consult cryptographic literature for recommended security levels (e.g., 128-bit security).
  • Side-Channel Attacks: While the server can't see the data, it can observe computation times and memory access patterns. In high-stakes scenarios, these "side channels" could leak information. Designing systems to be resistant to such attacks is an advanced topic.
  • Don't Roll Your Own Crypto: Always use well-vetted, peer-reviewed libraries like TenSEAL (which is built on Microsoft SEAL).

Conclusion

We've successfully walked through a complete, end-to-end example of performing a calculation on encrypted data. We acted as a client, encrypting sensitive heart rate data, then switched hats to a server that computed the average without ever seeing the raw numbers. This powerful privacy-preserving technique is no longer just theoretical; it's accessible to Python developers through libraries like TenSEAL.

While HE has performance trade-offs, it unlocks the ability to build a new class of secure applications, especially in sensitive domains like healthcare and finance.

Next Steps for Readers:

  • Experiment with other calculations, like calculating the variance or a simple linear regression.
  • Explore the performance impact of changing the context parameters.
  • Try serializing the context and encrypted vectors to files to simulate a more realistic client-server interaction.

Resources

#

Article Tags

python
security
privacy
cryptography
datascience
W

WellAlly's core development team, comprised of healthcare professionals, software engineers, and UX designers committed to revolutionizing digital health management.

Expertise

Healthcare Technology
Software Development
User Experience
AI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey