lightphe 0.0.8__tar.gz → 0.0.9__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.8 → lightphe-0.0.9}/PKG-INFO +75 -106
- {lightphe-0.0.8 → lightphe-0.0.9}/README.md +74 -105
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/__init__.py +14 -44
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/Benaloh.py +3 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/DamgardJurik.py +2 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/ElGamal.py +2 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/EllipticCurveElGamal.py +24 -13
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/GoldwasserMicali.py +3 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/NaccacheStern.py +3 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/OkamotoUchiyama.py +2 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/Paillier.py +2 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/RSA.py +2 -2
- lightphe-0.0.9/lightphe/elliptic_curve_forms/edwards.py +61 -0
- lightphe-0.0.9/lightphe/elliptic_curve_forms/weierstrass.py +75 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/models/Ciphertext.py +3 -3
- lightphe-0.0.9/lightphe/models/EllipticCurve.py +45 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/models/Tensor.py +9 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe.egg-info/PKG-INFO +75 -106
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe.egg-info/SOURCES.txt +3 -2
- {lightphe-0.0.8 → lightphe-0.0.9}/setup.py +1 -1
- lightphe-0.0.9/tests/__init__.py +0 -0
- lightphe-0.0.9/tests/test_ellipticcurveelgamal.py +69 -0
- lightphe-0.0.8/lightphe/elliptic/Weierstrass.py +0 -98
- lightphe-0.0.8/lightphe/models/EllipticCurve.py +0 -18
- lightphe-0.0.8/tests/__init__.py +0 -219
- lightphe-0.0.8/tests/test_ellipticcurveelgamal.py +0 -64
- {lightphe-0.0.8 → lightphe-0.0.9}/LICENSE +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.8/lightphe/elliptic → lightphe-0.0.9/lightphe/elliptic_curve_forms}/__init__.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/models/Homomorphic.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/requirements.txt +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/setup.cfg +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_cloud.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_damgard.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_naccache.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_paillier.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_rsa.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/tests/test_salary.py +0 -0
- {lightphe-0.0.8 → lightphe-0.0.9}/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.9
|
|
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
|
|
@@ -36,7 +36,7 @@ License-File: LICENSE
|
|
|
36
36
|
|
|
37
37
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
38
38
|
|
|
39
|
-
LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/)
|
|
39
|
+
LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms) [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/).
|
|
40
40
|
|
|
41
41
|
# Partially vs Fully Homomorphic Encryption
|
|
42
42
|
|
|
@@ -98,7 +98,7 @@ algorithms = [
|
|
|
98
98
|
"EllipticCurve-ElGamal"
|
|
99
99
|
]
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
phe = LightPHE(algorithm_name = algorithms[0])
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
# Encryption & Decryption
|
|
@@ -110,10 +110,10 @@ Once you built your cryptosystem, you will be able to encrypt and decrypt messag
|
|
|
110
110
|
m = 17
|
|
111
111
|
|
|
112
112
|
# calculate ciphertext
|
|
113
|
-
c =
|
|
113
|
+
c = phe.encrypt(m)
|
|
114
114
|
|
|
115
115
|
# proof of work
|
|
116
|
-
assert
|
|
116
|
+
assert phe.decrypt(c) == m
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
# Homomorphic Operations
|
|
@@ -122,76 +122,90 @@ Once you have the ciphertext, you will be able to perform homomorphic operations
|
|
|
122
122
|
|
|
123
123
|
### On-Prem Encryption
|
|
124
124
|
|
|
125
|
-
This
|
|
125
|
+
This code snippet illustrates how to generate a random public-private key pair using the Paillier and encrypt a plaintext pair. The resulting ciphertext pair, c1 and c2, along with the public key, is then sent from the on-premises environment to the cloud.
|
|
126
126
|
|
|
127
127
|
```python
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
128
|
+
def on_premise() -> Tuple[int, int, dict]:
|
|
129
|
+
"""
|
|
130
|
+
Executes on-premise operations: initializes a cryptosystem by generating
|
|
131
|
+
a random public-private key pair, then encrypts two plaintext values.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
result (tuple): A tuple containing:
|
|
135
|
+
- c1 (int): The first ciphertext
|
|
136
|
+
- c2 (int): The second ciphertext
|
|
137
|
+
- public_key (dict): The public key for the cryptosystem
|
|
138
|
+
"""
|
|
139
|
+
# generate a random private-public key pair
|
|
140
|
+
phe = LightPHE(algorithm_name = "Paillier")
|
|
141
|
+
|
|
142
|
+
# define plaintexts
|
|
143
|
+
m1 = 10000 # base salary in usd
|
|
144
|
+
m2 = 500 # wage increase in usd
|
|
145
|
+
|
|
146
|
+
# calculate ciphertexts
|
|
147
|
+
c1 = phe.encrypt(m1).value
|
|
148
|
+
c2 = phe.encrypt(m2).value
|
|
149
|
+
|
|
150
|
+
return (c1, c2, phe.cs.public_key)
|
|
146
151
|
```
|
|
147
152
|
|
|
148
153
|
### Homomorphic Operations on Cloud
|
|
149
154
|
|
|
150
|
-
This
|
|
155
|
+
This code snippet demonstrates how to perform homomorphic addition on the cloud side without using the private key. However, the cloud is unable to decrypt c3 itself, even though it is the one that calculated it.
|
|
151
156
|
|
|
152
157
|
```python
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
c1
|
|
160
|
-
c2
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
158
|
+
def cloud(c1: int, c2: int, public_key: dict) -> int:
|
|
159
|
+
"""
|
|
160
|
+
Performs cloud-side operations: reconstructs a cryptosystem using the
|
|
161
|
+
provided public key and executes a homomorphic addition on two ciphertexts.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
c1 (int): The first ciphertext
|
|
165
|
+
c2 (int): The second ciphertext
|
|
166
|
+
public_key (dict): The public key of an existing cryptosystem
|
|
167
|
+
Retunrs:
|
|
168
|
+
c3 (int): The resulting ciphertext after homomorphic addition
|
|
169
|
+
"""
|
|
170
|
+
# restore cryptosystem with just the public key
|
|
171
|
+
phe = LightPHE(algorithm_name = "Paillier", keys = public_key)
|
|
172
|
+
|
|
173
|
+
# cast c1 and c2 to ciphertext objects
|
|
174
|
+
c1 = phe.create_ciphertext_obj(c1)
|
|
175
|
+
c2 = phe.create_ciphertext_obj(c2)
|
|
176
|
+
|
|
177
|
+
# confirm that cloud cannot decrypt c1
|
|
178
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
179
|
+
phe.decrypt(c1)
|
|
180
|
+
|
|
181
|
+
# confirm that cloud cannot decrypt c2
|
|
182
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
183
|
+
phe.decrypt(c2)
|
|
184
|
+
|
|
185
|
+
# perform homomorphic addition
|
|
186
|
+
c3 = c1 + c2
|
|
187
|
+
|
|
188
|
+
# confirm that cloud cannot decrypt c3
|
|
189
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
190
|
+
phe.decrypt(c3)
|
|
191
|
+
|
|
192
|
+
return c3.value
|
|
176
193
|
```
|
|
177
194
|
|
|
178
|
-
### On-Prem Decryption
|
|
195
|
+
### On-Prem Decryption And Proof of Work
|
|
179
196
|
|
|
180
|
-
This
|
|
197
|
+
This code snippet demonstrates the proof of work. Even though c3 was calculated in the cloud by adding c1 and c2, on-premises can validate that its decryption must be equal to the addition of plaintexts m1 and m2.
|
|
181
198
|
|
|
182
199
|
```python
|
|
183
|
-
# on-prem side receives c3 from cloud
|
|
184
|
-
|
|
185
|
-
# restore cryptosystem with the exported private key
|
|
186
|
-
cs = LightPHE(algorithm_name = "Paillier", key_file = "private.txt")
|
|
187
|
-
|
|
188
200
|
# proof of work - private key required
|
|
189
|
-
assert
|
|
201
|
+
assert phe.decrypt(c3) == m1 + m2
|
|
190
202
|
```
|
|
191
203
|
|
|
204
|
+
In this homomorphic pipeline, the cloud's computational power was utilized to calculate c3, but it can only be decrypted by the on-premises side. Additionally, while we performed the encryption on the on-premises side, this is not strictly necessary; only the public key is required for encryption. Therefore, encryption can also be performed on the non-premises side. This approach is particularly convenient when collecting data from multiple edge devices and storing all of it in the cloud simultaneously.
|
|
205
|
+
|
|
192
206
|
### Scalar Multiplication
|
|
193
207
|
|
|
194
|
-
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. This
|
|
208
|
+
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. This code snippet demonstrates how to perform scalar multiplication on encrypted data using Paillier homomorphic encryption with the LightPHE library.
|
|
195
209
|
|
|
196
210
|
```python
|
|
197
211
|
# increasing something 5%
|
|
@@ -201,7 +215,7 @@ k = 1.05
|
|
|
201
215
|
c4 = k * c1
|
|
202
216
|
|
|
203
217
|
# proof of work on-prem - private key is required
|
|
204
|
-
assert
|
|
218
|
+
assert phe.decrypt(c4) == k * m1
|
|
205
219
|
```
|
|
206
220
|
|
|
207
221
|
### Ciphertext Regeneration
|
|
@@ -209,10 +223,10 @@ assert cs.decrypt(c4) == k * m1
|
|
|
209
223
|
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.
|
|
210
224
|
|
|
211
225
|
```python
|
|
212
|
-
c1_prime =
|
|
226
|
+
c1_prime = phe.regenerate_ciphertext(c1)
|
|
213
227
|
assert c1_prime.value != c1.value
|
|
214
|
-
assert
|
|
215
|
-
assert
|
|
228
|
+
assert phe.decrypt(c1_prime) == m1
|
|
229
|
+
assert phe.decrypt(c1) == m1
|
|
216
230
|
```
|
|
217
231
|
|
|
218
232
|
### Unsupported Operations
|
|
@@ -231,51 +245,6 @@ with pytest.raises(ValueError, match="Paillier is not homomorphic with respect t
|
|
|
231
245
|
|
|
232
246
|
However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.
|
|
233
247
|
|
|
234
|
-
# Working with vectors
|
|
235
|
-
|
|
236
|
-
You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.
|
|
237
|
-
|
|
238
|
-
```python
|
|
239
|
-
# build an additively homomorphic cryptosystem
|
|
240
|
-
cs = LightPHE(algorithm_name="Paillier")
|
|
241
|
-
|
|
242
|
-
# define plain tensors
|
|
243
|
-
t1 = [1.005, 2.05, 3.5, 4]
|
|
244
|
-
t2 = [5, 6.2, 7.002, 8.02]
|
|
245
|
-
|
|
246
|
-
# encrypt tensors
|
|
247
|
-
c1 = cs.encrypt(t1)
|
|
248
|
-
c2 = cs.encrypt(t2)
|
|
249
|
-
|
|
250
|
-
# perform homomorphic addition
|
|
251
|
-
c3 = c1 + c2
|
|
252
|
-
|
|
253
|
-
# perform homomorphic element-wise multiplication
|
|
254
|
-
c4 = c1 * c2
|
|
255
|
-
|
|
256
|
-
# perform homomorphic scalar multiplication
|
|
257
|
-
k = 5
|
|
258
|
-
c5 = k * c1
|
|
259
|
-
|
|
260
|
-
# decrypt the addition tensor
|
|
261
|
-
t3 = cs.decrypt(c3)
|
|
262
|
-
|
|
263
|
-
# decrypt the element-wise multiplied tensor
|
|
264
|
-
t4 = cs.decrypt(c4)
|
|
265
|
-
|
|
266
|
-
# decrypt the scalar multiplied tensor
|
|
267
|
-
t5 = cs.decrypt(c5)
|
|
268
|
-
|
|
269
|
-
# data validations
|
|
270
|
-
threshold = 0.5
|
|
271
|
-
for i in range(0, len(t1)):
|
|
272
|
-
assert abs((t1[i] + t2[i]) - t3[i]) < threshold
|
|
273
|
-
assert abs((t1[i] * t2[i]) - t4[i]) < threshold
|
|
274
|
-
assert abs((t1[i] * k) - t5[i]) < threshold
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.
|
|
278
|
-
|
|
279
248
|
# Contributing
|
|
280
249
|
|
|
281
250
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
22
22
|
|
|
23
|
-
LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/)
|
|
23
|
+
LightPHE is a lightweight partially homomorphic encryption library for python. It is a hybrid homomoprhic encryption library wrapping many schemes such as [`RSA`](https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/), [`ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Exponential ElGamal`](https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/), [`Elliptic Curve ElGamal`](https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/) ([`Weierstrass`](https://sefiks.com/2016/03/13/the-math-behind-elliptic-curve-cryptography/) and [`Edwards`](https://sefiks.com/2018/12/19/a-gentle-introduction-to-edwards-curves/) forms) [`Paillier`](https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/), [`Damgard-Jurik`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/), [`Okamoto–Uchiyama`](https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/), [`Benaloh`](https://sefiks.com/2023/10/06/a-step-by-step-partially-homomorphic-encryption-example-with-benaloh-in-python-from-scratch/), [`Naccache–Stern`](https://sefiks.com/2023/10/26/a-step-by-step-partially-homomorphic-encryption-example-with-naccache-stern-in-python/), [`Goldwasser–Micali`](https://sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/).
|
|
24
24
|
|
|
25
25
|
# Partially vs Fully Homomorphic Encryption
|
|
26
26
|
|
|
@@ -82,7 +82,7 @@ algorithms = [
|
|
|
82
82
|
"EllipticCurve-ElGamal"
|
|
83
83
|
]
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
phe = LightPHE(algorithm_name = algorithms[0])
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
# Encryption & Decryption
|
|
@@ -94,10 +94,10 @@ Once you built your cryptosystem, you will be able to encrypt and decrypt messag
|
|
|
94
94
|
m = 17
|
|
95
95
|
|
|
96
96
|
# calculate ciphertext
|
|
97
|
-
c =
|
|
97
|
+
c = phe.encrypt(m)
|
|
98
98
|
|
|
99
99
|
# proof of work
|
|
100
|
-
assert
|
|
100
|
+
assert phe.decrypt(c) == m
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
# Homomorphic Operations
|
|
@@ -106,76 +106,90 @@ Once you have the ciphertext, you will be able to perform homomorphic operations
|
|
|
106
106
|
|
|
107
107
|
### On-Prem Encryption
|
|
108
108
|
|
|
109
|
-
This
|
|
109
|
+
This code snippet illustrates how to generate a random public-private key pair using the Paillier and encrypt a plaintext pair. The resulting ciphertext pair, c1 and c2, along with the public key, is then sent from the on-premises environment to the cloud.
|
|
110
110
|
|
|
111
111
|
```python
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
112
|
+
def on_premise() -> Tuple[int, int, dict]:
|
|
113
|
+
"""
|
|
114
|
+
Executes on-premise operations: initializes a cryptosystem by generating
|
|
115
|
+
a random public-private key pair, then encrypts two plaintext values.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
result (tuple): A tuple containing:
|
|
119
|
+
- c1 (int): The first ciphertext
|
|
120
|
+
- c2 (int): The second ciphertext
|
|
121
|
+
- public_key (dict): The public key for the cryptosystem
|
|
122
|
+
"""
|
|
123
|
+
# generate a random private-public key pair
|
|
124
|
+
phe = LightPHE(algorithm_name = "Paillier")
|
|
125
|
+
|
|
126
|
+
# define plaintexts
|
|
127
|
+
m1 = 10000 # base salary in usd
|
|
128
|
+
m2 = 500 # wage increase in usd
|
|
129
|
+
|
|
130
|
+
# calculate ciphertexts
|
|
131
|
+
c1 = phe.encrypt(m1).value
|
|
132
|
+
c2 = phe.encrypt(m2).value
|
|
133
|
+
|
|
134
|
+
return (c1, c2, phe.cs.public_key)
|
|
130
135
|
```
|
|
131
136
|
|
|
132
137
|
### Homomorphic Operations on Cloud
|
|
133
138
|
|
|
134
|
-
This
|
|
139
|
+
This code snippet demonstrates how to perform homomorphic addition on the cloud side without using the private key. However, the cloud is unable to decrypt c3 itself, even though it is the one that calculated it.
|
|
135
140
|
|
|
136
141
|
```python
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
c1
|
|
144
|
-
c2
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
def cloud(c1: int, c2: int, public_key: dict) -> int:
|
|
143
|
+
"""
|
|
144
|
+
Performs cloud-side operations: reconstructs a cryptosystem using the
|
|
145
|
+
provided public key and executes a homomorphic addition on two ciphertexts.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
c1 (int): The first ciphertext
|
|
149
|
+
c2 (int): The second ciphertext
|
|
150
|
+
public_key (dict): The public key of an existing cryptosystem
|
|
151
|
+
Retunrs:
|
|
152
|
+
c3 (int): The resulting ciphertext after homomorphic addition
|
|
153
|
+
"""
|
|
154
|
+
# restore cryptosystem with just the public key
|
|
155
|
+
phe = LightPHE(algorithm_name = "Paillier", keys = public_key)
|
|
156
|
+
|
|
157
|
+
# cast c1 and c2 to ciphertext objects
|
|
158
|
+
c1 = phe.create_ciphertext_obj(c1)
|
|
159
|
+
c2 = phe.create_ciphertext_obj(c2)
|
|
160
|
+
|
|
161
|
+
# confirm that cloud cannot decrypt c1
|
|
162
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
163
|
+
phe.decrypt(c1)
|
|
164
|
+
|
|
165
|
+
# confirm that cloud cannot decrypt c2
|
|
166
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
167
|
+
phe.decrypt(c2)
|
|
168
|
+
|
|
169
|
+
# perform homomorphic addition
|
|
170
|
+
c3 = c1 + c2
|
|
171
|
+
|
|
172
|
+
# confirm that cloud cannot decrypt c3
|
|
173
|
+
with pytest.raises(ValueError, match="You must have private key"):
|
|
174
|
+
phe.decrypt(c3)
|
|
175
|
+
|
|
176
|
+
return c3.value
|
|
160
177
|
```
|
|
161
178
|
|
|
162
|
-
### On-Prem Decryption
|
|
179
|
+
### On-Prem Decryption And Proof of Work
|
|
163
180
|
|
|
164
|
-
This
|
|
181
|
+
This code snippet demonstrates the proof of work. Even though c3 was calculated in the cloud by adding c1 and c2, on-premises can validate that its decryption must be equal to the addition of plaintexts m1 and m2.
|
|
165
182
|
|
|
166
183
|
```python
|
|
167
|
-
# on-prem side receives c3 from cloud
|
|
168
|
-
|
|
169
|
-
# restore cryptosystem with the exported private key
|
|
170
|
-
cs = LightPHE(algorithm_name = "Paillier", key_file = "private.txt")
|
|
171
|
-
|
|
172
184
|
# proof of work - private key required
|
|
173
|
-
assert
|
|
185
|
+
assert phe.decrypt(c3) == m1 + m2
|
|
174
186
|
```
|
|
175
187
|
|
|
188
|
+
In this homomorphic pipeline, the cloud's computational power was utilized to calculate c3, but it can only be decrypted by the on-premises side. Additionally, while we performed the encryption on the on-premises side, this is not strictly necessary; only the public key is required for encryption. Therefore, encryption can also be performed on the non-premises side. This approach is particularly convenient when collecting data from multiple edge devices and storing all of it in the cloud simultaneously.
|
|
189
|
+
|
|
176
190
|
### Scalar Multiplication
|
|
177
191
|
|
|
178
|
-
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. This
|
|
192
|
+
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. This code snippet demonstrates how to perform scalar multiplication on encrypted data using Paillier homomorphic encryption with the LightPHE library.
|
|
179
193
|
|
|
180
194
|
```python
|
|
181
195
|
# increasing something 5%
|
|
@@ -185,7 +199,7 @@ k = 1.05
|
|
|
185
199
|
c4 = k * c1
|
|
186
200
|
|
|
187
201
|
# proof of work on-prem - private key is required
|
|
188
|
-
assert
|
|
202
|
+
assert phe.decrypt(c4) == k * m1
|
|
189
203
|
```
|
|
190
204
|
|
|
191
205
|
### Ciphertext Regeneration
|
|
@@ -193,10 +207,10 @@ assert cs.decrypt(c4) == k * m1
|
|
|
193
207
|
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.
|
|
194
208
|
|
|
195
209
|
```python
|
|
196
|
-
c1_prime =
|
|
210
|
+
c1_prime = phe.regenerate_ciphertext(c1)
|
|
197
211
|
assert c1_prime.value != c1.value
|
|
198
|
-
assert
|
|
199
|
-
assert
|
|
212
|
+
assert phe.decrypt(c1_prime) == m1
|
|
213
|
+
assert phe.decrypt(c1) == m1
|
|
200
214
|
```
|
|
201
215
|
|
|
202
216
|
### Unsupported Operations
|
|
@@ -215,51 +229,6 @@ with pytest.raises(ValueError, match="Paillier is not homomorphic with respect t
|
|
|
215
229
|
|
|
216
230
|
However, if you tried to multiply ciphertexts with RSA, or xor ciphertexts with Goldwasser-Micali, these will be succeeded because those cryptosystems support those homomorphic operations.
|
|
217
231
|
|
|
218
|
-
# Working with vectors
|
|
219
|
-
|
|
220
|
-
You can encrypt the output vectors of machine learning models with LightPHE. These encrypted tensors come with homomorphic operation support including homomorphic addition, element-wise multiplication and scalar multiplication.
|
|
221
|
-
|
|
222
|
-
```python
|
|
223
|
-
# build an additively homomorphic cryptosystem
|
|
224
|
-
cs = LightPHE(algorithm_name="Paillier")
|
|
225
|
-
|
|
226
|
-
# define plain tensors
|
|
227
|
-
t1 = [1.005, 2.05, 3.5, 4]
|
|
228
|
-
t2 = [5, 6.2, 7.002, 8.02]
|
|
229
|
-
|
|
230
|
-
# encrypt tensors
|
|
231
|
-
c1 = cs.encrypt(t1)
|
|
232
|
-
c2 = cs.encrypt(t2)
|
|
233
|
-
|
|
234
|
-
# perform homomorphic addition
|
|
235
|
-
c3 = c1 + c2
|
|
236
|
-
|
|
237
|
-
# perform homomorphic element-wise multiplication
|
|
238
|
-
c4 = c1 * c2
|
|
239
|
-
|
|
240
|
-
# perform homomorphic scalar multiplication
|
|
241
|
-
k = 5
|
|
242
|
-
c5 = k * c1
|
|
243
|
-
|
|
244
|
-
# decrypt the addition tensor
|
|
245
|
-
t3 = cs.decrypt(c3)
|
|
246
|
-
|
|
247
|
-
# decrypt the element-wise multiplied tensor
|
|
248
|
-
t4 = cs.decrypt(c4)
|
|
249
|
-
|
|
250
|
-
# decrypt the scalar multiplied tensor
|
|
251
|
-
t5 = cs.decrypt(c5)
|
|
252
|
-
|
|
253
|
-
# data validations
|
|
254
|
-
threshold = 0.5
|
|
255
|
-
for i in range(0, len(t1)):
|
|
256
|
-
assert abs((t1[i] + t2[i]) - t3[i]) < threshold
|
|
257
|
-
assert abs((t1[i] * t2[i]) - t4[i]) < threshold
|
|
258
|
-
assert abs((t1[i] * k) - t5[i]) < threshold
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
Unfortunately, vector multiplication (dot product) requires both homomorphic addition and homomorphic multiplication and this cannot be done with partially homomorphic encryption algorithms.
|
|
262
|
-
|
|
263
232
|
# Contributing
|
|
264
233
|
|
|
265
234
|
All PRs are more than welcome! If you are planning to contribute a large patch, please create an issue first to get any upfront questions or design decisions out of the way first.
|
|
@@ -23,6 +23,7 @@ logger = Logger(module="lightphe/__init__.py")
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class LightPHE:
|
|
26
|
+
__version__ = "0.0.9"
|
|
26
27
|
def __init__(
|
|
27
28
|
self,
|
|
28
29
|
algorithm_name: str,
|
|
@@ -30,6 +31,7 @@ class LightPHE:
|
|
|
30
31
|
key_file: Optional[str] = None,
|
|
31
32
|
key_size: Optional[int] = None,
|
|
32
33
|
precision: int = 5,
|
|
34
|
+
form: str = "weierstrass",
|
|
33
35
|
):
|
|
34
36
|
"""
|
|
35
37
|
Build LightPHE class
|
|
@@ -41,15 +43,19 @@ class LightPHE:
|
|
|
41
43
|
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
42
44
|
key_size (int): key size in bits
|
|
43
45
|
precision (int): precision for homomorphic operations on tensors
|
|
46
|
+
form (str): specifies the form of the elliptic curve.
|
|
47
|
+
Options: 'weierstrass' (default), 'edwards'.
|
|
48
|
+
This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
|
|
44
49
|
"""
|
|
45
50
|
self.algorithm_name = algorithm_name
|
|
46
51
|
self.precision = precision
|
|
52
|
+
self.form = form
|
|
47
53
|
|
|
48
54
|
if key_file is not None:
|
|
49
55
|
keys = self.restore_keys(target_file=key_file)
|
|
50
56
|
|
|
51
57
|
self.cs: Homomorphic = self.__build_cryptosystem(
|
|
52
|
-
algorithm_name=algorithm_name, keys=keys, key_size=key_size
|
|
58
|
+
algorithm_name=algorithm_name, keys=keys, key_size=key_size, form=form,
|
|
53
59
|
)
|
|
54
60
|
|
|
55
61
|
def __build_cryptosystem(
|
|
@@ -57,6 +63,7 @@ class LightPHE:
|
|
|
57
63
|
algorithm_name: str,
|
|
58
64
|
keys: Optional[dict] = None,
|
|
59
65
|
key_size: Optional[int] = None,
|
|
66
|
+
form: Optional[str] = None,
|
|
60
67
|
) -> Union[
|
|
61
68
|
RSA,
|
|
62
69
|
ElGamal,
|
|
@@ -72,17 +79,16 @@ class LightPHE:
|
|
|
72
79
|
Args:
|
|
73
80
|
algorithm_name (str): RSA | ElGamal | Exponential-ElGamal | EllipticCurve-ElGamal
|
|
74
81
|
| Paillier | Damgard-Jurik | Okamoto-Uchiyama | Benaloh | Naccache-Stern
|
|
75
|
-
| Goldwasser-Micali
|
|
82
|
+
| Goldwasser-Micali | Edwards-ElGamal
|
|
76
83
|
keys (dict): optional private-public key pair
|
|
77
84
|
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
78
85
|
key_size (int): key size in bits
|
|
86
|
+
form (str): specifies the form of the elliptic curve.
|
|
87
|
+
Options: 'weierstrass' (default), 'edwards'.
|
|
88
|
+
This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
|
|
79
89
|
Returns
|
|
80
90
|
cryptosystem
|
|
81
91
|
"""
|
|
82
|
-
|
|
83
|
-
if key_size is None:
|
|
84
|
-
key_size = self.recommend_key_size(algorithm_name=algorithm_name)
|
|
85
|
-
|
|
86
92
|
# build cryptosystem
|
|
87
93
|
if algorithm_name == Algorithm.RSA:
|
|
88
94
|
cs = RSA(keys=keys, key_size=key_size)
|
|
@@ -91,7 +97,7 @@ class LightPHE:
|
|
|
91
97
|
elif algorithm_name == Algorithm.ExponentialElGamal:
|
|
92
98
|
cs = ElGamal(keys=keys, key_size=key_size, exponential=True)
|
|
93
99
|
elif algorithm_name == Algorithm.EllipticCurveElGamal:
|
|
94
|
-
cs = EllipticCurveElGamal(keys=keys, key_size=key_size)
|
|
100
|
+
cs = EllipticCurveElGamal(keys=keys, key_size=key_size, form=form)
|
|
95
101
|
elif algorithm_name == Algorithm.Paillier:
|
|
96
102
|
cs = Paillier(keys=keys, key_size=key_size)
|
|
97
103
|
elif algorithm_name == Algorithm.DamgardJurik:
|
|
@@ -126,7 +132,7 @@ class LightPHE:
|
|
|
126
132
|
ciphertext = self.cs.encrypt(
|
|
127
133
|
plaintext=phe_utils.parse_int(value=plaintext, modulo=self.cs.plaintext_modulo)
|
|
128
134
|
)
|
|
129
|
-
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext)
|
|
135
|
+
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext, form=self.form)
|
|
130
136
|
|
|
131
137
|
def decrypt(
|
|
132
138
|
self, ciphertext: Union[Ciphertext, EncryptedTensor]
|
|
@@ -289,42 +295,6 @@ class LightPHE:
|
|
|
289
295
|
raise ValueError(f"File {target_file} must have public_key key")
|
|
290
296
|
return keys
|
|
291
297
|
|
|
292
|
-
def recommend_key_size(self, algorithm_name: str) -> int:
|
|
293
|
-
"""
|
|
294
|
-
Recommend a key size in bits if it is not mentioned by the user
|
|
295
|
-
Args:
|
|
296
|
-
algorithm_name (str): algorithm name
|
|
297
|
-
Returns
|
|
298
|
-
key_size (int)
|
|
299
|
-
"""
|
|
300
|
-
if algorithm_name == Algorithm.RSA:
|
|
301
|
-
key_size = 1024
|
|
302
|
-
elif algorithm_name == Algorithm.ElGamal:
|
|
303
|
-
key_size = 1024
|
|
304
|
-
elif algorithm_name == Algorithm.ExponentialElGamal:
|
|
305
|
-
key_size = 1024
|
|
306
|
-
# 160-bit ECC is equivalent to 1024-bit RSA
|
|
307
|
-
elif algorithm_name == Algorithm.EllipticCurveElGamal:
|
|
308
|
-
key_size = 160
|
|
309
|
-
elif algorithm_name == Algorithm.Paillier:
|
|
310
|
-
key_size = 1024
|
|
311
|
-
elif algorithm_name == Algorithm.DamgardJurik:
|
|
312
|
-
key_size = 1024
|
|
313
|
-
elif algorithm_name == Algorithm.OkamotoUchiyama:
|
|
314
|
-
key_size = 1024
|
|
315
|
-
# n should be several hundred bits or more
|
|
316
|
-
elif algorithm_name == Algorithm.GoldwasserMicali:
|
|
317
|
-
key_size = 100
|
|
318
|
-
# Benaloh and Naccache-Stern require to solve DLP in decryption
|
|
319
|
-
# so small key is recommended
|
|
320
|
-
elif algorithm_name == Algorithm.Benaloh:
|
|
321
|
-
key_size = 50
|
|
322
|
-
elif algorithm_name == Algorithm.NaccacheStern:
|
|
323
|
-
key_size = 37
|
|
324
|
-
else:
|
|
325
|
-
raise ValueError(f"unimplemented algorithm - {algorithm_name}")
|
|
326
|
-
return key_size
|
|
327
|
-
|
|
328
298
|
def create_ciphertext_obj(self, ciphertext: Union[int, tuple, list]) -> Ciphertext:
|
|
329
299
|
"""
|
|
330
300
|
Ciphertext objects have keys in addition ciphertext itself to perform
|