lightphe 0.0.14__tar.gz → 0.0.16__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.14 → lightphe-0.0.16}/PKG-INFO +55 -24
- {lightphe-0.0.14 → lightphe-0.0.16}/README.md +54 -23
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/__init__.py +7 -47
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/Benaloh.py +1 -7
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/DamgardJurik.py +1 -7
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/ElGamal.py +1 -4
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/EllipticCurveElGamal.py +6 -19
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/GoldwasserMicali.py +33 -36
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/NaccacheStern.py +103 -100
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/OkamotoUchiyama.py +1 -11
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/Paillier.py +1 -7
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/RSA.py +0 -12
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/models/Ciphertext.py +2 -2
- lightphe-0.0.16/lightphe/models/Homomorphic.py +71 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/models/Tensor.py +2 -2
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe.egg-info/PKG-INFO +55 -24
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe.egg-info/SOURCES.txt +0 -13
- lightphe-0.0.16/lightphe.egg-info/requires.txt +4 -0
- lightphe-0.0.16/requirements.txt +4 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/setup.py +1 -1
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_benaloh.py +1 -1
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_damgard.py +1 -1
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_elgamal.py +2 -2
- lightphe-0.0.16/tests/test_ellipticcurveelgamal.py +134 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_goldwasser.py +10 -3
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_keys.py +7 -2
- lightphe-0.0.16/tests/test_naccache.py +203 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_okamoto.py +2 -2
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_paillier.py +1 -1
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_rsa.py +1 -1
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_salary.py +1 -1
- lightphe-0.0.14/lightphe/commons/binary_operations.py +0 -147
- lightphe-0.0.14/lightphe/elliptic_curve_forms/edwards.py +0 -89
- lightphe-0.0.14/lightphe/elliptic_curve_forms/koblitz.py +0 -166
- lightphe-0.0.14/lightphe/elliptic_curve_forms/weierstrass.py +0 -120
- lightphe-0.0.14/lightphe/models/Curve.py +0 -29
- lightphe-0.0.14/lightphe/models/EllipticCurve.py +0 -196
- lightphe-0.0.14/lightphe/models/Homomorphic.py +0 -52
- lightphe-0.0.14/lightphe/standard_curves/__init__.py +0 -0
- lightphe-0.0.14/lightphe/standard_curves/edwards.py +0 -144
- lightphe-0.0.14/lightphe/standard_curves/inventory.py +0 -83
- lightphe-0.0.14/lightphe/standard_curves/koblitz.py +0 -433
- lightphe-0.0.14/lightphe/standard_curves/weierstrass.py +0 -936
- lightphe-0.0.14/lightphe.egg-info/requires.txt +0 -3
- lightphe-0.0.14/requirements.txt +0 -3
- lightphe-0.0.14/tests/__init__.py +0 -0
- lightphe-0.0.14/tests/test_ellipticcurve.py +0 -75
- lightphe-0.0.14/tests/test_ellipticcurveelgamal.py +0 -263
- lightphe-0.0.14/tests/test_naccache.py +0 -94
- {lightphe-0.0.14 → lightphe-0.0.16}/LICENSE +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.14/lightphe/elliptic_curve_forms → lightphe-0.0.16/lightphe/models}/__init__.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/setup.cfg +0 -0
- {lightphe-0.0.14/lightphe/models → lightphe-0.0.16/tests}/__init__.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_cloud.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.14 → lightphe-0.0.16}/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.16
|
|
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
|
|
@@ -28,7 +28,7 @@ License-File: LICENSE
|
|
|
28
28
|
[](https://www.youtube.com/@sefiks?sub_confirmation=1)
|
|
29
29
|
[](https://twitter.com/intent/user?screen_name=serengil)
|
|
30
30
|
|
|
31
|
-
[](https://www.patreon.com/serengil?repo=lightphe)
|
|
32
32
|
[](https://github.com/sponsors/serengil)
|
|
33
33
|
[](https://buymeacoffee.com/serengil)
|
|
34
34
|
|
|
@@ -252,11 +252,9 @@ ECC is a powerful public-key cryptosystem based on the algebraic structure of el
|
|
|
252
252
|
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
253
|
|
|
254
254
|
```python
|
|
255
|
-
forms = ["weierstass", "edwards", "koblitz"]
|
|
256
|
-
|
|
257
255
|
phe = LightPHE(
|
|
258
256
|
algorithm_name="EllipticCurve-ElGamal",
|
|
259
|
-
form=
|
|
257
|
+
form="edwards", # or weierstrass, koblitz
|
|
260
258
|
)
|
|
261
259
|
```
|
|
262
260
|
|
|
@@ -264,7 +262,7 @@ One of the crucial factors that define the security level of an elliptic curve c
|
|
|
264
262
|
|
|
265
263
|
Each curve in LightPHE has a specific order, which is carefully chosen to balance performance and security. By selecting an elliptic curve with a larger order, you increase the security of your cryptographic system, but this may come with a trade-off in computational efficiency. Therefore, choosing the appropriate curve order is a crucial decision based on your application’s security and performance requirements.
|
|
266
264
|
|
|
267
|
-
See [`curves`](https://github.com/serengil/
|
|
265
|
+
See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a list of all supported forms, curves and their details.
|
|
268
266
|
|
|
269
267
|
### Vector Embeddings and Tensors
|
|
270
268
|
|
|
@@ -343,26 +341,50 @@ Many machine learning models rely on a two-tower architecture, including facial
|
|
|
343
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.
|
|
344
342
|
|
|
345
343
|
```python
|
|
346
|
-
#
|
|
347
|
-
cs = LightPHE("Paillier")
|
|
348
|
-
|
|
349
|
-
# define plain vectors - suppose those are l2 normalized already
|
|
344
|
+
# define a plain vectors for source and target
|
|
350
345
|
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
351
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)
|
|
352
351
|
|
|
353
|
-
#
|
|
352
|
+
# export keys
|
|
353
|
+
cs.export_keys("secret.txt")
|
|
354
|
+
cs.export_keys("public.txt", public=True)
|
|
355
|
+
|
|
356
|
+
# encrypt source embedding
|
|
354
357
|
encrypted_alpha = cs.encrypt(alpha)
|
|
355
358
|
|
|
356
|
-
#
|
|
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
|
|
357
366
|
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
358
367
|
|
|
359
|
-
# decrypt
|
|
360
|
-
|
|
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]
|
|
361
377
|
|
|
362
378
|
# proof of work
|
|
363
|
-
assert abs(
|
|
379
|
+
assert abs(calculated_similarity - expected_similarity) < 1e-2
|
|
364
380
|
```
|
|
365
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
|
+
|
|
366
388
|
# Contributing
|
|
367
389
|
|
|
368
390
|
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.
|
|
@@ -376,11 +398,11 @@ There are many ways to support a project - starring⭐️ the GitHub repo is jus
|
|
|
376
398
|
You can also support this work on [Patreon](https://www.patreon.com/serengil?repo=lightphe), [GitHub Sponsors](https://github.com/sponsors/serengil) or [Buy Me a Coffee](https://buymeacoffee.com/serengil).
|
|
377
399
|
|
|
378
400
|
<a href="https://www.patreon.com/serengil?repo=lightphe">
|
|
379
|
-
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%"
|
|
401
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%">
|
|
380
402
|
</a>
|
|
381
403
|
|
|
382
404
|
<a href="https://buymeacoffee.com/serengil">
|
|
383
|
-
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/bmc-button.png" width="25%"
|
|
405
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/bmc-button.png" width="25%">
|
|
384
406
|
</a>
|
|
385
407
|
|
|
386
408
|
Also, your company's logo will be shown on README on GitHub if you become sponsor in gold, silver or bronze tiers.
|
|
@@ -390,13 +412,22 @@ Also, your company's logo will be shown on README on GitHub if you become sponso
|
|
|
390
412
|
Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
|
|
391
413
|
|
|
392
414
|
```BibTeX
|
|
393
|
-
@
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
415
|
+
@article{serengil2024lightphe,
|
|
416
|
+
title={LightPHE: Integrating Partially Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations},
|
|
417
|
+
author={Serengil, Sefik Ilkin and Ozpinar, Alper},
|
|
418
|
+
journal={arXiv preprint arXiv:2408.05219},
|
|
419
|
+
note={doi: 10.48550/arXiv.2408.05219. [Online]. Available: \url{https://arxiv.org/abs/2408.05219}},
|
|
420
|
+
year={2025}
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
```BibTeX
|
|
425
|
+
@article{serengil2025vectorsimilarity,
|
|
426
|
+
title={Encrypted Vector Similarity Computations Using Partially Homomorphic Encryption: Applications and Performance Analysis},
|
|
427
|
+
author={Serengil, Sefik and Ozpinar, Alper},
|
|
428
|
+
journal={arXiv preprint arXiv:2503.05850},
|
|
429
|
+
note={doi: 10.48550/arXiv.2503.05850. [Online]. Available: \url{https://arxiv.org/abs/2503.05850}},
|
|
430
|
+
year={2025}
|
|
400
431
|
}
|
|
401
432
|
```
|
|
402
433
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://www.youtube.com/@sefiks?sub_confirmation=1)
|
|
13
13
|
[](https://twitter.com/intent/user?screen_name=serengil)
|
|
14
14
|
|
|
15
|
-
[](https://www.patreon.com/serengil?repo=lightphe)
|
|
16
16
|
[](https://github.com/sponsors/serengil)
|
|
17
17
|
[](https://buymeacoffee.com/serengil)
|
|
18
18
|
|
|
@@ -236,11 +236,9 @@ ECC is a powerful public-key cryptosystem based on the algebraic structure of el
|
|
|
236
236
|
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
237
|
|
|
238
238
|
```python
|
|
239
|
-
forms = ["weierstass", "edwards", "koblitz"]
|
|
240
|
-
|
|
241
239
|
phe = LightPHE(
|
|
242
240
|
algorithm_name="EllipticCurve-ElGamal",
|
|
243
|
-
form=
|
|
241
|
+
form="edwards", # or weierstrass, koblitz
|
|
244
242
|
)
|
|
245
243
|
```
|
|
246
244
|
|
|
@@ -248,7 +246,7 @@ One of the crucial factors that define the security level of an elliptic curve c
|
|
|
248
246
|
|
|
249
247
|
Each curve in LightPHE has a specific order, which is carefully chosen to balance performance and security. By selecting an elliptic curve with a larger order, you increase the security of your cryptographic system, but this may come with a trade-off in computational efficiency. Therefore, choosing the appropriate curve order is a crucial decision based on your application’s security and performance requirements.
|
|
250
248
|
|
|
251
|
-
See [`curves`](https://github.com/serengil/
|
|
249
|
+
See [`curves`](https://github.com/serengil/LightECC#supported-curves) page for a list of all supported forms, curves and their details.
|
|
252
250
|
|
|
253
251
|
### Vector Embeddings and Tensors
|
|
254
252
|
|
|
@@ -327,26 +325,50 @@ Many machine learning models rely on a two-tower architecture, including facial
|
|
|
327
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.
|
|
328
326
|
|
|
329
327
|
```python
|
|
330
|
-
#
|
|
331
|
-
cs = LightPHE("Paillier")
|
|
332
|
-
|
|
333
|
-
# define plain vectors - suppose those are l2 normalized already
|
|
328
|
+
# define a plain vectors for source and target
|
|
334
329
|
alpha = [7.1, 5.2, 5.3, 2.4, 3.5, 4.6] # On-prem vector (user tower)
|
|
335
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)
|
|
336
335
|
|
|
337
|
-
#
|
|
336
|
+
# export keys
|
|
337
|
+
cs.export_keys("secret.txt")
|
|
338
|
+
cs.export_keys("public.txt", public=True)
|
|
339
|
+
|
|
340
|
+
# encrypt source embedding
|
|
338
341
|
encrypted_alpha = cs.encrypt(alpha)
|
|
339
342
|
|
|
340
|
-
#
|
|
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
|
|
341
350
|
encrypted_cosine_similarity = encrypted_alpha @ beta
|
|
342
351
|
|
|
343
|
-
# decrypt
|
|
344
|
-
|
|
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]
|
|
345
361
|
|
|
346
362
|
# proof of work
|
|
347
|
-
assert abs(
|
|
363
|
+
assert abs(calculated_similarity - expected_similarity) < 1e-2
|
|
348
364
|
```
|
|
349
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
|
+
|
|
350
372
|
# Contributing
|
|
351
373
|
|
|
352
374
|
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.
|
|
@@ -360,11 +382,11 @@ There are many ways to support a project - starring⭐️ the GitHub repo is jus
|
|
|
360
382
|
You can also support this work on [Patreon](https://www.patreon.com/serengil?repo=lightphe), [GitHub Sponsors](https://github.com/sponsors/serengil) or [Buy Me a Coffee](https://buymeacoffee.com/serengil).
|
|
361
383
|
|
|
362
384
|
<a href="https://www.patreon.com/serengil?repo=lightphe">
|
|
363
|
-
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%"
|
|
385
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%">
|
|
364
386
|
</a>
|
|
365
387
|
|
|
366
388
|
<a href="https://buymeacoffee.com/serengil">
|
|
367
|
-
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/bmc-button.png" width="25%"
|
|
389
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/bmc-button.png" width="25%">
|
|
368
390
|
</a>
|
|
369
391
|
|
|
370
392
|
Also, your company's logo will be shown on README on GitHub if you become sponsor in gold, silver or bronze tiers.
|
|
@@ -374,13 +396,22 @@ Also, your company's logo will be shown on README on GitHub if you become sponso
|
|
|
374
396
|
Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
|
|
375
397
|
|
|
376
398
|
```BibTeX
|
|
377
|
-
@
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
399
|
+
@article{serengil2024lightphe,
|
|
400
|
+
title={LightPHE: Integrating Partially Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations},
|
|
401
|
+
author={Serengil, Sefik Ilkin and Ozpinar, Alper},
|
|
402
|
+
journal={arXiv preprint arXiv:2408.05219},
|
|
403
|
+
note={doi: 10.48550/arXiv.2408.05219. [Online]. Available: \url{https://arxiv.org/abs/2408.05219}},
|
|
404
|
+
year={2025}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```BibTeX
|
|
409
|
+
@article{serengil2025vectorsimilarity,
|
|
410
|
+
title={Encrypted Vector Similarity Computations Using Partially Homomorphic Encryption: Applications and Performance Analysis},
|
|
411
|
+
author={Serengil, Sefik and Ozpinar, Alper},
|
|
412
|
+
journal={arXiv preprint arXiv:2503.05850},
|
|
413
|
+
note={doi: 10.48550/arXiv.2503.05850. [Online]. Available: \url{https://arxiv.org/abs/2503.05850}},
|
|
414
|
+
year={2025}
|
|
384
415
|
}
|
|
385
416
|
```
|
|
386
417
|
|
|
@@ -9,13 +9,16 @@ 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
|
|
12
16
|
|
|
13
17
|
# project dependencies
|
|
14
18
|
from lightphe.models.Homomorphic import Homomorphic
|
|
15
19
|
from lightphe.models.Ciphertext import Ciphertext
|
|
16
20
|
from lightphe.models.Algorithm import Algorithm
|
|
17
21
|
from lightphe.models.Tensor import Fraction, EncryptedTensor
|
|
18
|
-
from lightphe.models.EllipticCurve import EllipticCurvePoint
|
|
19
22
|
from lightphe.commons import phe_utils
|
|
20
23
|
from lightphe.commons.logger import Logger
|
|
21
24
|
|
|
@@ -29,16 +32,13 @@ from lightphe.cryptosystems.Benaloh import Benaloh
|
|
|
29
32
|
from lightphe.cryptosystems.NaccacheStern import NaccacheStern
|
|
30
33
|
from lightphe.cryptosystems.GoldwasserMicali import GoldwasserMicali
|
|
31
34
|
from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
|
|
32
|
-
from lightphe.elliptic_curve_forms.weierstrass import Weierstrass
|
|
33
|
-
from lightphe.elliptic_curve_forms.edwards import TwistedEdwards
|
|
34
|
-
from lightphe.elliptic_curve_forms.koblitz import Koblitz
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
# pylint: disable=eval-used, simplifiable-if-expression, too-few-public-methods
|
|
38
38
|
|
|
39
39
|
logger = Logger(module="lightphe/__init__.py")
|
|
40
40
|
|
|
41
|
-
VERSION = "0.0.
|
|
41
|
+
VERSION = "0.0.16"
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class LightPHE:
|
|
@@ -94,7 +94,7 @@ class LightPHE:
|
|
|
94
94
|
|
|
95
95
|
def __build_cryptosystem(
|
|
96
96
|
self,
|
|
97
|
-
algorithm_name: str,
|
|
97
|
+
algorithm_name: str = "Paillier",
|
|
98
98
|
keys: Optional[dict] = None,
|
|
99
99
|
key_size: Optional[int] = None,
|
|
100
100
|
form: Optional[str] = None,
|
|
@@ -115,6 +115,7 @@ class LightPHE:
|
|
|
115
115
|
algorithm_name (str): RSA | ElGamal | Exponential-ElGamal | EllipticCurve-ElGamal
|
|
116
116
|
| Paillier | Damgard-Jurik | Okamoto-Uchiyama | Benaloh | Naccache-Stern
|
|
117
117
|
| Goldwasser-Micali | Edwards-ElGamal
|
|
118
|
+
Default is Paillier.
|
|
118
119
|
keys (dict): optional private-public key pair
|
|
119
120
|
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
120
121
|
key_size (int): key size in bits
|
|
@@ -464,44 +465,3 @@ def encrypt_float(
|
|
|
464
465
|
logger.error(f"Exception while running encrypt_float: {str(err)}")
|
|
465
466
|
logger.error(traceback.format_exc())
|
|
466
467
|
raise err
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
class ECC:
|
|
470
|
-
__version__ = VERSION
|
|
471
|
-
|
|
472
|
-
def __init__(
|
|
473
|
-
self, form_name: Optional[str] = None, curve_name: Optional[str] = None
|
|
474
|
-
):
|
|
475
|
-
"""
|
|
476
|
-
Construct an Elliptic Curve over a finite field (prime or binary)
|
|
477
|
-
Args:
|
|
478
|
-
form_name (str): specifies the form of the elliptic curve.
|
|
479
|
-
Options: 'weierstrass' (default), 'edwards', 'koblitz'.
|
|
480
|
-
curve_name (str): specifies the elliptic curve to use.
|
|
481
|
-
Options:
|
|
482
|
-
- e.g. ed25519, ed448 for edwards form
|
|
483
|
-
- e.g. secp256k1 for weierstrass form
|
|
484
|
-
- e.g. k-409 for koblitz form
|
|
485
|
-
List of all available curves:
|
|
486
|
-
github.com/serengil/LightPHE/blob/master/lightphe/elliptic_curve_forms/README.md
|
|
487
|
-
"""
|
|
488
|
-
if form_name is None or form_name == "weierstrass":
|
|
489
|
-
self.curve = Weierstrass(curve=curve_name)
|
|
490
|
-
elif form_name in "edwards":
|
|
491
|
-
self.curve = TwistedEdwards(curve=curve_name)
|
|
492
|
-
elif form_name in "koblitz":
|
|
493
|
-
self.curve = Koblitz(curve=curve_name)
|
|
494
|
-
else:
|
|
495
|
-
raise ValueError(f"unimplemented curve form - {form_name}")
|
|
496
|
-
|
|
497
|
-
# base point
|
|
498
|
-
self.G = EllipticCurvePoint(self.curve.G[0], self.curve.G[1], self.curve)
|
|
499
|
-
|
|
500
|
-
# order of the curve
|
|
501
|
-
self.n = self.curve.n
|
|
502
|
-
|
|
503
|
-
# point at infinity or neutral / identity element
|
|
504
|
-
self.O = EllipticCurvePoint(self.curve.O[0], self.curve.O[1], self.curve)
|
|
505
|
-
|
|
506
|
-
# modulo
|
|
507
|
-
self.modulo = self.curve.modulo
|
|
@@ -169,13 +169,7 @@ class Benaloh(Homomorphic):
|
|
|
169
169
|
n = self.keys["public_key"]["n"]
|
|
170
170
|
return (ciphertext1 * ciphertext2) % n
|
|
171
171
|
|
|
172
|
-
def
|
|
173
|
-
raise ValueError("Benaloh is not homomorphic with respect to the multiplication")
|
|
174
|
-
|
|
175
|
-
def xor(self, ciphertext1: int, ciphertext2: int) -> int:
|
|
176
|
-
raise ValueError("Benaloh is not homomorphic with respect to the exclusive or")
|
|
177
|
-
|
|
178
|
-
def multiply_by_contant(self, ciphertext: int, constant: int) -> int:
|
|
172
|
+
def multiply_by_constant(self, ciphertext: int, constant: int) -> int:
|
|
179
173
|
"""
|
|
180
174
|
Multiply a ciphertext with a plain constant.
|
|
181
175
|
Result of this must be equal to E(m1 * constant) where E(m1) = ciphertext
|
|
@@ -125,13 +125,7 @@ class DamgardJurik(Homomorphic):
|
|
|
125
125
|
modulo = pow(n, s + 1)
|
|
126
126
|
return (ciphertext1 * ciphertext2) % modulo
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
raise ValueError("Damgard-Jurik is not homomorphic with respect to the multiplication")
|
|
130
|
-
|
|
131
|
-
def xor(self, ciphertext1: int, ciphertext2: int) -> int:
|
|
132
|
-
raise ValueError("Damgard-Jurik is not homomorphic with respect to the exclusive or")
|
|
133
|
-
|
|
134
|
-
def multiply_by_contant(self, ciphertext: int, constant: int) -> int:
|
|
128
|
+
def multiply_by_constant(self, ciphertext: int, constant: int) -> int:
|
|
135
129
|
"""
|
|
136
130
|
Multiply a ciphertext by a known plain constant
|
|
137
131
|
Result of this must be equal to E(m1 * m2), where E(m1) = ciphertext
|
|
@@ -169,10 +169,7 @@ class ElGamal(Homomorphic):
|
|
|
169
169
|
p = self.keys["public_key"]["p"]
|
|
170
170
|
return (ciphertext1[0] * ciphertext2[0]) % p, (ciphertext1[1] * ciphertext2[1]) % p
|
|
171
171
|
|
|
172
|
-
def
|
|
173
|
-
raise ValueError("ElGamal is not homomorphic with respect to the exclusive or")
|
|
174
|
-
|
|
175
|
-
def multiply_by_contant(self, ciphertext: tuple, constant: int) -> tuple:
|
|
172
|
+
def multiply_by_constant(self, ciphertext: tuple, constant: int) -> tuple:
|
|
176
173
|
if self.exponential is False:
|
|
177
174
|
raise ValueError("ElGamal is not supporting multiplying ciphertext by a known constant")
|
|
178
175
|
p = self.keys["public_key"]["p"]
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import random
|
|
3
3
|
from typing import Optional
|
|
4
4
|
|
|
5
|
+
# 3rd party dependencies
|
|
6
|
+
from lightecc import LightECC as ECC
|
|
7
|
+
from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
|
|
8
|
+
|
|
5
9
|
# project dependencies
|
|
6
10
|
from lightphe.models.Homomorphic import Homomorphic
|
|
7
|
-
from lightphe.models.EllipticCurve import EllipticCurvePoint
|
|
8
11
|
from lightphe.commons.logger import Logger
|
|
9
12
|
|
|
10
13
|
logger = Logger(module="lightphe/cryptosystems/EllipticCurveElGamal.py")
|
|
@@ -40,9 +43,6 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
40
43
|
- secp256k1 for weierstrass form
|
|
41
44
|
This parameter is only used if `algorithm_name` is 'EllipticCurve-ElGamal'.
|
|
42
45
|
"""
|
|
43
|
-
# to avoid circular import, import ECC class here
|
|
44
|
-
from lightphe import ECC
|
|
45
|
-
|
|
46
46
|
self.ecc = ECC(form_name=form, curve_name=curve)
|
|
47
47
|
|
|
48
48
|
self.keys = keys or self.generate_keys(key_size or self.ecc.n.bit_length())
|
|
@@ -142,11 +142,6 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
142
142
|
|
|
143
143
|
return s_prime / self.ecc.G
|
|
144
144
|
|
|
145
|
-
def multiply(self, ciphertext1: tuple, ciphertext2: tuple) -> tuple:
|
|
146
|
-
raise ValueError(
|
|
147
|
-
"Elliptic Curve ElGamal is not homomorphic with respect to the multiplication"
|
|
148
|
-
)
|
|
149
|
-
|
|
150
145
|
def add(self, ciphertext1: tuple, ciphertext2: tuple) -> tuple:
|
|
151
146
|
"""
|
|
152
147
|
Perform homomorphic addition on encrypted data
|
|
@@ -171,12 +166,9 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
171
166
|
|
|
172
167
|
return a.get_point(), b.get_point()
|
|
173
168
|
|
|
174
|
-
def xor(self, ciphertext1: tuple, ciphertext2: tuple) -> int:
|
|
175
|
-
raise ValueError(
|
|
176
|
-
"Elliptic Curve ElGamal is not homomorphic with respect to the exclusive or"
|
|
177
|
-
)
|
|
178
169
|
|
|
179
|
-
|
|
170
|
+
|
|
171
|
+
def multiply_by_constant(self, ciphertext: tuple, constant: int) -> tuple:
|
|
180
172
|
"""
|
|
181
173
|
Multiply a ciphertext with a plain constant.
|
|
182
174
|
Result of this must be equal to k x E(m1) = E(m1 * k)
|
|
@@ -198,8 +190,3 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
198
190
|
Q_prime = Q * constant
|
|
199
191
|
|
|
200
192
|
return P_prime.get_point(), Q_prime.get_point()
|
|
201
|
-
|
|
202
|
-
def reencrypt(self, ciphertext: tuple) -> tuple:
|
|
203
|
-
raise ValueError(
|
|
204
|
-
"Elliptic Curve ElGamal does not support regeneration of ciphertext"
|
|
205
|
-
)
|
|
@@ -6,6 +6,7 @@ import math
|
|
|
6
6
|
# 3rd party dependencies
|
|
7
7
|
import sympy
|
|
8
8
|
from sympy import jacobi_symbol
|
|
9
|
+
from tqdm import tqdm
|
|
9
10
|
|
|
10
11
|
# project dependencies
|
|
11
12
|
from lightphe.models.Homomorphic import Homomorphic
|
|
@@ -29,51 +30,53 @@ class GoldwasserMicali(Homomorphic):
|
|
|
29
30
|
set this to None if you want to generate random keys.
|
|
30
31
|
key_size (int): key size in bits
|
|
31
32
|
"""
|
|
32
|
-
|
|
33
|
-
self.keys = keys or self.generate_keys(key_size or
|
|
33
|
+
|
|
34
|
+
self.keys = keys or self.generate_keys(key_size or 1024)
|
|
34
35
|
self.ciphertext_modulo = self.keys["public_key"]["n"]
|
|
35
36
|
# TODO: not sure about the plaintext modulo
|
|
36
37
|
self.plaintext_modulo = self.keys["public_key"]["n"]
|
|
37
38
|
|
|
38
|
-
def generate_keys(self, key_size: int) -> dict:
|
|
39
|
+
def generate_keys(self, key_size: int, max_tries: int = 10000) -> dict:
|
|
39
40
|
"""
|
|
40
41
|
Generate public and private keys of Goldwasser-Micali cryptosystem
|
|
41
42
|
Args:
|
|
42
43
|
key_size (int): key size in bits
|
|
44
|
+
max_tries (int): maximum number of tries to generate keys
|
|
43
45
|
Returns:
|
|
44
46
|
keys (dict): having private_key and public_key keys
|
|
45
47
|
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
for attempt in tqdm(range(max_tries), disable=True):
|
|
49
|
+
# pick large random primes p and q
|
|
50
|
+
p = sympy.randprime(2 ** (key_size // 2 - 100), 2 ** (key_size // 2) - 1)
|
|
51
|
+
q = sympy.randprime(2 ** (key_size // 2 - 100), 2 ** (key_size // 2) - 1)
|
|
52
|
+
|
|
53
|
+
# to prevent factorizatin attacks, it is recommended that n should be
|
|
54
|
+
# several hundred bits or more
|
|
55
|
+
n = p * q
|
|
56
|
+
|
|
57
|
+
# find quadratic non-residue x
|
|
58
|
+
for _ in range(1000): # try max 1000 random x
|
|
59
|
+
x = random.randint(2, n - 1)
|
|
60
|
+
if math.gcd(x, n) != 1:
|
|
61
|
+
continue
|
|
62
|
+
if jacobi_symbol(x, p) == -1 and jacobi_symbol(x, q) == -1:
|
|
63
|
+
break
|
|
64
|
+
else:
|
|
65
|
+
# # If no suitable x is found after 1000 tries, discard the current p–q pair and retry
|
|
66
|
+
continue
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
68
|
+
keys = {
|
|
69
|
+
"public_key": {"n": n, "x": x},
|
|
70
|
+
"private_key": {"p": p, "q": q},
|
|
71
|
+
}
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
73
|
+
logger.debug(
|
|
74
|
+
f"Goldwasser-Micali keys generated after {attempt+1} attempts, n bits: {n.bit_length()}"
|
|
75
|
+
)
|
|
57
76
|
|
|
58
|
-
|
|
77
|
+
return keys
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
while True:
|
|
62
|
-
x = random.randint(1, n - 1)
|
|
63
|
-
if (
|
|
64
|
-
math.gcd(x, n) == 1
|
|
65
|
-
and jacobi_symbol(x, p) == -1
|
|
66
|
-
and jacobi_symbol(x, q) == -1
|
|
67
|
-
):
|
|
68
|
-
break
|
|
69
|
-
|
|
70
|
-
keys["public_key"]["n"] = n
|
|
71
|
-
keys["public_key"]["x"] = x
|
|
72
|
-
|
|
73
|
-
keys["private_key"]["p"] = p
|
|
74
|
-
keys["private_key"]["q"] = q
|
|
75
|
-
|
|
76
|
-
return keys
|
|
79
|
+
raise RuntimeError(f"Failed to generate keys after {max_tries} attempts")
|
|
77
80
|
|
|
78
81
|
def generate_random_key(self) -> int:
|
|
79
82
|
"""
|
|
@@ -193,9 +196,3 @@ class GoldwasserMicali(Homomorphic):
|
|
|
193
196
|
ciphertext3.append((c1 * c2) % self.ciphertext_modulo)
|
|
194
197
|
|
|
195
198
|
return ciphertext3
|
|
196
|
-
|
|
197
|
-
def multiply_by_contant(self, ciphertext: int, constant: int):
|
|
198
|
-
raise ValueError("Goldwasser-Micali does not support multiplying with constant")
|
|
199
|
-
|
|
200
|
-
def reencrypt(self, ciphertext: int):
|
|
201
|
-
raise ValueError("Goldwasser-Micali does not support re-encryption")
|