Metadata-Version: 2.1
Name: simplefhe
Version: 1.0.0
Summary: Dead-simple full homomorphic encryption (FHE) for Python
Home-page: https://github.com/wgxli/simple-fhe
Author: Samuel Li
Author-email: simplefhe@samuelj.li
License: MIT
Platform: UNKNOWN
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Description-Content-Type: text/markdown

# A Simple Drop-In Solution for Full Homomorphic Encryption
Full Homomorphic Encryption (FHE) allows untrusted (e.g. cloud) applications
to operate directly on encrypted data, eliminating the need for server-side decryption or trust.

`simplefhe` is a Python library for FHE that intends to be as easy-to-use as possible.
In the simplest case, just a few lines of code are all you need to have working FHE!

## The Problem
Suppose we have some sensitive data we wish to process on a remote server.
The usual approach is to send the data over a secure connection to be processed server-side.

```python
# The server
def process(x):
    return x*x - 3*x + 1


# The client
sensitive_data = [-30, -5, 17, 28]
for entry in sensitive_data:
    print(entry, process(entry)) # Bad! We are leaking sensitive information.
```
The result:
```
-30 991
-5 41
17 239
28 701
```
However, this requires trusting the server to keep your data confidential. One rogue admin or database hack is all it takes to expose your sensitive data to the public.

## The Solution
A few lines of extra code is all it takes to implement Full Homomorphic Encryption (FHE):
```python
from simplefhe import (
    encrypt, decrypt,
    generate_keypair,
    set_public_key, set_private_key,
    display_config
)

# In a real application, the keypair would be generated once,
# and only the public key would be provided to the server.
# A more realistic example is given later.
public_key, private_key = generate_keypair()
set_private_key(private_key)
set_public_key(public_key)

display_config()


# The server
def process(x):
    return x*x - 3*x + 1


# The client
sensitive_data = [-30, -5, 17, 28]
for entry in sensitive_data:
    encrypted = encrypt(entry) # Encrypt the data...
    result = process(encrypted) # Process the data encrypted on the server...
    print(entry, decrypt(result)) # Decrypt the result on the client.
```
We obtain the same results, as expected:
```
===== simplefhe config =====
mode: integer (exact)
min_int: -1048575
max_int: 1048576
public_key: initialized
private_key: initialized

-30 991
-5 41
17 239
28 701
```
In this example, we encrypt the data on the client, *send only the encrypted data to the server,* process the encrypted data server-side,
and return the encrypted result to be client-side decrypted. This requires zero trust of the remote server.

## A More Realistic Example
Of course, the client and server will generally be separate applications.
Here we demonstrate a more realistic pipeline.

### Step 1: Keypair Generation
We first generate a fixed pair of keys:
```python
from simplefhe import generate_keypair

public_key, private_key  = generate_keypair()
public_key.save('keys/public.key')
private_key.save('keys/private.key')
print('Keypair saved to keys/ directory')
```

### Step 2: Client-Side Encryption
Next, we encrypt our sensitive data on the client.
Here we save the encrypted results to disk,
but in the real-world these files would be sent over a network to the server.
```python
from simplefhe import encrypt, load_public_key, display_config

load_public_key('keys/public.key')
display_config()


# Encrypt our data (client-side)
sensitive_data = [-30, -5, 17, 28]

for i, entry in enumerate(sensitive_data):
    encrypted = encrypt(entry)
    encrypted.save(f'inputs/{i}.dat')
    print(f'[CLIENT] Input {entry} encrypted to inputs/{i}.dat')


# We may then safely send these files to the server
# over a (possibly insecure) network connection
```
Output:
```
===== simplefhe config =====
mode: integer (exact)
min_int: -1048575
max_int: 1048576
public_key: initialized
private_key: missing

[CLIENT] Input -30 encrypted to inputs/0.dat
[CLIENT] Input -5 encrypted to inputs/1.dat
[CLIENT] Input 17 encrypted to inputs/2.dat
[CLIENT] Input 28 encrypted to inputs/3.dat
```

### Step 3: Server-Side Processing
We process the encrypted data from the client.
The server never has access to the private key,
and can never decrypt the client's sensitive data.
```python
from simplefhe import load_public_key, display_config, load_encrypted_value

load_public_key('keys/public.key')
display_config()

# Process values on server.
def f(x): return x*x - 3*x + 1

for i in range(4):
    # Load encrypted value sent from client
    value = load_encrypted_value(f'inputs/{i}.dat')

    # simplefhe seamlessly translates all arithmetic to
    # FHE encrypted operations.
    # We never gain access to the unencrypted information.
    result = f(value) 

    # Send encrypted result back to client
    result.save(f'outputs/{i}.dat')
    print(f'[SERVER] Processed entry {i}: inputs/{i}.dat -> outputs/{i}.dat')
```
Output:
```
===== simplefhe config =====
mode: integer (exact)
min_int: -1048575
max_int: 1048576
public_key: initialized
private_key: missing

[SERVER] Processed entry 0: inputs/0.dat -> outputs/0.dat
[SERVER] Processed entry 1: inputs/1.dat -> outputs/1.dat
[SERVER] Processed entry 2: inputs/2.dat -> outputs/2.dat
[SERVER] Processed entry 3: inputs/3.dat -> outputs/3.dat
```

### Step 4: Client-Side Decryption
Finally, the encrypted results are sent back to the client,
where they are decrypted.
The private key never needs to leave the client.
```python
from simplefhe import (
    load_private_key, display_config,
    decrypt, load_encrypted_value
)

# Note: this is the only step at which the private key is used!
load_private_key('keys/private.key')
display_config()


# Decrypt results from the server (client-side)
sensitive_data = [-30, -5, 17, 28]

for i, entry in enumerate(sensitive_data):
    encrypted = load_encrypted_value(f'outputs/{i}.dat')
    result = decrypt(encrypted)
    print(f'[CLIENT] Result for {entry}: {result}')
```
As expected, we obtain the correct results:
```
===== simplefhe config =====
mode: integer (exact)
min_int: -1048575
max_int: 1048576
public_key: missing
private_key: initialized

[CLIENT] Result for -30: 991
[CLIENT] Result for -5: 41
[CLIENT] Result for 17: 239
[CLIENT] Result for 28: 701
```

## Installation
`simplefhe` depends on [SEAL-Python](https://github.com/Huelse/SEAL-Python) and all its prerequisites.
After installing `SEAL-Python`, the `simplefhe` library
is just a `pip` install away:
`pip3 install simplefhe`

## Notes
- Comparison operations (`<`, `=`, `>`) are not supported on encrypted data.
If they were, it would be pretty easy to figure out what the plaintext is!
As a side effect, it's not really possible to branch based on encrypted data.
- There is some randomness in the encryption process: the same value, encrypted with the same key, will yield different ciphertexts.
This prevents a simple plaintext enumeration attack.
- Float support is currently experimental.


