- Notifications
You must be signed in to change notification settings - Fork0
TNO PET Lab - secure Multi-Party Computation (MPC) - Encryption Schemes - ElGamal
License
TNO-MPC/encryption_schemes.elgamal
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This package provides implementations of the multiplicative and additive versions of the ElGamal encryption scheme.
Supports:
- Positive and negative numbers.
- Multiplicative homomorphic multiplication of ciphertexts, negation of ciphertexts, exponentiation of ciphertexts with integral powers, check for zero underlying plaintext.
- Additive ElGamal: homomorphic addition of ciphertexts, negation of ciphertexts, multiplication of ciphertext with integral scalars.
The TNO PET Lab consists of generic software components, procedures, and functionalities developed and maintained on a regular basis to facilitate and aid in the development of PET solutions. The lab is a cross-project initiative allowing us to integrate and reuse previously developed PET functionalities to boost the development of new protocols and solutions.
The packagetno.mpc.encryption_schemes.elgamal
is part of theTNO Python Toolbox.
Limitations in (end-)use: the content of this software package may solely be used for applications that comply with international export control laws.
This implementation of cryptographic software has not been audited. Use at your own risk.
Documentation of thetno.mpc.encryption_schemes.elgamal
package can be foundhere.
Easily install thetno.mpc.encryption_schemes.elgamal
package usingpip
:
$python -m pip install tno.mpc.encryption_schemes.elgamal
Note: If you are cloning the repository and wish to edit the source code, besure to install the package in editable mode:
$python -m pip install -e'tno.mpc.encryption_schemes.elgamal'
If you wish to run the tests you can use:
$python -m pip install'tno.mpc.encryption_schemes.elgamal[tests]'
Note: A significant performance improvement can be achieved by installing the GMPY2 library.
$python -m pip install'tno.mpc.encryption_schemes.elgamal[gmpy]'
Basic usage of the multiplicative variant of ElGamal is as follows.Note that only the multiplicative variant of ElGamal allows for checking whether the plaintext is equal to zero without decrypting.This is explained in thebackground section.
fromtno.mpc.encryption_schemes.elgamal.elgamalimportElGamalif__name__=="__main__":# initialize ElGamal with key length of 1024 bitselgamal_scheme=ElGamal.from_security_parameter(bits=1024)# encrypt the number 8ciphertext1=elgamal_scheme.encrypt(8)# multiply the original plaintext by 5ciphertext1*=5# take the original plaintext to the power 2ciphertext1**=2# check whether the ciphertext is equal to zeroassertnotciphertext1.is_zero()# encrypt the number 10ciphertext2=elgamal_scheme.encrypt(10)# multiply the encrypted numbers with each otherencrypted_multiplication=ciphertext1*ciphertext2# ...communication...# decrypt the encrypted sum to 16000decrypted_multiplication=elgamal_scheme.decrypt(encrypted_multiplication)assertdecrypted_multiplication==16000
Basic usage of the additive variant of ElGamal is as follows.
fromtno.mpc.encryption_schemes.elgamal.elgamal_additiveimportElGamalAdditiveif__name__=="__main__":# initialize ElGamalAdditive with key length of 1024 bitselgamal_additive_scheme=ElGamalAdditive.from_security_parameter(bits=1024)# encrypt the number 8ciphertext1=elgamal_additive_scheme.encrypt(8)# add 5 to the original plaintextciphertext1+=5# multiply the original plaintext by 10ciphertext1*=10# encrypt the number 10ciphertext2=elgamal_additive_scheme.encrypt(10)# add both encrypted numbers togetherencrypted_sum=ciphertext1+ciphertext2# ...communication...# decrypt the encrypted sum to 140decrypted_sum=elgamal_additive_scheme.decrypt(encrypted_sum)assertdecrypted_sum==140
Running this example will show several warnings. The remainder of this documentation explains why the warnings are issued and how to get rid of them depending on the users' preferences.
An encrypted message is called a ciphertext. A ciphertext in the current package has a propertyis_fresh
that indicates whether this ciphertext has fresh randomness, in which case it can be communicated to another player securely. More specifically, a ciphertextc
is fresh if another user, knowledgeable of all prior communication and all current ciphertexts marked as fresh, cannot deduce any more private information from learningc
.
The package understands that the freshness of the result of a homomorphic operation depends on the freshness of the inputs, and that the homomorphic operation renders the inputs unfresh. For example, ifc1
andc2
are fresh ciphertexts, thenc12 = c1 + c2
is marked as a fresh encryption (no rerandomization needed) of the sum of the two underlying plaintexts. After the operation, ciphertextsc1
andc2
are no longer fresh.
The fact thatc1
andc2
were both fresh implies that, at some point, we randomized them. After the operationc12 = c1 + c2
, onlyc12
is fresh. This implies that one randomization was lost in the process. In particular, we wasted resources. An alternative approach was to have unfreshc1
andc2
then compute the unfresh resultc12
and only randomize that ciphertext. This time, no resources were wasted. The package issues a warning to inform the user this and similar efficiency opportunities.
The package integrates naturally withtno.mpc.communication
and if that is used for communication, its serialization logic will ensure that all sent ciphertexts are fresh. A warning is issued if a ciphertext was randomized in the proces. A ciphertext is always marked as unfresh after it is serialized. Similarly, all received ciphertexts are considered unfresh.
The crypto-neutral developer is facilitated by the package as follows: the package takes care of all bookkeeping, and the serialization used bytno.mpc.communication
takes care of all randomization. The warnings can bedisabled for a smoother experience.
The eager crypto-youngster can improve their understanding and hone their skills by learning from the warnings that the package provides in a safe environment. The package is safe to use when combined withtno.mpc.communication
. It remains to be safe while you transform your code from 'randomize-early' (fresh encryptions) to 'randomize-late' (unfresh encryptions, randomize before exposure). At that point you have optimized the efficiency of the library while ensuring that all exposed ciphertexts are fresh before they are serialized. In particular, you no longer rely on our serialization for (re)randomizing your ciphertexts.
Finally, the experienced cryptographer can turn off warnings / turn them into exceptions, or benefit from theis_fresh
flag for own purposes (e.g. different serializer or communication).
By default, thewarnings
package prints only the first occurence of a warning for each location (module + line number) where the warning is issued. The user may easilychange this behaviour to never see warnings:
fromtno.mpc.encryption_schemes.elgamal_baseimportEncryptionSchemeWarningwarnings.simplefilter("ignore",EncryptionSchemeWarning)
Alternatively, the user may pass"once"
,"always"
or even"error"
.
Finally, note that some operations issue two warnings, e.g.c1-c2
issues a warning for computing-c2
and a warning for computingc1 + (-c2)
.
Thebasic usage can be improved upon by explicitly randomizing at late as possible.
We demonstrate here for multiplicative ElGamal, but it works the analogous for additive ElGamal: instead ofencrypt()
one should useunsafe_encrypt
and explicitly userandomize()
after the local calculations are done.
fromtno.mpc.encryption_schemes.elgamal.elgamalimportElGamalif__name__=="__main__":elgamal_scheme=ElGamal.from_security_parameter(bits=1024)# unsafe_encrypt does NOT randomize the generated ciphertext; it is deterministic stillciphertext1=elgamal_scheme.unsafe_encrypt(8)ciphertext1*=5ciphertext1**=2ciphertext2=elgamal_scheme.unsafe_encrypt(10)# no randomness can be wasted by multiplying the two unfresh encryptionsencrypted_multiplication=ciphertext1*ciphertext2# randomize the result, which is now freshencrypted_multiplication.randomize()# ...communication...decrypted_multiplication=elgamal_scheme.decrypt(encrypted_multiplication)assertdecrypted_multiplication==16000
As explainedabove, this implementation avoids wasted randomization forencrypted_multiplication
orencrypted_sum
and therefore is more efficient.
Encrypting messages and randomizing ciphertexts is an involved operation that requires randomly generating large values and processing them in some way. This process can be sped up which will boost the performance of your script or package. The base packagetno.mpc.encryption_schemes.templates
provides several ways to more quickly generate randomness and we will show two of them below.
The simplest improvement gain is to generate the required amount of randomness as soon as the scheme is initialized (so prior to any call torandomize
orencrypt
):
fromtno.mpc.encryption_schemes.elgamal.elgamalimportElGamal# For the additive version, the above line should be replaced by# from tno.mpc.encryption_schemes.elgamal.elgamal_additive import ElGamalAdditiveif__name__=="__main__":# For the additive version, the ElGamal in the line below should be replaced by ElGamalAdditive.elgamal_scheme=ElGamal.from_security_parameter(bits=1024)elgamal_scheme.boot_randomness_generation(amount=5)# Possibly do some stuff hereformsginrange(5):# The required randomness for encryption is already prepared, so this operation is faster.elgamal_scheme.encrypt(msg)elgamal_scheme.shut_down()
CallingElGamal.boot_randomness_generation
will generate a number of processes that is each tasked with generating some of the requested randomness. By default, the number of processes equals the number of CPUs on your device.
A more advanced approach is to generate the randomness a priori and store it. Then, if you run your main protocol, all randomness is readily available. This looks as follows. First, the key-generating party generates a public-private keypair and shares the public key with the other participants. Now, every player pregenerates the amount of randomness needed for her part of the protocol and stores it in a file. For example, this can be done overnight or during the weekend. When the main protocol is executed, every player uses the same scheme (public key) as communicated before, configures the scheme to use the pregenerated randomness from file, and runs the main protocol without the need to generate randomness for encryption at that time. A minimal example is provided below.
frompathlibimportPathfromtypingimportListfromtno.mpc.communicationimportSerializationfromtno.mpc.encryption_schemes.templates.random_sourcesimportFileSourcefromtno.mpc.encryption_schemes.elgamal.elgamalimportElGamal,ElGamalCipherTextdefdeserializer(x:str)->tuple[int,int]:"""Deserialize a string representing a tuple of integers."""x=x.removeprefix("(").removesuffix(")")a,b=x.split(",")returnint(a),int(b)definitialize_and_store_scheme()->None:# Generate schemescheme=ElGamal.from_security_parameter(bits=1024)# Store without secret key for otherswithopen(Path("scheme_without_secret_key"),"wb")asfile:file.write(Serialization.pack(scheme,msg_id="",use_pickle=False))# Store with secret key for own usescheme.share_secret_key=Truewithopen(Path("scheme_with_secret_key"),"wb")asfile:file.write(Serialization.pack(scheme,msg_id="",use_pickle=False))# Tidy up to simulate real environment (program terminates)scheme.clear_instances()defload_scheme(path:Path)->ElGamal:# Load scheme from diskwithopen(path,"rb")asfile:scheme_raw=file.read()returnSerialization.unpack(scheme_raw)[1]defpregenerate_randomness_in_weekend(scheme:ElGamal,amount:int,path:Path)->None:# Generate randomnessscheme.boot_randomness_generation(amount)# Save randomness to comma-separated csvwithopen(path,"w")asfile:for_inrange(amount):file.write(f"{scheme.get_randomness()};")# Shut down processes gracefullyscheme.shut_down()defshow_pregenerated_randomness(scheme:ElGamal,amount:int,path:Path)->None:# Configure file as randomness sourcescheme.register_randomness_source(FileSource(path,delimiter=";",deserializer=deserializer) )# Consume randomness from fileforiinrange(amount):print(f"Random element{i}:{scheme.get_randomness()}")defuse_pregenerated_randomness_in_encryption(scheme:ElGamal,amount:int,path:Path)->List[ElGamalCipherText]:# Configure file as randomness sourcescheme.register_randomness_source(FileSource(path,delimiter=";",deserializer=deserializer) )# Consume randomness from fileciphertexts= [scheme.encrypt(_)for_inrange(amount)]returnciphertextsdefdecrypt_result(scheme:ElGamal,ciphertexts:List[ElGamalCipherText])->None:# Show resultfori,ciphertextinenumerate(ciphertexts):print(f"Decryption of ciphertext{i}:{scheme.decrypt(ciphertext)}")if__name__=="__main__":AMOUNT=5RANDOMNESS_PATH=Path("randomness.csv")# Alice initializes, stores and distributes the ElGamal schemeinitialize_and_store_scheme()# Tidy up to simulate real environment (second party doesn't yet have the ElGamal instance)ElGamal.clear_instances()# Bob loads the ElGamal scheme, pregenerates randomness and encrypts the values 0,...,AMOUNT-1scheme_without_secret_key=load_scheme("scheme_without_secret_key")assert (scheme_without_secret_key.secret_keyisNone ),"Loaded ElGamal scheme contains secret key! This is not supposed to happen."pregenerate_randomness_in_weekend(scheme_without_secret_key,AMOUNT,RANDOMNESS_PATH )show_pregenerated_randomness(scheme_without_secret_key,AMOUNT,RANDOMNESS_PATH)# Prints the following to screen (numbers will be different):# Random element 0: 663667452419034735381232312860937013...# Random element 1: ...# ...ciphertexts=use_pregenerated_randomness_in_encryption(scheme_without_secret_key,AMOUNT,RANDOMNESS_PATH )# Tidy up to simulate real environment (first party should use own ElGamal instance)ElGamal.clear_instances()# Alice receives the ciphertexts from Bob and decrypts themscheme_with_secret_key=load_scheme("scheme_with_secret_key")decrypt_result(scheme_with_secret_key,ciphertexts)# Prints the following to screen:# Decryption of ciphertext 0: 0.000# Decryption of ciphertext 1: 1.000# ...
The ElGamal encryption scheme is an asymmetric encryption scheme based on the discrete logarithm problem.Although a seemingly simple scheme, here some of the design choices of the implementation are highlighted.
There are two versions of the scheme, allowing for multiplicative or additive homomorphic operations.The multiplicative scheme is more widespread, therefore it is implemented in theElGamal
class.The additive scheme is implemented in theElGamalAdditive
class.Common functionality between the two versions are implemented in theElGamalBase
class.
Key material is created by generating a safe prime
The current implementation generates the safe prime
The generator
More on the considerations when implementing ElGamal can be found in thispaper.
For the multiplicative scheme, a message
Decryption of both versions is done by calculating
About
TNO PET Lab - secure Multi-Party Computation (MPC) - Encryption Schemes - ElGamal
Resources
License
Uh oh!
There was an error while loading.Please reload this page.