lightphe 0.0.17__tar.gz → 0.0.19__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. {lightphe-0.0.17 → lightphe-0.0.19}/PKG-INFO +11 -6
  2. {lightphe-0.0.17 → lightphe-0.0.19}/README.md +10 -5
  3. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/__init__.py +30 -11
  4. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/Benaloh.py +30 -10
  5. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/GoldwasserMicali.py +15 -3
  6. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/NaccacheStern.py +5 -1
  7. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/RSA.py +41 -25
  8. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Homomorphic.py +5 -1
  9. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/PKG-INFO +11 -6
  10. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/SOURCES.txt +1 -0
  11. {lightphe-0.0.17 → lightphe-0.0.19}/setup.py +1 -1
  12. lightphe-0.0.19/tests/test_large_keys.py +77 -0
  13. {lightphe-0.0.17 → lightphe-0.0.19}/LICENSE +0 -0
  14. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/__init__.py +0 -0
  15. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/logger.py +0 -0
  16. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/phe_utils.py +0 -0
  17. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/DamgardJurik.py +0 -0
  18. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/ElGamal.py +0 -0
  19. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/EllipticCurveElGamal.py +0 -0
  20. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
  21. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/Paillier.py +0 -0
  22. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/__init__.py +0 -0
  23. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Algorithm.py +0 -0
  24. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Ciphertext.py +0 -0
  25. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Tensor.py +0 -0
  26. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/__init__.py +0 -0
  27. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/dependency_links.txt +0 -0
  28. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/requires.txt +0 -0
  29. {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/top_level.txt +0 -0
  30. {lightphe-0.0.17 → lightphe-0.0.19}/requirements.txt +0 -0
  31. {lightphe-0.0.17 → lightphe-0.0.19}/setup.cfg +0 -0
  32. {lightphe-0.0.17 → lightphe-0.0.19}/tests/__init__.py +0 -0
  33. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_benaloh.py +0 -0
  34. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_ciphertexts.py +0 -0
  35. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_cloud.py +0 -0
  36. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_damgard.py +0 -0
  37. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_elgamal.py +0 -0
  38. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_ellipticcurveelgamal.py +0 -0
  39. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_exporting_keys.py +0 -0
  40. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_goldwasser.py +0 -0
  41. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_keys.py +0 -0
  42. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_naccache.py +0 -0
  43. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_okamoto.py +0 -0
  44. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_paillier.py +0 -0
  45. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_rsa.py +0 -0
  46. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_salary.py +0 -0
  47. {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_tensors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightphe
3
- Version: 0.0.17
3
+ Version: 0.0.19
4
4
  Summary: A Lightweight Partially Homomorphic Encryption Library for Python
5
5
  Home-page: https://github.com/serengil/LightPHE
6
6
  Author: Sefik Ilkin Serengil
@@ -129,7 +129,7 @@ assert cs.decrypt(c3) == m1 + m2
129
129
  assert cs.decrypt(c4) == k * m1
130
130
  ```
131
131
 
132
- On the other hand, if you adopt a multiplicatively homomorphic cryptosyste (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
132
+ On the other hand, if you adopt a multiplicatively homomorphic cryptosystem (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
133
133
 
134
134
  ```python
135
135
  # build a multiplicatively homomorphic cryptosystem
@@ -152,7 +152,7 @@ assert cs.decrypt(c3) == m1 * m2
152
152
 
153
153
  ### Ciphertext Regeneration
154
154
 
155
- 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.
155
+ The most of additively homomorphic algorithms allow 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.
156
156
 
157
157
  ```python
158
158
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -163,7 +163,7 @@ assert cs.decrypt(c1) == m1
163
163
 
164
164
  ### Elliptic Curve Cryptography
165
165
 
166
- ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
166
+ ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass (default), edwards and koblitz) and 100+ standard elliptic curve configurations.
167
167
 
168
168
  In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
169
169
 
@@ -171,7 +171,8 @@ In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic
171
171
  forms = ["weierstrass", "edwards", "koblitz"]
172
172
  phe = LightPHE(
173
173
  algorithm_name="EllipticCurve-ElGamal",
174
- form=forms[1],
174
+ form="edwards",
175
+ # curve="ed448", # optinally you can specify the curve for given form
175
176
  )
176
177
  ```
177
178
 
@@ -204,12 +205,16 @@ c4 = c1 + c2
204
205
  c5 = 3 * c1
205
206
 
206
207
  # perform element-wise multiplication between an encrypted embedding and plain embedding
207
- c6 = c1 * t3 # Encrypted-plaintext multiplication
208
+ c6 = c1 * t3
209
+
210
+ # encrypted dot product (likewise cosine similarity)
211
+ c7 = c1 @ t3
208
212
 
209
213
  # proof of work
210
214
  assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
211
215
  assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
212
216
  assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
217
+ assert np.allclose(cs.decrypt(c7)[0], sum([a * b for a, b in zip(t1, t3)]), rtol=1e-2)
213
218
  ```
214
219
 
215
220
  # Contributing
@@ -113,7 +113,7 @@ assert cs.decrypt(c3) == m1 + m2
113
113
  assert cs.decrypt(c4) == k * m1
114
114
  ```
115
115
 
116
- On the other hand, if you adopt a multiplicatively homomorphic cryptosyste (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
116
+ On the other hand, if you adopt a multiplicatively homomorphic cryptosystem (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
117
117
 
118
118
  ```python
119
119
  # build a multiplicatively homomorphic cryptosystem
@@ -136,7 +136,7 @@ assert cs.decrypt(c3) == m1 * m2
136
136
 
137
137
  ### Ciphertext Regeneration
138
138
 
139
- 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.
139
+ The most of additively homomorphic algorithms allow 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.
140
140
 
141
141
  ```python
142
142
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -147,7 +147,7 @@ assert cs.decrypt(c1) == m1
147
147
 
148
148
  ### Elliptic Curve Cryptography
149
149
 
150
- ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
150
+ ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass (default), edwards and koblitz) and 100+ standard elliptic curve configurations.
151
151
 
152
152
  In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
153
153
 
@@ -155,7 +155,8 @@ In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic
155
155
  forms = ["weierstrass", "edwards", "koblitz"]
156
156
  phe = LightPHE(
157
157
  algorithm_name="EllipticCurve-ElGamal",
158
- form=forms[1],
158
+ form="edwards",
159
+ # curve="ed448", # optinally you can specify the curve for given form
159
160
  )
160
161
  ```
161
162
 
@@ -188,12 +189,16 @@ c4 = c1 + c2
188
189
  c5 = 3 * c1
189
190
 
190
191
  # perform element-wise multiplication between an encrypted embedding and plain embedding
191
- c6 = c1 * t3 # Encrypted-plaintext multiplication
192
+ c6 = c1 * t3
193
+
194
+ # encrypted dot product (likewise cosine similarity)
195
+ c7 = c1 @ t3
192
196
 
193
197
  # proof of work
194
198
  assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
195
199
  assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
196
200
  assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
201
+ assert np.allclose(cs.decrypt(c7)[0], sum([a * b for a, b in zip(t1, t3)]), rtol=1e-2)
197
202
  ```
198
203
 
199
204
  # Contributing
@@ -9,10 +9,6 @@ import copy
9
9
 
10
10
  # 3rd party dependencies
11
11
  from tqdm import tqdm
12
- from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
13
- from lightecc.forms.weierstrass import Weierstrass
14
- from lightecc.forms.edwards import TwistedEdwards
15
- from lightecc.forms.koblitz import Koblitz
16
12
 
17
13
  # project dependencies
18
14
  from lightphe.models.Homomorphic import Homomorphic
@@ -38,7 +34,7 @@ from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
38
34
 
39
35
  logger = Logger(module="lightphe/__init__.py")
40
36
 
41
- VERSION = "0.0.17"
37
+ VERSION = "0.0.19"
42
38
 
43
39
 
44
40
  class LightPHE:
@@ -53,6 +49,8 @@ class LightPHE:
53
49
  precision: int = 5,
54
50
  form: Optional[str] = None,
55
51
  curve: Optional[str] = None,
52
+ plaintext_limit: Optional[int] = None,
53
+ max_tries: int = 10000,
56
54
  ):
57
55
  """
58
56
  Build LightPHE class
@@ -61,7 +59,7 @@ class LightPHE:
61
59
  | Paillier | Damgard-Jurik | Okamoto-Uchiyama | Benaloh | Naccache-Stern
62
60
  | Goldwasser-Micali
63
61
  keys (dict): optional private-public key pair
64
- key_file (str): if keys are exported, you can load them into cryptosystem
62
+ key_file (str): if keys were exported already, you can load them into cryptosystem
65
63
  key_size (int): key size in bits
66
64
  precision (int): precision for homomorphic operations on tensors
67
65
  form (str): specifies the form of the elliptic curve.
@@ -73,8 +71,14 @@ class LightPHE:
73
71
  - e.g. secp256k1 for weierstrass form
74
72
  - e.g. k-409 for koblitz form
75
73
  List of all available curves:
76
- github.com/serengil/LightPHE/blob/master/lightphe/elliptic_curve_forms/README.md
74
+ https://github.com/serengil/LightECC?tab=readme-ov-file#supported-curves
77
75
  This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
76
+ plaintext_limit (int, optional): Upper bound for plaintext values.
77
+ This parameter is only used if `algorithm_name` is 'Benaloh'.
78
+ max_tries (int): maximum attempts to generate keys. Default is 10000.
79
+ RSA, Benaloh, Naccache-Stern and Goldwasser-Micali algorithms
80
+ need multiple attempts to generate valid keys. Will be discarded
81
+ for other algorithms.
78
82
  """
79
83
  self.algorithm_name = algorithm_name
80
84
  self.precision = precision
@@ -90,6 +94,8 @@ class LightPHE:
90
94
  key_size=key_size,
91
95
  form=form,
92
96
  curve=curve,
97
+ plaintext_limit=plaintext_limit,
98
+ max_tries=max_tries,
93
99
  )
94
100
 
95
101
  def __build_cryptosystem(
@@ -99,6 +105,8 @@ class LightPHE:
99
105
  key_size: Optional[int] = None,
100
106
  form: Optional[str] = None,
101
107
  curve: Optional[str] = None,
108
+ plaintext_limit: Optional[int] = None,
109
+ max_tries: int = 10000,
102
110
  ) -> Union[
103
111
  RSA,
104
112
  ElGamal,
@@ -127,12 +135,18 @@ class LightPHE:
127
135
  - ed25519, ed448 for edwards form
128
136
  - secp256k1 for weierstrass form
129
137
  This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
138
+ plaintext_limit (int, optional): Upper bound for plaintext values.
139
+ This parameter is only used if `algorithm_name` is 'Benaloh'.
140
+ max_tries (int): maximum attempts to generate keys. Default is 10000.
141
+ RSA, Benaloh, Naccache-Stern and Goldwasser-Micali algorithms
142
+ need multiple attempts to generate valid keys. Will be discarded
143
+ for other algorithms.
130
144
  Returns
131
145
  cryptosystem
132
146
  """
133
147
  # build cryptosystem
134
148
  if algorithm_name == Algorithm.RSA:
135
- cs = RSA(keys=keys, key_size=key_size)
149
+ cs = RSA(keys=keys, key_size=key_size, max_tries=max_tries)
136
150
  elif algorithm_name == Algorithm.ElGamal:
137
151
  cs = ElGamal(keys=keys, key_size=key_size)
138
152
  elif algorithm_name == Algorithm.ExponentialElGamal:
@@ -148,11 +162,16 @@ class LightPHE:
148
162
  elif algorithm_name == Algorithm.OkamotoUchiyama:
149
163
  cs = OkamotoUchiyama(keys=keys, key_size=key_size)
150
164
  elif algorithm_name == Algorithm.Benaloh:
151
- cs = Benaloh(keys=keys, key_size=key_size)
165
+ cs = Benaloh(
166
+ keys=keys,
167
+ key_size=key_size,
168
+ plaintext_limit=plaintext_limit,
169
+ max_tries=max_tries,
170
+ )
152
171
  elif algorithm_name == Algorithm.NaccacheStern:
153
- cs = NaccacheStern(keys=keys, key_size=key_size)
172
+ cs = NaccacheStern(keys=keys, key_size=key_size, max_tries=max_tries)
154
173
  elif algorithm_name == Algorithm.GoldwasserMicali:
155
- cs = GoldwasserMicali(keys=keys, key_size=key_size)
174
+ cs = GoldwasserMicali(keys=keys, key_size=key_size, max_tries=max_tries)
156
175
  else:
157
176
  raise ValueError(f"unimplemented algorithm - {algorithm_name}")
158
177
  return cs
@@ -15,19 +15,37 @@ logger = Logger(module="lightphe/cryptosystems/Benaloh.py")
15
15
 
16
16
 
17
17
  class Benaloh(Homomorphic):
18
- def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
18
+ def __init__(
19
+ self,
20
+ keys: Optional[dict] = None,
21
+ key_size: Optional[int] = None,
22
+ plaintext_limit: Optional[int] = None,
23
+ max_tries: int = 10000,
24
+ ):
19
25
  """
20
26
  Args:
21
27
  keys (dict): private - public key pair.
22
28
  set this to None if you want to generate random keys.
23
- key_size (int): key size in bits. default is less than other cryptosystems
24
- because decryption of Benaloh requires to solve DLP :/
25
- """
26
- self.keys = keys or self.generate_keys(key_size or 1024)
29
+ key_size (int): key size in bits.
30
+ plaintext_limit (int, optional): Upper bound for plaintext values.
31
+ If provided, r is set to the next prime greater than this value;
32
+ otherwise, r is chosen randomly from a default range.
33
+ max_tries (int): maximum attempts to generate keys.
34
+ """
35
+ self.keys = keys or self.generate_keys(
36
+ key_size=key_size or 1024,
37
+ plaintext_limit=plaintext_limit,
38
+ max_tries=max_tries,
39
+ )
27
40
  self.plaintext_modulo = self.keys["public_key"]["r"]
28
41
  self.ciphertext_modulo = self.keys["public_key"]["n"]
29
42
 
30
- def generate_keys(self, key_size: int, max_tries: int = 10000) -> dict:
43
+ def generate_keys(
44
+ self,
45
+ key_size: int,
46
+ max_tries: int = 10000,
47
+ plaintext_limit: Optional[int] = None,
48
+ ) -> dict:
31
49
  """
32
50
  Generate public and private keys of Paillier cryptosystem
33
51
  Args:
@@ -49,8 +67,10 @@ class Benaloh(Homomorphic):
49
67
  phi = (p - 1) * (q - 1)
50
68
 
51
69
  # generate block size r
52
- # TODO: consider to get limit from user
53
- r = sympy.randprime(1000, 2000)
70
+ if plaintext_limit is None:
71
+ r = sympy.randprime(1000, 2000)
72
+ else:
73
+ r = sympy.nextprime(plaintext_limit)
54
74
  # plaintexts will be allowed in [0, r-1]
55
75
 
56
76
  # block size r checks
@@ -58,7 +78,7 @@ class Benaloh(Homomorphic):
58
78
  # r should divide p-1 without remainder
59
79
  (p - 1) % r == 0
60
80
  # r and (p - 1) / r must be coprimes
61
- and gcd(r, int((p - 1) / r)) == 1
81
+ and gcd(r, int((p - 1) // r)) == 1
62
82
  # r and q-1 must be coprimes
63
83
  and gcd(r, q - 1) == 1
64
84
  ):
@@ -86,7 +106,7 @@ class Benaloh(Homomorphic):
86
106
  else:
87
107
  raise RuntimeError(
88
108
  f"Failed to generate Benaloh keys after {max_tries} attempts."
89
- "Please try to rerun."
109
+ f"Please try to rerun or consider to decrese {plaintext_limit=}."
90
110
  )
91
111
 
92
112
  keys["public_key"]["y"] = y
@@ -23,17 +23,29 @@ class GoldwasserMicali(Homomorphic):
23
23
  Ref: sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/
24
24
  """
25
25
 
26
- def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
26
+ def __init__(
27
+ self,
28
+ keys: Optional[dict] = None,
29
+ key_size: Optional[int] = None,
30
+ max_tries: int = 10000,
31
+ ):
27
32
  """
28
33
  Args:
29
34
  keys (dict): private - public key pair.
30
35
  set this to None if you want to generate random keys.
31
36
  key_size (int): key size in bits
37
+ max_tries (int): maximum attempts to generate keys
32
38
  """
33
39
 
34
- self.keys = keys or self.generate_keys(key_size or 1024)
40
+ self.keys = keys or self.generate_keys(
41
+ key_size=key_size or 1024, max_tries=max_tries
42
+ )
35
43
  self.ciphertext_modulo = self.keys["public_key"]["n"]
36
- # TODO: not sure about the plaintext modulo
44
+
45
+ # Plaintext can be any integer (even larger than n) because it is internally
46
+ # encrypted bit by bit. Internally, each bit uses modulo 2, but LightPHE expects
47
+ # integers, not bits. The original integer can be fully restored after decryption
48
+ # even if it is larger than n.
37
49
  self.plaintext_modulo = self.keys["public_key"]["n"]
38
50
 
39
51
  def generate_keys(self, key_size: int, max_tries: int = 10000) -> dict:
@@ -25,6 +25,7 @@ class NaccacheStern(Homomorphic):
25
25
  keys: Optional[dict] = None,
26
26
  key_size: Optional[int] = None,
27
27
  deterministic: bool = False,
28
+ max_tries: int = 10000,
28
29
  ):
29
30
  """
30
31
  Args:
@@ -34,9 +35,12 @@ class NaccacheStern(Homomorphic):
34
35
  decryption requires to solve DLP.
35
36
  deterministic (boolean): deterministic or probabilistic version of
36
37
  cryptosystem
38
+ max_tries (int): maximum attempts to generate keys
37
39
  """
38
40
  # Naccache-Stern requires to solve DLP in decryption, so small key is recommended
39
- self.keys = keys or self.generate_keys(key_size or 1024)
41
+ self.keys = keys or self.generate_keys(
42
+ key_size=key_size or 1024, max_tries=max_tries
43
+ )
40
44
  self.plaintext_modulo = self.keys["public_key"]["sigma"]
41
45
  self.ciphertext_modulo = self.keys["public_key"]["n"]
42
46
  self.deterministic = deterministic
@@ -14,7 +14,13 @@ class RSA(Homomorphic):
14
14
  Ref: https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/
15
15
  """
16
16
 
17
- def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None, encrypt_with_public=True):
17
+ def __init__(
18
+ self,
19
+ keys: Optional[dict] = None,
20
+ key_size: Optional[int] = None,
21
+ encrypt_with_public=True,
22
+ max_tries: int = 10000,
23
+ ):
18
24
  """
19
25
  Args:
20
26
  keys (dict): private - public key pair.
@@ -26,17 +32,25 @@ class RSA(Homomorphic):
26
32
  one can decrypt it with your public (digital signatures).
27
33
  Set this arg to True if you want to do encryption with public key e,
28
34
  and do decryption with private key d.
35
+ max_tries (int): maximum attempts to generate keys.
29
36
  """
30
- self.keys = keys or self.generate_keys(key_size or 1024)
37
+ self.keys = keys or self.generate_keys(
38
+ key_size=key_size or 1024, max_tries=max_tries
39
+ )
31
40
  self.plaintext_modulo = self.keys["public_key"]["n"]
32
41
  self.ciphertext_modulo = self.keys["public_key"]["n"]
33
42
  self.encrypt_with_public = encrypt_with_public
34
43
 
35
- def generate_keys(self, key_size: int) -> dict:
44
+ def generate_keys(
45
+ self,
46
+ key_size: int,
47
+ max_tries: int = 10000,
48
+ ) -> dict:
36
49
  """
37
50
  Generate public and private keys of RSA cryptosystem
38
51
  Args:
39
52
  key_size (int): key size in bits
53
+ max_tries (int): maximum number of tries to generate keys
40
54
  Returns:
41
55
  keys (dict): having private_key and public_key keys
42
56
  """
@@ -44,33 +58,35 @@ class RSA(Homomorphic):
44
58
  keys["private_key"] = {}
45
59
  keys["public_key"] = {}
46
60
 
47
- while True:
48
- try:
49
- # picking a prime modulus p and q
50
- p = sympy.randprime(200, 2 ** int(key_size / 2) - 1)
51
- q = sympy.randprime(200, 2 ** int(key_size / 2) - 1)
61
+ for _ in range(max_tries):
62
+ # picking a prime modulus p and q
63
+ p = sympy.randprime(2 ** (key_size // 2 - 300), 2 ** (key_size // 2) - 1)
64
+ q = sympy.randprime(2 ** (key_size // 2 - 300), 2 ** (key_size // 2) - 1)
52
65
 
53
- n = p * q
54
- phi = (p - 1) * (q - 1)
66
+ if p == q:
67
+ continue
55
68
 
56
- # select public exponent e
57
- while True:
58
- e = random.randint(1, phi - 1)
59
- if math.gcd(e, n) == 1:
60
- break
69
+ n = p * q
70
+ phi = (p - 1) * (q - 1)
61
71
 
62
- d = pow(e, -1, phi)
63
- break
64
- except:
65
- pass
66
-
67
- keys["public_key"]["n"] = n
68
- keys["public_key"]["e"] = e
69
- keys["private_key"]["d"] = d
70
- return keys
72
+ # select public exponent e
73
+ for _ in range(1000): # try max 1000 random e
74
+ e = random.randint(2, phi - 1)
75
+ if math.gcd(e, phi) == 1:
76
+ d = pow(e, -1, phi)
77
+ return {
78
+ "public_key": {"n": n, "e": e},
79
+ "private_key": {"d": d},
80
+ }
81
+ raise ValueError(f"Failed to generate RSA keys after {max_tries} tries")
71
82
 
72
83
  def generate_random_key(self) -> int:
73
- pass
84
+ """
85
+ RSA does not require random key for encryption, still return one
86
+ Returns:
87
+ random key (int): one time random key for encryption
88
+ """
89
+ return random.randint(1, self.keys["public_key"]["n"] - 1)
74
90
 
75
91
  def encrypt(self, plaintext: int) -> int:
76
92
  """
@@ -15,7 +15,11 @@ class Homomorphic(ABC):
15
15
 
16
16
  @abstractmethod
17
17
  def generate_keys(
18
- self, key_size: int, s: Optional[int] = None, max_retries: Optional[int] = None
18
+ self,
19
+ key_size: int,
20
+ s: Optional[int] = None,
21
+ max_retries: Optional[int] = None,
22
+ plaintext_limit: Optional[int] = None,
19
23
  ) -> dict:
20
24
  pass
21
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lightphe
3
- Version: 0.0.17
3
+ Version: 0.0.19
4
4
  Summary: A Lightweight Partially Homomorphic Encryption Library for Python
5
5
  Home-page: https://github.com/serengil/LightPHE
6
6
  Author: Sefik Ilkin Serengil
@@ -129,7 +129,7 @@ assert cs.decrypt(c3) == m1 + m2
129
129
  assert cs.decrypt(c4) == k * m1
130
130
  ```
131
131
 
132
- On the other hand, if you adopt a multiplicatively homomorphic cryptosyste (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
132
+ On the other hand, if you adopt a multiplicatively homomorphic cryptosystem (e.g. RSA or ElGamal), you can multiply ciphertexts without revealing the private key or the plaintext.
133
133
 
134
134
  ```python
135
135
  # build a multiplicatively homomorphic cryptosystem
@@ -152,7 +152,7 @@ assert cs.decrypt(c3) == m1 * m2
152
152
 
153
153
  ### Ciphertext Regeneration
154
154
 
155
- 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.
155
+ The most of additively homomorphic algorithms allow 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.
156
156
 
157
157
  ```python
158
158
  c1_prime = cs.regenerate_ciphertext(c1)
@@ -163,7 +163,7 @@ assert cs.decrypt(c1) == m1
163
163
 
164
164
  ### Elliptic Curve Cryptography
165
165
 
166
- ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass, edwards and koblitz) and 100+ standard elliptic curve configurations.
166
+ ECC is a powerful public-key cryptosystem based on the algebraic structure of elliptic curves over finite fields. The library supports 3 elliptic curve forms (weierstrass (default), edwards and koblitz) and 100+ standard elliptic curve configurations.
167
167
 
168
168
  In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) scheme is implemented, offering a secure and efficient homomorphic encryption option.
169
169
 
@@ -171,7 +171,8 @@ In LightPHE, the [Elliptic Curve ElGamal](https://sefiks.com/2018/08/21/elliptic
171
171
  forms = ["weierstrass", "edwards", "koblitz"]
172
172
  phe = LightPHE(
173
173
  algorithm_name="EllipticCurve-ElGamal",
174
- form=forms[1],
174
+ form="edwards",
175
+ # curve="ed448", # optinally you can specify the curve for given form
175
176
  )
176
177
  ```
177
178
 
@@ -204,12 +205,16 @@ c4 = c1 + c2
204
205
  c5 = 3 * c1
205
206
 
206
207
  # perform element-wise multiplication between an encrypted embedding and plain embedding
207
- c6 = c1 * t3 # Encrypted-plaintext multiplication
208
+ c6 = c1 * t3
209
+
210
+ # encrypted dot product (likewise cosine similarity)
211
+ c7 = c1 @ t3
208
212
 
209
213
  # proof of work
210
214
  assert np.allclose(cs.decrypt(c4), [a + b for a, b in zip(t1, t2)], rtol=1e-2)
211
215
  assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
212
216
  assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
217
+ assert np.allclose(cs.decrypt(c7)[0], sum([a * b for a, b in zip(t1, t3)]), rtol=1e-2)
213
218
  ```
214
219
 
215
220
  # Contributing
@@ -36,6 +36,7 @@ tests/test_ellipticcurveelgamal.py
36
36
  tests/test_exporting_keys.py
37
37
  tests/test_goldwasser.py
38
38
  tests/test_keys.py
39
+ tests/test_large_keys.py
39
40
  tests/test_naccache.py
40
41
  tests/test_okamoto.py
41
42
  tests/test_paillier.py
@@ -8,7 +8,7 @@ with open("requirements.txt", "r", encoding="utf-8") as f:
8
8
 
9
9
  setuptools.setup(
10
10
  name="lightphe",
11
- version="0.0.17",
11
+ version="0.0.19",
12
12
  author="Sefik Ilkin Serengil",
13
13
  author_email="serengil@gmail.com",
14
14
  description="A Lightweight Partially Homomorphic Encryption Library for Python",
@@ -0,0 +1,77 @@
1
+ import time
2
+ import unittest
3
+
4
+ from lightphe import LightPHE
5
+ from lightphe.commons.logger import Logger
6
+
7
+ logger = Logger(module="tests/test_large_keys.py")
8
+
9
+
10
+ @unittest.skip("This is an experimental test for large key sizes.")
11
+ def test_large_keys():
12
+ """
13
+ Use this test to see no problem for larger keys.
14
+
15
+ Algorithm| Key Size | Key Generation Time | Encryption Time | Homomorphic Time | Decryption Time
16
+ --- | --- | --- | --- | --- | ---
17
+ Goldwasser-Micali | 1024 | 0.01 | 0.00 | 0.00 |0.00
18
+ Goldwasser-Micali | 2048 | 0.08 | 0.00 | 0.00 |0.03
19
+ Goldwasser-Micali | 3072 | 0.45 | 0.00 | 0.00 |0.08
20
+ Goldwasser-Micali | 7680 | 11.30 | 0.00 | 0.00 |1.21
21
+
22
+ Benaloh | 1024 | 4.91 | 0.00 | 0.00 |0.01
23
+ Benaloh | 2048 | 140.54 | 0.00 | 0.00 |0.03
24
+ Benaloh | 3072 | 439.46 | 0.00 | 0.00 |0.08
25
+
26
+ Naccache-Stern | 1024 | 19.97 | 0.00 | 0.00 |0.06
27
+ """
28
+ algorithms = [
29
+ "Naccache-Stern",
30
+ "Goldwasser-Micali",
31
+ "Benaloh",
32
+ ]
33
+ key_sizes = [1024, 2048, 3072, 7680]
34
+
35
+ for key_size in key_sizes:
36
+ for algorithm_name in algorithms:
37
+ logger.debug(f"Testing {algorithm_name} with key size {key_size} bits")
38
+ tic = time.time()
39
+ cs = LightPHE(algorithm_name=algorithm_name, key_size=key_size)
40
+ toc = time.time()
41
+ keygen_time = toc - tic
42
+
43
+ m1 = 17
44
+ m2 = 23
45
+
46
+ tic = time.time()
47
+ c1 = cs.encrypt(m1)
48
+ c2 = cs.encrypt(m2)
49
+ toc = time.time()
50
+ encryption_time = toc - tic
51
+
52
+ if algorithm_name in ["Naccache-Stern", "Benaloh"]:
53
+ tic = time.time()
54
+ c3 = c1 + c2
55
+ toc = time.time()
56
+ homomorphic_time = toc - tic
57
+
58
+ tic = time.time()
59
+ result = cs.decrypt(c3)
60
+ toc = time.time()
61
+ decryption_time = toc - tic
62
+ assert result == m1 + m2
63
+ elif algorithm_name in ["Goldwasser-Micali"]:
64
+ tic = time.time()
65
+ c3 = c1 ^ c2
66
+ toc = time.time()
67
+ homomorphic_time = toc - tic
68
+
69
+ tic = time.time()
70
+ result = cs.decrypt(c3)
71
+ toc = time.time()
72
+ decryption_time = toc - tic
73
+ assert result == m1 ^ m2
74
+
75
+ logger.info(
76
+ f"{algorithm_name} | {key_size} | {keygen_time:.2f} | {encryption_time:.2f} | {homomorphic_time:.2f} |{decryption_time:.2f}"
77
+ )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes