TKK_E32232028/.venv/lib/python3.10/site-packages/lightphe/cryptosystems/EllipticCurveElGamal.py

218 lines
7.2 KiB
Python

# built-in dependencies
import random
from typing import Optional
# 3rd party dependencies
from lightecc import LightECC as ECC
from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
# project dependencies
from lightphe.models.Homomorphic import Homomorphic
from lightphe.commons.logger import Logger
logger = Logger(module="lightphe/cryptosystems/EllipticCurveElGamal.py")
class EllipticCurveElGamal(Homomorphic):
"""
Elliptic Curve ElGamal algorithm is an additively homomorphic algorithm
Unluckily, it requires to solve (EC)DLP to restore plaintext in decryption
However it is easy to restore plaintext while plaintext is not very large
unsimilar to Benaloh or Naccache-Stern
Ref: https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/
"""
REQUIRED_KEYS = {
"public_key": ["Qa"],
"private_key": ["ka"],
}
def __init__(
self,
keys: Optional[dict] = None,
key_size: Optional[int] = None,
form: Optional[str] = None,
curve: Optional[str] = None,
):
"""
Args:
keys (dict): private - public key pair.
set this to None if you want to generate random keys.
key_size (int): key size in bits. default is 160.
this is equivalent to 1024 bit RSA.
form (str): specifies the elliptic curve form.
Options are 'weierstrass' (default), 'edwards'.
curve (str): specifies the elliptic curve to use.
Options:
- ed25519, ed448 for edwards form
- secp256k1 for weierstrass form
This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
"""
self.ecc = ECC(form_name=form, curve_name=curve)
self.keys = keys or self.generate_keys(key_size or self.ecc.n.bit_length())
if curve is not None:
self.keys["curve"] = curve
if form is not None:
self.keys["form"] = form
self.plaintext_modulo = self.ecc.modulo
self.ciphertext_modulo = self.ecc.modulo
def generate_keys(self, key_size: int):
"""
Generate public and private keys of Elliptic Curve ElGamal cryptosystem
Args:
key_size (int): key size in bits
Returns:
keys (dict): having private_key and public_key keys
"""
keys = {}
keys["private_key"] = {}
keys["public_key"] = {}
# private key
ka = random.getrandbits(key_size)
logger.debug(
f"{key_size} bit private key generated for Elliptic Curve ElGamal."
)
# base point
# public key
Qa = self.ecc.G * ka
keys["public_key"]["Qa"] = Qa.get_point()
keys["private_key"]["ka"] = ka
return keys
def generate_random_key(self) -> int:
"""
Elliptic Curve ElGamal requires to generate one-time random key per encryption
Returns:
random key (int): one time random key for encryption
"""
# return random.getrandbits(128)
return random.getrandbits(self.ecc.n.bit_length())
def encrypt(self, plaintext: int, random_key: Optional[int] = None) -> tuple:
"""
Encrypt plaintext with Elliptic Curve ElGamal
Args:
plaintext (int): message to encrypt
random_key (int): random key for encryption. Do not set this to a static value.
Returns
ciphertext (tuple): c1 and c2
"""
# public key
x, y = self.keys["public_key"]["Qa"]
Qa = EllipticCurvePoint(x=x, y=y, curve=self.ecc.curve)
# random key
r = random_key or self.generate_random_key()
s = self.ecc.G * plaintext
c1 = self.ecc.G * r
c2 = (Qa * r) + s
return c1.get_point(), c2.get_point()
def decrypt(self, ciphertext: tuple) -> int:
"""
Decrypt ciphertext with Elliptic Curve ElGamal
Args:
ciphertext (tuple): c1 and c2
Returns:
plaintext (int): restored message
"""
# private key
ka = self.keys["private_key"]["ka"]
# c1 and c2 as tuple of integers
c1, c2 = ciphertext
c1 = EllipticCurvePoint(x=c1[0], y=c1[1], curve=self.ecc.curve)
c2 = EllipticCurvePoint(x=c2[0], y=c2[1], curve=self.ecc.curve)
s_prime = (-c1 * ka) + c2
# s_prime is a point on the elliptic curve
# s_prime = k x G
# we need to find k from known s_prime and G
# this requires to solve ECDLP
return s_prime / self.ecc.G
def add(self, ciphertext1: tuple, ciphertext2: tuple) -> tuple:
"""
Perform homomorphic addition on encrypted data
Result of this must be equal to E(m1 + m2)
Args:
ciphertext1 (dict): Elliptic Curve ElGamal ciphertext consisting of c1 and c2 keys
ciphertext2 (dict): Elliptic Curve ElGamal ciphertext consisting of c1 and c2 keys
Returns
ciphertext (dict): Elliptic Curve ElGamal ciphertext consisting of c1 and c2 keys
"""
c1_1, c1_2 = ciphertext1
c2_1, c2_2 = ciphertext2
# cast them to elliptic curve points
c1_1 = EllipticCurvePoint(x=c1_1[0], y=c1_1[1], curve=self.ecc.curve)
c1_2 = EllipticCurvePoint(x=c1_2[0], y=c1_2[1], curve=self.ecc.curve)
c2_1 = EllipticCurvePoint(x=c2_1[0], y=c2_1[1], curve=self.ecc.curve)
c2_2 = EllipticCurvePoint(x=c2_2[0], y=c2_2[1], curve=self.ecc.curve)
a = c1_1 + c2_1
b = c1_2 + c2_2
return a.get_point(), b.get_point()
def multiply_by_constant(self, ciphertext: tuple, constant: int) -> tuple:
"""
Multiply a ciphertext with a plain constant.
Result of this must be equal to k x E(m1) = E(m1 * k)
where E(m1) = ciphertext
Args:
ciphertext (int): ciphertext created with Elliptic Curve ElGamal
constant (int): known plain constant
Returns:
ciphertext (int): new ciphertext created with Elliptic Curve ElGamal
"""
# Both P and Q are tuples of integers
P, Q = ciphertext
# cast P and Q to EllipticCurvePoint
P = EllipticCurvePoint(x=P[0], y=P[1], curve=self.ecc.curve)
Q = EllipticCurvePoint(x=Q[0], y=Q[1], curve=self.ecc.curve)
P_prime = P * constant
Q_prime = Q * constant
return P_prime.get_point(), Q_prime.get_point()
def reencrypt(self, ciphertext: tuple) -> tuple:
"""
Re-encrypt a ciphertext with a new random key
Args:
ciphertext (tuple): c1 and c2
Returns:
ciphertext (tuple): new c1 and c2
"""
r_prime = self.generate_random_key()
x, y = self.keys["public_key"]["Qa"]
Qa = EllipticCurvePoint(x=x, y=y, curve=self.ecc.curve)
# c1 and c2 as tuple of integers
c1, c2 = ciphertext
c1 = EllipticCurvePoint(x=c1[0], y=c1[1], curve=self.ecc.curve)
c2 = EllipticCurvePoint(x=c2[0], y=c2[1], curve=self.ecc.curve)
c1_prime = c1 + r_prime * self.ecc.G
c2_prime = c2 + r_prime * Qa
return c1_prime.get_point(), c2_prime.get_point()