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

153 lines
5.3 KiB
Python

import random
import math
from typing import Optional
import sympy
from lightphe.models.Homomorphic import Homomorphic
from lightphe.commons.logger import Logger
logger = Logger(module="lightphe/cryptosystems/RSA.py")
class RSA(Homomorphic):
"""
RSA algorithm is partially homomorphic with respect to the multiplication
Ref: https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/
"""
REQUIRED_KEYS = {
"public_key": ["n", "e"],
"private_key": ["d"],
}
def __init__(
self,
keys: Optional[dict] = None,
key_size: Optional[int] = None,
encrypt_with_public=True,
max_tries: int = 10000,
):
"""
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
encrypt_with_public (boolean): RSA has two keys: private (d) and public (e).
If you encrypt a message with smo's public, then just that person can decrypt it
with his private (secure message). Otherwise, if you encrypt it with your private,
one can decrypt it with your public (digital signatures).
Set this arg to True if you want to do encryption with public key e,
and do decryption with private key d.
max_tries (int): maximum attempts to generate keys.
"""
self.keys = keys or self.generate_keys(
key_size=key_size or 1024, max_tries=max_tries
)
self.plaintext_modulo = self.keys["public_key"]["n"]
self.ciphertext_modulo = self.keys["public_key"]["n"]
self.encrypt_with_public = encrypt_with_public
def generate_keys(
self,
key_size: int,
max_tries: int = 10000,
) -> dict:
"""
Generate public and private keys of RSA cryptosystem
Args:
key_size (int): key size in bits
max_tries (int): maximum number of tries to generate keys
Returns:
keys (dict): having private_key and public_key keys
"""
keys = {}
keys["private_key"] = {}
keys["public_key"] = {}
for _ in range(max_tries):
# picking a prime modulus p and q
p = sympy.randprime(2 ** (key_size // 2 - 300), 2 ** (key_size // 2) - 1)
q = sympy.randprime(2 ** (key_size // 2 - 300), 2 ** (key_size // 2) - 1)
if p == q:
continue
n = p * q
phi = (p - 1) * (q - 1)
# select public exponent e
for _ in range(1000): # try max 1000 random e
e = random.randint(2, phi - 1)
if math.gcd(e, phi) == 1:
d = pow(e, -1, phi)
return {
"public_key": {"n": n, "e": e},
"private_key": {"d": d},
}
raise ValueError(f"Failed to generate RSA keys after {max_tries} tries")
def generate_random_key(self) -> int:
"""
RSA does not require random key for encryption, still return one
Returns:
random key (int): one time random key for encryption
"""
return random.randint(1, self.keys["public_key"]["n"] - 1)
def encrypt(self, plaintext: int) -> int:
"""
Encrypt plain messages with RSA
Args:
plaintext (int): plain message
Returns:
ciphertext (int): ciphertext encrypted with RSA
"""
n = self.keys["public_key"]["n"]
if plaintext > n:
plaintext = plaintext % n
logger.debug(
f"RSA can encrypt messages [1, {n}]. "
f"Seems you exceeded this limit. New plaintext is {plaintext}"
)
if self.encrypt_with_public is True:
e = self.keys["public_key"]["e"]
c = pow(plaintext, e, n)
else:
d = self.keys["private_key"]["d"]
c = pow(plaintext, d, n)
return c
def decrypt(self, ciphertext: int) -> int:
"""
Decrypt ciphertexts with RSA
Args:
ciphertext (int): encrypted message
decrypt_with_private (int): RSA has two keys: private (d) and public (e).
If you encrypt a message with smo's public, then just that person can decrypt it
with his private (secure message). Otherwise, if you encrypt it with your private,
one can decrypt it with your public (digital signatures).
Set this arg to True if you want to do encryption with public key e,
and do decryption with private key d.
Returns:
plaintext (int): restored message
"""
n = self.keys["public_key"]["n"]
if self.encrypt_with_public is True:
d = self.keys["private_key"]["d"]
p = pow(ciphertext, d, n)
else:
e = self.keys["public_key"]["e"]
p = pow(ciphertext, e, n)
return p
def multiply(self, ciphertext1: int, ciphertext2: int) -> int:
"""
Perform homomorphic multiplication on encrypted data.
Result of this must be equal to E(m1 * m2)
"""
n = self.keys["public_key"]["n"]
return (ciphertext1 * ciphertext2) % n