Code Monkey home page Code Monkey logo

lightphe's Introduction

LightPHE

PyPI Downloads Stars Tests License

Blog YouTube Twitter

Support me on Patreon GitHub Sponsors Buy Me a Coffee

LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as RSA, ElGamal, Exponential ElGamal, Elliptic Curve ElGamal, Paillier, Damgard-Jurik, Okamoto–Uchiyama, Benaloh, Naccache–Stern, Goldwasser–Micali.

Partially vs Fully Homomorphic Encryption

Even though fully homomorphic encryption (FHE) has become available in recent times, but when considering the trade-offs, LightPHE emerges as a more efficient and practical choice. If your specific task doesn't demand the full homomorphic capabilities, opting for partial homomorphism with LightPHE is the logical decision.

  • 🏎️ Notably faster
  • 💻 Demands fewer computational resources
  • 📏 Generating much smaller ciphertexts
  • 🔑 Distributing much smaller keys
  • 🧠 Well-suited for memory-constrained environments
  • ⚖️ Strikes a favorable balance for practical use cases

Installation PyPI

The easiest way to install the LightPHE package is to install it from python package index (PyPI).

pip install lightphe

Then you will be able to import the library and use its functionalities.

from lightphe import LightPHE

Summary of Homomorphic Features of Different Cryptosystems in LightPHE

In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.

Algorithm Multiplicatively
Homomorphic
Additively
Homomorphic
Multiplication with a Plain Constant Exclusively
Homomorphic
Regeneration
of Ciphertext
RSA
ElGamal
Exponential ElGamal
Elliptic Curve ElGamal
Paillier
Damgard-Jurik
Benaloh
Naccache-Stern
Okamoto-Uchiyama
Goldwasser-Micali

Building cryptosystem

Once you imported the library, then you can build a cryptosystem for several algorithms. This basically generates private and public key pair.

algorithms = [
  "RSA",
  "ElGamal",
  "Exponential-ElGamal",
  "Paillier",
  "Damgard-Jurik",
  "Okamoto-Uchiyama",
  "Benaloh",
  "Naccache-Stern",
  "Goldwasser-Micali",
  "EllipticCurve-ElGamal"
]

cs = LightPHE(algorithm_name = algorithms[0])

Encryption & Decryption

Once you built your cryptosystem, you will be able to encrypt and decrypt messages with the built cryptosystem.

# define plaintext
m = 17

# calculate ciphertext
c = cs.encrypt(m)

assert cs.decrypt(c) == m

Homomorphic Operations

Once you have the ciphertext, you will be able to perform homomorphic operations on encrypted data. For instance, Paillier is homomorphic with respect to the addition. In other words, decryption of the addition of two ciphertexts is equivalent to addition of plaintexts.

cs = LightPHE(algorithm_name = "Paillier")

# define plaintexts
m1 = 17
m2 = 23

# calculate ciphertexts
c1 = cs.encrypt(m1)
c2 = cs.encrypt(m2)

# homomorphic addition - private key is not required!
c3 = c1 + c2

# proof of work
assert cs.decrypt(c3) == m1 + m2

⚡ Notice that once can perform c1 + c2 here without holding private key. However, just the data owner with private key can perform encryption and decryption. This is the basic definition of homomorphic encryption.

Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. Simply put, decryption of scalar multiplication of ciphertext is equivalent to that constant times plaintext as well.

# increasing something 5%
k = 1.05

# scalar multiplication - private key is not required!
c4 = k * c1

# proof of work
assert cs.decrypt(c4) == k * m1

⚡ Herein, k * c1 operation can be performed by anyone without holding private key.

Similar to the most of additively homomorphic algorithms, Paillier lets you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.

c1_prime = cs.regenerate_ciphertext(c1)
assert c1_prime.value != c1.value
assert cs.decrypt(c1_prime) == m1
assert cs.decrypt(c1) == m1

Finally, if you try to perform an operation that algorithm does not support, then an exception will be thrown. For instance, Paillier is not homomorphic with respect to the multiplication or xor. To put it simply, you cannot multiply two ciphertexts. If you enforce this calculation, you will have an exception.

# pailier is not multiplicatively homomorphic
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the multiplication"):
  c1 * c2

# pailier is not exclusively homomorphic
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the exclusive or"):
  c1 ^ c2

However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.

Working with vectors

You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.

# build an additively homomorphic cryptosystem
cs = LightPHE(algorithm_name="Paillier")

# define plain tensors
t1 = [1.005, 2.05, 3.5, 4]
t2 = [5, 6.2, 7.002, 8.02]

# encrypt tensors
c1 = cs.encrypt(t1)
c2 = cs.encrypt(t2)

# perform homomorphic addition
c3 = c1 + c2

# perform homomorphic element-wise multiplication
c4 = c1 * c2

# perform homomorphic scalar multiplication
k = 5
c5 = k * c1

# decrypt the addition tensor
t3 = cs.decrypt(c3)

# decrypt the element-wise multiplied tensor
t4 = cs.decrypt(c4)

# decrypt the scalar multiplied tensor
t5 = cs.decrypt(c5)

# data validations
threshold = 0.5
for i in range(0, len(t1)):
   assert abs((t1[i] + t2[i]) - t3[i]) < threshold
   assert abs((t1[i] * t2[i]) - t4[i]) < threshold
   assert abs((t1[i] * k) - t5[i]) < threshold

Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.

Contributing

All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.

You should be able run make test and make lint commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in GitHub actions before approval. Besides, workflow will evaluate the code with pylint as well.

Support

There are many ways to support a project - starring⭐️ the GitHub repo is just one 🙏

You can also support this work on Patreon, GitHub Sponsors or Buy Me a Coffee.

Citation

Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:

@misc{serengil2023lightphe,
  abstract     = {LightPHE: A Lightweight Partially Homomorphic Encryption Library for Python},
  author       = {Serengil, Sefik Ilkin},
  title        = {LightPHE},
  howpublished = {https://github.com/serengil/LightPHE},
  year         = {2023}
}

Also, if you use LightPHE in your projects, please add lightphe in the requirements.txt.

License

LightPHE is licensed under the MIT License - see LICENSE for more details.

lightphe's People

Contributors

serengil avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

crypt-asu

lightphe's Issues

encrypt float numbers if possible

As an user, I want to encrypt float numbers. In that way, I will be able perform homomorphic operations for percentage calculations. This is already supported in multiplying a ciphertext with a known constant.

Logic for converting float to int: https://github.com/serengil/LightPHE/blob/master/lightphe/models/Ciphertext.py#L116

Where this logic should be used, too: https://github.com/serengil/LightPHE/blob/master/lightphe/__init__.py#L113

You may consider to move __convert_to_int to somewhere common

solving dlp and ecdlp not with brute borce

In the decryption steps of algorithms Benaloh, Naccache-Stern, and Exponential ElGamal, we are solving discrete logarithm problem with brute force. Instead, we can use some faster algorithms such as baby step giant step. Luckily, sympy has out-of-the-box modulo for this.

Similarly, the decryption step of Elliptic Curve ElGamal requires to solve elliptic curve discrete logarithm problem. We should adopt something similar to ones mentioned above.

PS: I gave the links with dedicated DLP and ECDLP lines above.

Creating classes for each cryptosystem

In the interface of lightphe, create different classes for each cryptosystem that extending LightPHE class. In that way, an user does not need to set algorithm name as string, and can use auto-complete feature easily.

We will need to retire build_cryptosystem method here, and put recommend_key_size method to outside of that class.

Public key export deletes private key

When performing export_keys() in lightphe.init in case of exporting (publishing) the public key, it deletes private key in the process. This leaves the LightPHE object (who should have private key, because it generated it) unable to decrypt.

246: if public is True and keys.get("private_key") is not None:
247:     del keys["private_key"]

Current workaround:

gm = LightPHE('Paillier')
gm.export_keys('private.key', public=False)
gm.export_keys('public.key', public=True)
gm = LightPHE('Paillier', key_file='private.key')

Proposed fix:
Don't delete the private_key from keys when performing export_keys(), just export the public_key

pubkey = {'public_key': keys['public_key']}
with open(target_file, "w", encoding="UTF-8") as file:
    file.write(pubkey)

Multiplication with a Plain Constant doesn't work

plain constant multiplication doesn't work for the algorithms that are supported for this operation, i.e. Paillier, Damgard-Jurik, etc.

Run the sample code in the README,

from lightphe import LightPHE
cs = LightPHE(algorithm_name = "Paillier")
m1 = 17
c1 = cs.encrypt(m1)
k = 1.05

assert cs.decrypt(k * c1) == k * m1

I get

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

I think this is due to the type of k. If k is integer, e.g. 5, the above code works. I tested in 3 environment, the result is the same:

  1. py3.10 64bit mac
  2. py3.12 64bit mac
  3. py3.10 32bit windows

Private key for encryption?

IMO those two lines In LightPHE.init().encrypt() should check for public key, since you can encrypt with only public key, but you need private key for decryption.

119: if self.cs.keys.get("private_key") is None:
120:    raise ValueError("You must have private key to perform encryption")

Proposed fix:

119: if self.cs.keys.get("public_key") is None:
120:    raise ValueError("You must have public key to perform encryption")

Goldwasser-Micali decryption fails for larger keys

Currently, we set the default key length of Goldwasser-Micali to 100 bits here. This is working fine. However, if we increase the key length (e.g. 200 bits), then decryption fails. We need to find out the root cause of this problem.

add homomorphic operation support for encrypted tensors

  • Encrypt & decrypt tensors with float items
  • Homomorphic operations such add addition, element-wise multiplication, scalar multiplication and xor
  • Homomorphic multiplication (dot product) will not work because it requires addition and multiplication meanwhile and this cannot be done with PHE.
  • Tensor should be 1D vectors and all items must be int or float. We will handle multi-dimensional vectors (tensors) in a separate PR.

To encrypt & decrypt floats, we may adopt one of these approaches:

Option 1: Store Dividend and Divisor Separately

Pros:

Exact Representation: This method allows you to exactly represent the original value without loss of precision. You store the dividend and divisor separately, allowing for precise arithmetic operations during decryption.

Cons:

Increased Storage: Storing both the dividend and divisor separately may require more storage compared to the second option, where you only need to store a single integer.

Complexity: Managing two separate values might add complexity to your implementation. You need to make sure that the operations are performed correctly during encryption and decryption.

Option 2: Multiply and Divide by a Fixed Factor

Pros:

Simplicity: This method is simpler to implement, as you only need to multiply and divide by a fixed factor. This reduces the complexity of your code.

Reduced Storage: You only need to store a single integer value, which can save storage space compared to storing both the dividend and divisor separately.

Cons:

Loss of Precision: Multiplying and dividing by a fixed factor can lead to a loss of precision. If your fixed factor is not large enough, you may encounter rounding errors during encryption and decryption.

Limited Range: Depending on the fixed factor, you might have a limited range for the values you can represent in your encrypted tensor.

I should create ciphertext object with number

LightPHE class should have create ciphertext method accepting number or tuple. One can perform homomorphic operations without holding private key in that way.

Also make public method will be good to drop private key in LightPHE class.

Possibility to not decrypt original data

Hi,

My need is to take two values A and B, encrypt them with a public key, sum the encrypted data C.
Then I need to decrypt C with the private key but to not be able to decrypt also the original values A and B.
Is this possible using LightPHE?

Support multi-dimensional vectors (tensors)

Currently, tensor operations support just 1D vectors. We need to change the following methods.

Instead of a one depth for loop, element wise operations can be calculated as:

def element_wise_multiply(tensor1, tensor2):
    # Make sure both tensors have the same dimensions
    if len(tensor1) == len(tensor2) and all(len(row1) == len(row2) for row1, row2 in zip(tensor1, tensor2)):
        rows = len(tensor1)
        cols = len(tensor1[0])

        result = [[0 for _ in range(cols)] for _ in range(rows)]

        # Perform element-wise multiplication using nested for loop
        for i in range(rows):
            for j in range(cols):
                result[i][j] = tensor1[i][j] * tensor2[i][j]

        return result
    else:
        raise ValueError("Tensors must have the same dimensions for element-wise multiplication.")

# Example usage
tensor1 = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

tensor2 = [
    [9, 8, 7],
    [6, 5, 4],
    [3, 2, 1]
]

result = element_wise_multiply(tensor1, tensor2)

printing ciphertexts not dumping its value

repr method should be added for Ciphertext class as well.

class MyClass:
    def __init__(self, value):
        self.value = value

    def __str__(self):
        return f"MyClass instance with value: {self.value}"

    def __repr__(self):
        return f"MyClass({self.value})"

# Create an instance of MyClass
obj = MyClass(42)

# When you print the object
print(obj)  # Calls __str__

# When you directly run the object
obj  # Calls __repr__

Effective key generation for Naccache-Stern

Currently, we set the key length of Naccace-Stern to 37 bits here and here

This is working fine but if we increase the key length, then it cannot find any keys for a long time. TBH, I never see a key is found.

We need to find a way to generate larger keys. This is answered in a stackoverflow post but when I do it, still it is not working.

supporting tensors

Current lightphe api allows users to encrypt & decrypt and homomorphic operations on numbers. As a feauture, lightphe should support tensors for these operation set. This can be used in machine learning applications!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.