lightphe 0.0.16__tar.gz → 0.0.18__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.16 → lightphe-0.0.18}/PKG-INFO +41 -214
- {lightphe-0.0.16 → lightphe-0.0.18}/README.md +40 -213
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/__init__.py +9 -8
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/Benaloh.py +51 -23
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/GoldwasserMicali.py +9 -2
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/NaccacheStern.py +1 -1
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/models/Homomorphic.py +5 -1
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe.egg-info/PKG-INFO +41 -214
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe.egg-info/SOURCES.txt +1 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/setup.py +1 -1
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_benaloh.py +10 -3
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_keys.py +1 -1
- lightphe-0.0.18/tests/test_large_keys.py +76 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/LICENSE +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/DamgardJurik.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/ElGamal.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/EllipticCurveElGamal.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/OkamotoUchiyama.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/Paillier.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/RSA.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/requirements.txt +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/setup.cfg +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/__init__.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_cloud.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_damgard.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_naccache.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_paillier.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_rsa.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/tests/test_salary.py +0 -0
- {lightphe-0.0.16 → lightphe-0.0.18}/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.18
|
|
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
|
|
@@ -67,7 +67,7 @@ from lightphe import LightPHE
|
|
|
67
67
|
|
|
68
68
|
In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.
|
|
69
69
|
|
|
70
|
-
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Multiplication
|
|
70
|
+
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Exclusively<br>Homomorphic | Regeneration<br>of Ciphertext |
|
|
71
71
|
| --- | --- | --- | --- | --- | --- |
|
|
72
72
|
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
73
73
|
| ElGamal | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
@@ -98,124 +98,56 @@ algorithms = [
|
|
|
98
98
|
"EllipticCurve-ElGamal"
|
|
99
99
|
]
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
# Encryption & Decryption
|
|
105
|
-
|
|
106
|
-
Once you built your cryptosystem, you will be able to encrypt and decrypt messages with the built cryptosystem.
|
|
107
|
-
|
|
108
|
-
```python
|
|
109
|
-
# define plaintext
|
|
110
|
-
m = 17
|
|
111
|
-
|
|
112
|
-
# calculate ciphertext
|
|
113
|
-
c = phe.encrypt(m)
|
|
114
|
-
|
|
115
|
-
# proof of work
|
|
116
|
-
assert phe.decrypt(c) == m
|
|
101
|
+
cs = LightPHE(algorithm_name = algorithms[0])
|
|
117
102
|
```
|
|
118
103
|
|
|
119
104
|
# Homomorphic Operations
|
|
120
105
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
### On-Prem Encryption
|
|
124
|
-
|
|
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.
|
|
106
|
+
The following example demonstrates a simple workflow using LightPHE with an additively homomorphic cryptosystem (e.g. Paillier). First, we build the cryptosystem on-premises and define two plaintext values. Next, we encrypt the plaintexts, which can be done either on-premises or in the cloud, and does not require the private key. Homomorphic operations, such as addition and scalar multiplication, can then be performed on the ciphertexts—these operations can be offloaded to the cloud, leveraging its computational power without revealing the private key or the plaintext. Finally, the results are decrypted on-premises using the private key, verifying that the homomorphic operations on encrypted data produce the expected plaintext results.
|
|
126
107
|
|
|
127
108
|
```python
|
|
128
|
-
|
|
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)
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Homomorphic Operations on Cloud
|
|
109
|
+
# build an additively homomorphic cryptosystem
|
|
110
|
+
cs = LightPHE(algorithm_name = "Paillier")
|
|
154
111
|
|
|
155
|
-
|
|
112
|
+
# define plaintexts
|
|
113
|
+
m1 = 10000 # base salary in usd
|
|
114
|
+
m2 = 500 # wage increase in usd
|
|
156
115
|
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
193
|
-
```
|
|
116
|
+
# encrypt plaintexts - private key is not required.
|
|
117
|
+
c1 = cs.encrypt(m1)
|
|
118
|
+
c2 = cs.encrypt(m2)
|
|
194
119
|
|
|
195
|
-
|
|
120
|
+
# homomorphic addition - private key is not required
|
|
121
|
+
c3 = c1 + c2
|
|
196
122
|
|
|
197
|
-
|
|
123
|
+
# homomorphic scalar multiplication - private key is not required
|
|
124
|
+
k = 1.05 # increase something 5%
|
|
125
|
+
c4 = k * c1
|
|
198
126
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
assert
|
|
127
|
+
# decryption - private key is required
|
|
128
|
+
assert cs.decrypt(c3) == m1 + m2
|
|
129
|
+
assert cs.decrypt(c4) == k * m1
|
|
202
130
|
```
|
|
203
131
|
|
|
204
|
-
|
|
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.
|
|
205
133
|
|
|
206
|
-
|
|
134
|
+
```python
|
|
135
|
+
# build a multiplicatively homomorphic cryptosystem
|
|
136
|
+
cs = LightPHE(algorithm_name = "RSA")
|
|
207
137
|
|
|
208
|
-
|
|
138
|
+
# define plaintexts
|
|
139
|
+
m1 = 17
|
|
140
|
+
m2 = 21
|
|
209
141
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
142
|
+
# encrypt plaintexts - private key is not required.
|
|
143
|
+
c1 = cs.encrypt(m1)
|
|
144
|
+
c2 = cs.encrypt(m2)
|
|
213
145
|
|
|
214
|
-
#
|
|
215
|
-
|
|
146
|
+
# homomorphic multiplication
|
|
147
|
+
c3 = c1 * c2
|
|
216
148
|
|
|
217
|
-
#
|
|
218
|
-
assert
|
|
149
|
+
# decryption - private key is required
|
|
150
|
+
assert cs.decrypt(c3) == m1 * m2
|
|
219
151
|
```
|
|
220
152
|
|
|
221
153
|
### Ciphertext Regeneration
|
|
@@ -223,28 +155,12 @@ assert phe.decrypt(c4) == k * m1
|
|
|
223
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.
|
|
224
156
|
|
|
225
157
|
```python
|
|
226
|
-
c1_prime =
|
|
158
|
+
c1_prime = cs.regenerate_ciphertext(c1)
|
|
227
159
|
assert c1_prime.value != c1.value
|
|
228
|
-
assert
|
|
229
|
-
assert
|
|
160
|
+
assert cs.decrypt(c1_prime) == m1
|
|
161
|
+
assert cs.decrypt(c1) == m1
|
|
230
162
|
```
|
|
231
163
|
|
|
232
|
-
### Unsupported Operations
|
|
233
|
-
|
|
234
|
-
Finally, if you try to perform an operation that algorithm does not support, then an exception will be thrown. For instance, Paillier is not homomorphic with respect to the multiplication or xor. To put it simply, you cannot multiply two ciphertexts. If you enforce this calculation, you will have an exception.
|
|
235
|
-
|
|
236
|
-
```python
|
|
237
|
-
# pailier is not multiplicatively homomorphic
|
|
238
|
-
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the multiplication"):
|
|
239
|
-
c1 * c2
|
|
240
|
-
|
|
241
|
-
# pailier is not exclusively homomorphic
|
|
242
|
-
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the exclusive or"):
|
|
243
|
-
c1 ^ c2
|
|
244
|
-
```
|
|
245
|
-
|
|
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.
|
|
247
|
-
|
|
248
164
|
### Elliptic Curve Cryptography
|
|
249
165
|
|
|
250
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.
|
|
@@ -252,9 +168,10 @@ ECC is a powerful public-key cryptosystem based on the algebraic structure of el
|
|
|
252
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.
|
|
253
169
|
|
|
254
170
|
```python
|
|
171
|
+
forms = ["weierstrass", "edwards", "koblitz"]
|
|
255
172
|
phe = LightPHE(
|
|
256
173
|
algorithm_name="EllipticCurve-ElGamal",
|
|
257
|
-
form=
|
|
174
|
+
form=forms[1],
|
|
258
175
|
)
|
|
259
176
|
```
|
|
260
177
|
|
|
@@ -264,48 +181,9 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
|
|
|
264
181
|
|
|
265
182
|
See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a list of all supported forms, curves and their details.
|
|
266
183
|
|
|
267
|
-
### Vector Embeddings
|
|
184
|
+
### Vector Embeddings
|
|
268
185
|
|
|
269
|
-
LightPHE supports homomorphic encryption on vector embeddings
|
|
270
|
-
|
|
271
|
-
```python
|
|
272
|
-
# build a cryptosystem
|
|
273
|
-
cs = LightPHE(algorithm_name="Paillier")
|
|
274
|
-
|
|
275
|
-
# define plain embedding
|
|
276
|
-
tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
|
|
277
|
-
|
|
278
|
-
# encrypt vector embedding
|
|
279
|
-
encrypted_tensors = cs.encrypt(tensor)
|
|
280
|
-
|
|
281
|
-
# restore embedding
|
|
282
|
-
decrypted_tensors = cs.decrypt(encrypted_tensors)
|
|
283
|
-
|
|
284
|
-
# proof of work
|
|
285
|
-
assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
|
|
289
|
-
|
|
290
|
-
```python
|
|
291
|
-
# build a multiplicatively homomorphic cryptosystem (e.g. RSA)
|
|
292
|
-
cs = LightPHE("RSA")
|
|
293
|
-
|
|
294
|
-
# define plain embeddings
|
|
295
|
-
t1 = [1.005, 2.05, -3.5, 3.1, -4]
|
|
296
|
-
t2 = [5, 6.2, -7.002, -7.1, 8.02]
|
|
297
|
-
|
|
298
|
-
# encrypt embeddings
|
|
299
|
-
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
300
|
-
|
|
301
|
-
# perform element-wise homomorphic multiplication
|
|
302
|
-
c3 = c1 * c2
|
|
303
|
-
|
|
304
|
-
# proof of work
|
|
305
|
-
assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
306
|
-
```
|
|
307
|
-
|
|
308
|
-
Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
|
|
186
|
+
LightPHE supports homomorphic encryption on vector embeddings. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|
|
309
187
|
|
|
310
188
|
```python
|
|
311
189
|
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
@@ -334,57 +212,6 @@ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
|
|
|
334
212
|
assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
|
|
335
213
|
```
|
|
336
214
|
|
|
337
|
-
### Vector Similarity Search with PHE
|
|
338
|
-
|
|
339
|
-
Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
|
|
340
|
-
|
|
341
|
-
For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
|
|
342
|
-
|
|
343
|
-
```python
|
|
344
|
-
# define a plain vectors for source and target
|
|
345
|
-
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
346
|
-
beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
|
|
347
|
-
expected_similarity = sum(x * y for x, y in zip(alpha, beta))
|
|
348
|
-
|
|
349
|
-
# build an additively homomorphic cryptosystem (e.g. Paillier) on-prem
|
|
350
|
-
cs = LightPHE(algorithm_name = "Paillier", precision = 19)
|
|
351
|
-
|
|
352
|
-
# export keys
|
|
353
|
-
cs.export_keys("secret.txt")
|
|
354
|
-
cs.export_keys("public.txt", public=True)
|
|
355
|
-
|
|
356
|
-
# encrypt source embedding
|
|
357
|
-
encrypted_alpha = cs.encrypt(alpha)
|
|
358
|
-
|
|
359
|
-
# remove cryptosystem and plain alpha not to be leaked in cloud
|
|
360
|
-
del cs, alpha
|
|
361
|
-
|
|
362
|
-
# restore the cryptosystem in cloud with only public key
|
|
363
|
-
cloud_cs = LightPHE(algorithm_name = "Paillier", precision = 19, key_file = "public.txt")
|
|
364
|
-
|
|
365
|
-
# dot product of encrypted and plain embedding pair
|
|
366
|
-
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
367
|
-
|
|
368
|
-
# computed by the cloud but cloud cannot decrypt it
|
|
369
|
-
with pytest.raises(ValueError, match="must have private key"):
|
|
370
|
-
cloud_cs.decrypt(encrypted_cosine_similarity)
|
|
371
|
-
|
|
372
|
-
# restore the cryptosystem on-prem with secret key
|
|
373
|
-
cs = LightPHE(algorithm_name = "Paillier", precision = 19, key_file = "secret.txt")
|
|
374
|
-
|
|
375
|
-
# decrypt similarity (on prem)
|
|
376
|
-
calculated_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
|
|
377
|
-
|
|
378
|
-
# proof of work
|
|
379
|
-
assert abs(calculated_similarity - expected_similarity) < 1e-2
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
## Security Concerns and Considerations
|
|
383
|
-
|
|
384
|
-
While LightPHE enables encryption using public keys and supports homomorphic operations on encrypted data, it's important to understand a subtle but critical security implication of this approach. Since the it is homomorphic, the cloud (or any third-party processor) can perform computations without knowing the plaintext. However, a malicious actor can still encrypt arbitrary values and craft valid ciphertexts because encryption depends on your public key and it is publicly known. For example, even if a salary is encrypted, an attacker could encrypt 500 USD using your public key and update the encrypted salary with this forged ciphertext — without needing to know the original value.
|
|
385
|
-
|
|
386
|
-
To mitigate this, we recommend combining LightPHE with digital signature schemes. By keeping a signed audit trail of update operations in a separate log table, you can detect and reject unauthorized changes, and even restore the original data in case of tampering. We suggest checking out [`LightDSA`](https://github.com/serengil/LightDSA), which provides a lightweight cryptographic interface similar to LightPHE and supports popular digital signature algorithms like RSA, DSA, ECDSA, and EdDSA.
|
|
387
|
-
|
|
388
215
|
# Contributing
|
|
389
216
|
|
|
390
217
|
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.
|
|
@@ -51,7 +51,7 @@ from lightphe import LightPHE
|
|
|
51
51
|
|
|
52
52
|
In summary, LightPHE is covering following algorithms and these are partially homomorphic with respect to the operations mentioned in the following table.
|
|
53
53
|
|
|
54
|
-
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Multiplication
|
|
54
|
+
| Algorithm | Multiplicatively<br>Homomorphic | Additively<br>Homomorphic | Scalar Multiplication | Exclusively<br>Homomorphic | Regeneration<br>of Ciphertext |
|
|
55
55
|
| --- | --- | --- | --- | --- | --- |
|
|
56
56
|
| RSA | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
57
57
|
| ElGamal | ✅ | ❌ | ❌ | ❌ | ✅ |
|
|
@@ -82,124 +82,56 @@ algorithms = [
|
|
|
82
82
|
"EllipticCurve-ElGamal"
|
|
83
83
|
]
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
# Encryption & Decryption
|
|
89
|
-
|
|
90
|
-
Once you built your cryptosystem, you will be able to encrypt and decrypt messages with the built cryptosystem.
|
|
91
|
-
|
|
92
|
-
```python
|
|
93
|
-
# define plaintext
|
|
94
|
-
m = 17
|
|
95
|
-
|
|
96
|
-
# calculate ciphertext
|
|
97
|
-
c = phe.encrypt(m)
|
|
98
|
-
|
|
99
|
-
# proof of work
|
|
100
|
-
assert phe.decrypt(c) == m
|
|
85
|
+
cs = LightPHE(algorithm_name = algorithms[0])
|
|
101
86
|
```
|
|
102
87
|
|
|
103
88
|
# Homomorphic Operations
|
|
104
89
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### On-Prem Encryption
|
|
108
|
-
|
|
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.
|
|
90
|
+
The following example demonstrates a simple workflow using LightPHE with an additively homomorphic cryptosystem (e.g. Paillier). First, we build the cryptosystem on-premises and define two plaintext values. Next, we encrypt the plaintexts, which can be done either on-premises or in the cloud, and does not require the private key. Homomorphic operations, such as addition and scalar multiplication, can then be performed on the ciphertexts—these operations can be offloaded to the cloud, leveraging its computational power without revealing the private key or the plaintext. Finally, the results are decrypted on-premises using the private key, verifying that the homomorphic operations on encrypted data produce the expected plaintext results.
|
|
110
91
|
|
|
111
92
|
```python
|
|
112
|
-
|
|
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)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Homomorphic Operations on Cloud
|
|
93
|
+
# build an additively homomorphic cryptosystem
|
|
94
|
+
cs = LightPHE(algorithm_name = "Paillier")
|
|
138
95
|
|
|
139
|
-
|
|
96
|
+
# define plaintexts
|
|
97
|
+
m1 = 10000 # base salary in usd
|
|
98
|
+
m2 = 500 # wage increase in usd
|
|
140
99
|
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
177
|
-
```
|
|
100
|
+
# encrypt plaintexts - private key is not required.
|
|
101
|
+
c1 = cs.encrypt(m1)
|
|
102
|
+
c2 = cs.encrypt(m2)
|
|
178
103
|
|
|
179
|
-
|
|
104
|
+
# homomorphic addition - private key is not required
|
|
105
|
+
c3 = c1 + c2
|
|
180
106
|
|
|
181
|
-
|
|
107
|
+
# homomorphic scalar multiplication - private key is not required
|
|
108
|
+
k = 1.05 # increase something 5%
|
|
109
|
+
c4 = k * c1
|
|
182
110
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
assert
|
|
111
|
+
# decryption - private key is required
|
|
112
|
+
assert cs.decrypt(c3) == m1 + m2
|
|
113
|
+
assert cs.decrypt(c4) == k * m1
|
|
186
114
|
```
|
|
187
115
|
|
|
188
|
-
|
|
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.
|
|
189
117
|
|
|
190
|
-
|
|
118
|
+
```python
|
|
119
|
+
# build a multiplicatively homomorphic cryptosystem
|
|
120
|
+
cs = LightPHE(algorithm_name = "RSA")
|
|
191
121
|
|
|
192
|
-
|
|
122
|
+
# define plaintexts
|
|
123
|
+
m1 = 17
|
|
124
|
+
m2 = 21
|
|
193
125
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
126
|
+
# encrypt plaintexts - private key is not required.
|
|
127
|
+
c1 = cs.encrypt(m1)
|
|
128
|
+
c2 = cs.encrypt(m2)
|
|
197
129
|
|
|
198
|
-
#
|
|
199
|
-
|
|
130
|
+
# homomorphic multiplication
|
|
131
|
+
c3 = c1 * c2
|
|
200
132
|
|
|
201
|
-
#
|
|
202
|
-
assert
|
|
133
|
+
# decryption - private key is required
|
|
134
|
+
assert cs.decrypt(c3) == m1 * m2
|
|
203
135
|
```
|
|
204
136
|
|
|
205
137
|
### Ciphertext Regeneration
|
|
@@ -207,28 +139,12 @@ assert phe.decrypt(c4) == k * m1
|
|
|
207
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.
|
|
208
140
|
|
|
209
141
|
```python
|
|
210
|
-
c1_prime =
|
|
142
|
+
c1_prime = cs.regenerate_ciphertext(c1)
|
|
211
143
|
assert c1_prime.value != c1.value
|
|
212
|
-
assert
|
|
213
|
-
assert
|
|
144
|
+
assert cs.decrypt(c1_prime) == m1
|
|
145
|
+
assert cs.decrypt(c1) == m1
|
|
214
146
|
```
|
|
215
147
|
|
|
216
|
-
### Unsupported Operations
|
|
217
|
-
|
|
218
|
-
Finally, if you try to perform an operation that algorithm does not support, then an exception will be thrown. For instance, Paillier is not homomorphic with respect to the multiplication or xor. To put it simply, you cannot multiply two ciphertexts. If you enforce this calculation, you will have an exception.
|
|
219
|
-
|
|
220
|
-
```python
|
|
221
|
-
# pailier is not multiplicatively homomorphic
|
|
222
|
-
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the multiplication"):
|
|
223
|
-
c1 * c2
|
|
224
|
-
|
|
225
|
-
# pailier is not exclusively homomorphic
|
|
226
|
-
with pytest.raises(ValueError, match="Paillier is not homomorphic with respect to the exclusive or"):
|
|
227
|
-
c1 ^ c2
|
|
228
|
-
```
|
|
229
|
-
|
|
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.
|
|
231
|
-
|
|
232
148
|
### Elliptic Curve Cryptography
|
|
233
149
|
|
|
234
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.
|
|
@@ -236,9 +152,10 @@ ECC is a powerful public-key cryptosystem based on the algebraic structure of el
|
|
|
236
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.
|
|
237
153
|
|
|
238
154
|
```python
|
|
155
|
+
forms = ["weierstrass", "edwards", "koblitz"]
|
|
239
156
|
phe = LightPHE(
|
|
240
157
|
algorithm_name="EllipticCurve-ElGamal",
|
|
241
|
-
form=
|
|
158
|
+
form=forms[1],
|
|
242
159
|
)
|
|
243
160
|
```
|
|
244
161
|
|
|
@@ -248,48 +165,9 @@ Each curve in LightPHE has a specific order, which is carefully chosen to balanc
|
|
|
248
165
|
|
|
249
166
|
See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a list of all supported forms, curves and their details.
|
|
250
167
|
|
|
251
|
-
### Vector Embeddings
|
|
168
|
+
### Vector Embeddings
|
|
252
169
|
|
|
253
|
-
LightPHE supports homomorphic encryption on vector embeddings
|
|
254
|
-
|
|
255
|
-
```python
|
|
256
|
-
# build a cryptosystem
|
|
257
|
-
cs = LightPHE(algorithm_name="Paillier")
|
|
258
|
-
|
|
259
|
-
# define plain embedding
|
|
260
|
-
tensor = [1.005, 2.05, 3.005, 4.005, -5.05, 6, 7.003, 7.002]
|
|
261
|
-
|
|
262
|
-
# encrypt vector embedding
|
|
263
|
-
encrypted_tensors = cs.encrypt(tensor)
|
|
264
|
-
|
|
265
|
-
# restore embedding
|
|
266
|
-
decrypted_tensors = cs.decrypt(encrypted_tensors)
|
|
267
|
-
|
|
268
|
-
# proof of work
|
|
269
|
-
assert all(abs(original - decrypted) < 1e-2 for original, decrypted in zip(tensor, decrypted_tensors))
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
Encrypted embeddings retain their homomorphic properties, enabling secure computations on encrypted data without decryption. For example, two embeddings encrypted using a multiplicatively homomorphic algorithm can be multiplied element-wise.
|
|
273
|
-
|
|
274
|
-
```python
|
|
275
|
-
# build a multiplicatively homomorphic cryptosystem (e.g. RSA)
|
|
276
|
-
cs = LightPHE("RSA")
|
|
277
|
-
|
|
278
|
-
# define plain embeddings
|
|
279
|
-
t1 = [1.005, 2.05, -3.5, 3.1, -4]
|
|
280
|
-
t2 = [5, 6.2, -7.002, -7.1, 8.02]
|
|
281
|
-
|
|
282
|
-
# encrypt embeddings
|
|
283
|
-
c1, c2 = cs.encrypt(t1), cs.encrypt(t2)
|
|
284
|
-
|
|
285
|
-
# perform element-wise homomorphic multiplication
|
|
286
|
-
c3 = c1 * c2
|
|
287
|
-
|
|
288
|
-
# proof of work
|
|
289
|
-
assert np.allclose(cs.decrypt(c3), [a * b for a, b in zip(t1, t2)], rtol=1e-2)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
Similarly, two embeddings encrypted with an additively homomorphic algorithm can be added. Additionally, an encrypted embedding can be multiplied by a constant or a plain embedding.
|
|
170
|
+
LightPHE supports homomorphic encryption on vector embeddings. This is useful in privacy-preserving machine learning, secure aggregation, and confidential data processing.
|
|
293
171
|
|
|
294
172
|
```python
|
|
295
173
|
# build an additively homomorphic cryptosystem (e.g. Paillier)
|
|
@@ -318,57 +196,6 @@ assert np.allclose(cs.decrypt(c5), [a * 3 for a in t1], rtol=1e-2)
|
|
|
318
196
|
assert np.allclose(cs.decrypt(c6), [a * b for a, b in zip(t1, t3)], rtol=1e-2)
|
|
319
197
|
```
|
|
320
198
|
|
|
321
|
-
### Vector Similarity Search with PHE
|
|
322
|
-
|
|
323
|
-
Many machine learning models rely on a two-tower architecture, including facial recognition, reverse image search, recommendation engines, large language models, and more. In this setup, user and item inputs are separately mapped to vector embeddings.
|
|
324
|
-
|
|
325
|
-
For example, suppose all facial embeddings in your database are encrypted on-prem in advance. When verifying an identity, the attempted facial embedding is generated on an edge device or in the cloud. You can compute the encrypted similarity by performing a dot product between the encrypted vector and the plain vector, ensuring secure comparison without decrypting sensitive data. Only additively homomorphic cryptosystems offer encrypted similarity calculation.
|
|
326
|
-
|
|
327
|
-
```python
|
|
328
|
-
# define a plain vectors for source and target
|
|
329
|
-
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
330
|
-
beta = [5.6, 3.7, 2.8, 4, 0, 5.9] # Cloud vector (item tower)
|
|
331
|
-
expected_similarity = sum(x * y for x, y in zip(alpha, beta))
|
|
332
|
-
|
|
333
|
-
# build an additively homomorphic cryptosystem (e.g. Paillier) on-prem
|
|
334
|
-
cs = LightPHE(algorithm_name = "Paillier", precision = 19)
|
|
335
|
-
|
|
336
|
-
# export keys
|
|
337
|
-
cs.export_keys("secret.txt")
|
|
338
|
-
cs.export_keys("public.txt", public=True)
|
|
339
|
-
|
|
340
|
-
# encrypt source embedding
|
|
341
|
-
encrypted_alpha = cs.encrypt(alpha)
|
|
342
|
-
|
|
343
|
-
# remove cryptosystem and plain alpha not to be leaked in cloud
|
|
344
|
-
del cs, alpha
|
|
345
|
-
|
|
346
|
-
# restore the cryptosystem in cloud with only public key
|
|
347
|
-
cloud_cs = LightPHE(algorithm_name = "Paillier", precision = 19, key_file = "public.txt")
|
|
348
|
-
|
|
349
|
-
# dot product of encrypted and plain embedding pair
|
|
350
|
-
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
351
|
-
|
|
352
|
-
# computed by the cloud but cloud cannot decrypt it
|
|
353
|
-
with pytest.raises(ValueError, match="must have private key"):
|
|
354
|
-
cloud_cs.decrypt(encrypted_cosine_similarity)
|
|
355
|
-
|
|
356
|
-
# restore the cryptosystem on-prem with secret key
|
|
357
|
-
cs = LightPHE(algorithm_name = "Paillier", precision = 19, key_file = "secret.txt")
|
|
358
|
-
|
|
359
|
-
# decrypt similarity (on prem)
|
|
360
|
-
calculated_similarity = cs.decrypt(encrypted_cosine_similarity)[0]
|
|
361
|
-
|
|
362
|
-
# proof of work
|
|
363
|
-
assert abs(calculated_similarity - expected_similarity) < 1e-2
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
## Security Concerns and Considerations
|
|
367
|
-
|
|
368
|
-
While LightPHE enables encryption using public keys and supports homomorphic operations on encrypted data, it's important to understand a subtle but critical security implication of this approach. Since the it is homomorphic, the cloud (or any third-party processor) can perform computations without knowing the plaintext. However, a malicious actor can still encrypt arbitrary values and craft valid ciphertexts because encryption depends on your public key and it is publicly known. For example, even if a salary is encrypted, an attacker could encrypt 500 USD using your public key and update the encrypted salary with this forged ciphertext — without needing to know the original value.
|
|
369
|
-
|
|
370
|
-
To mitigate this, we recommend combining LightPHE with digital signature schemes. By keeping a signed audit trail of update operations in a separate log table, you can detect and reject unauthorized changes, and even restore the original data in case of tampering. We suggest checking out [`LightDSA`](https://github.com/serengil/LightDSA), which provides a lightweight cryptographic interface similar to LightPHE and supports popular digital signature algorithms like RSA, DSA, ECDSA, and EdDSA.
|
|
371
|
-
|
|
372
199
|
# Contributing
|
|
373
200
|
|
|
374
201
|
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.
|