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.
- {lightphe-0.0.17 → lightphe-0.0.19}/PKG-INFO +11 -6
- {lightphe-0.0.17 → lightphe-0.0.19}/README.md +10 -5
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/__init__.py +30 -11
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/Benaloh.py +30 -10
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/GoldwasserMicali.py +15 -3
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/NaccacheStern.py +5 -1
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/RSA.py +41 -25
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Homomorphic.py +5 -1
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/PKG-INFO +11 -6
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/SOURCES.txt +1 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/setup.py +1 -1
- lightphe-0.0.19/tests/test_large_keys.py +77 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/LICENSE +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/EllipticCurveElGamal.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/requirements.txt +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/setup.cfg +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/__init__.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_cloud.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_damgard.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_keys.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_naccache.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_paillier.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_rsa.py +0 -0
- {lightphe-0.0.17 → lightphe-0.0.19}/tests/test_salary.py +0 -0
- {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.
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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.
|
|
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
|
|
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/
|
|
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(
|
|
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__(
|
|
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.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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(
|
|
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
|
-
|
|
53
|
-
|
|
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)
|
|
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__(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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__(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
66
|
+
if p == q:
|
|
67
|
+
continue
|
|
55
68
|
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|