lightphe 0.0.1__tar.gz → 0.0.2__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.1 → lightphe-0.0.2}/PKG-INFO +17 -7
- {lightphe-0.0.1 → lightphe-0.0.2}/README.md +16 -6
- lightphe-0.0.2/lightphe/__init__.py +219 -0
- lightphe-0.0.2/lightphe/commons/__init__.py +0 -0
- lightphe-0.0.2/lightphe/commons/logger.py +39 -0
- lightphe-0.0.2/lightphe/cryptosystems/Benaloh.py +208 -0
- lightphe-0.0.2/lightphe/cryptosystems/DamgardJurik.py +177 -0
- lightphe-0.0.2/lightphe/cryptosystems/ElGamal.py +202 -0
- lightphe-0.0.2/lightphe/cryptosystems/EllipticCurveElGamal.py +168 -0
- lightphe-0.0.2/lightphe/cryptosystems/GoldwasserMicali.py +166 -0
- lightphe-0.0.2/lightphe/cryptosystems/NaccacheStern.py +301 -0
- lightphe-0.0.2/lightphe/cryptosystems/OkamotoUchiyama.py +180 -0
- lightphe-0.0.2/lightphe/cryptosystems/Paillier.py +166 -0
- lightphe-0.0.2/lightphe/cryptosystems/RSA.py +142 -0
- lightphe-0.0.2/lightphe/cryptosystems/__init__.py +0 -0
- lightphe-0.0.2/lightphe/elliptic/Weierstrass.py +97 -0
- lightphe-0.0.2/lightphe/elliptic/__init__.py +0 -0
- lightphe-0.0.2/lightphe/models/Algorithm.py +12 -0
- lightphe-0.0.2/lightphe/models/Ciphertext.py +138 -0
- lightphe-0.0.2/lightphe/models/EllipticCurve.py +18 -0
- lightphe-0.0.2/lightphe/models/Homomorphic.py +53 -0
- lightphe-0.0.2/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/PKG-INFO +17 -7
- lightphe-0.0.2/lightphe.egg-info/SOURCES.txt +39 -0
- lightphe-0.0.2/lightphe.egg-info/top_level.txt +2 -0
- {lightphe-0.0.1 → lightphe-0.0.2}/setup.py +1 -1
- lightphe-0.0.2/tests/__init__.py +219 -0
- lightphe-0.0.2/tests/test_benaloh.py +63 -0
- lightphe-0.0.2/tests/test_damgard.py +63 -0
- lightphe-0.0.2/tests/test_elgamal.py +139 -0
- lightphe-0.0.2/tests/test_ellipticcurveelgamal.py +64 -0
- lightphe-0.0.2/tests/test_goldwasser.py +64 -0
- lightphe-0.0.2/tests/test_naccache.py +94 -0
- lightphe-0.0.2/tests/test_okamoto.py +61 -0
- lightphe-0.0.2/tests/test_paillier.py +97 -0
- lightphe-0.0.2/tests/test_rsa.py +65 -0
- lightphe-0.0.2/tests/test_salary.py +53 -0
- lightphe-0.0.1/lightphe.egg-info/SOURCES.txt +0 -8
- lightphe-0.0.1/lightphe.egg-info/top_level.txt +0 -1
- {lightphe-0.0.1 → lightphe-0.0.2}/LICENSE +0 -0
- {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/requires.txt +0 -0
- {lightphe-0.0.1 → lightphe-0.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
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
|
|
@@ -31,13 +31,13 @@ License-File: LICENSE
|
|
|
31
31
|
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
|
-
<p align="center"><img src="icons/phi.png" width="200" height="240"></p>
|
|
34
|
+
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
35
35
|
|
|
36
36
|
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/), [`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/).
|
|
37
37
|
|
|
38
38
|
Even though fully homomorphic encryption (FHE) has become available in recent times, but when considering the trade-offs, LightPHE emerges as a more efficient and practical choice. If your specific task doesn't demand the full homomorphic capabilities, opting for partial homomorphism with LightPHE is the logical decision. LightPHE is notably faster and demands fewer computational resources compared to FHE. Besides, it generates smaller ciphertexts, making it well-suited for memory-constrained environments. Finally, LightPHE strikes a favorable balance between security and efficiency for practical use cases.
|
|
39
39
|
|
|
40
|
-
# Installation
|
|
40
|
+
# Installation [](https://pypi.org/project/lightphe/)
|
|
41
41
|
|
|
42
42
|
The easiest way to install the LightPHE package is to install it from python package index (PyPI).
|
|
43
43
|
|
|
@@ -125,8 +125,8 @@ assert cs.decrypt(c1 + c2) == m1 + m2
|
|
|
125
125
|
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. Simply put, decryption of scalar multiplication of ciphertext is equivalent to that constant times plaintext as well.
|
|
126
126
|
|
|
127
127
|
```python
|
|
128
|
-
# scalar multiplication
|
|
129
|
-
k =
|
|
128
|
+
# scalar multiplication (increase its value 5%)
|
|
129
|
+
k = 1.05
|
|
130
130
|
assert cs.decrypt(k * c1) == k * m1
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -159,6 +159,16 @@ All PRs are more than welcome! If you are planning to contribute a large patch,
|
|
|
159
159
|
|
|
160
160
|
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval. Besides, workflow will evaluate the code with pylint as well.
|
|
161
161
|
|
|
162
|
+
# Support
|
|
163
|
+
|
|
164
|
+
There are many ways to support a project - starring⭐️ the GitHub repo is just one 🙏
|
|
165
|
+
|
|
166
|
+
You can also support this work on [Patreon](https://www.patreon.com/serengil?repo=lightphe) or [GitHub Sponsors](https://github.com/sponsors/serengil).
|
|
167
|
+
|
|
168
|
+
<a href="https://www.patreon.com/serengil?repo=lightphe">
|
|
169
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%" height="30%">
|
|
170
|
+
</a>
|
|
171
|
+
|
|
162
172
|
# Citation
|
|
163
173
|
|
|
164
174
|
Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
|
|
@@ -173,10 +183,10 @@ Please cite LightPHE in your publications if it helps your research. Here is its
|
|
|
173
183
|
}
|
|
174
184
|
```
|
|
175
185
|
|
|
176
|
-
Also, if you use LightPHE in your projects, please add `
|
|
186
|
+
Also, if you use LightPHE in your projects, please add `lightphe` in the `requirements.txt`.
|
|
177
187
|
|
|
178
188
|
# License
|
|
179
189
|
|
|
180
|
-
LightPHE is licensed under the MIT License - see [`LICENSE`](LICENSE) for more details.
|
|
190
|
+
LightPHE is licensed under the MIT License - see [`LICENSE`](https://github.com/serengil/LightPHE/blob/master/LICENSE) for more details.
|
|
181
191
|
|
|
182
192
|
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<p align="center"><img src="icons/phi.png" width="200" height="240"></p>
|
|
18
|
+
<p align="center"><img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/phi.png" width="200" height="240"></p>
|
|
19
19
|
|
|
20
20
|
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/), [`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/).
|
|
21
21
|
|
|
22
22
|
Even though fully homomorphic encryption (FHE) has become available in recent times, but when considering the trade-offs, LightPHE emerges as a more efficient and practical choice. If your specific task doesn't demand the full homomorphic capabilities, opting for partial homomorphism with LightPHE is the logical decision. LightPHE is notably faster and demands fewer computational resources compared to FHE. Besides, it generates smaller ciphertexts, making it well-suited for memory-constrained environments. Finally, LightPHE strikes a favorable balance between security and efficiency for practical use cases.
|
|
23
23
|
|
|
24
|
-
# Installation
|
|
24
|
+
# Installation [](https://pypi.org/project/lightphe/)
|
|
25
25
|
|
|
26
26
|
The easiest way to install the LightPHE package is to install it from python package index (PyPI).
|
|
27
27
|
|
|
@@ -109,8 +109,8 @@ assert cs.decrypt(c1 + c2) == m1 + m2
|
|
|
109
109
|
Besides, Paillier is supporting multiplying ciphertexts by a known plain constant. Simply put, decryption of scalar multiplication of ciphertext is equivalent to that constant times plaintext as well.
|
|
110
110
|
|
|
111
111
|
```python
|
|
112
|
-
# scalar multiplication
|
|
113
|
-
k =
|
|
112
|
+
# scalar multiplication (increase its value 5%)
|
|
113
|
+
k = 1.05
|
|
114
114
|
assert cs.decrypt(k * c1) == k * m1
|
|
115
115
|
```
|
|
116
116
|
|
|
@@ -143,6 +143,16 @@ All PRs are more than welcome! If you are planning to contribute a large patch,
|
|
|
143
143
|
|
|
144
144
|
You should be able run `make test` and `make lint` commands successfully before committing. Once a PR is created, GitHub test workflow will be run automatically and unit test results will be available in [GitHub actions](https://github.com/serengil/LightPHE/actions/workflows/tests.yml) before approval. Besides, workflow will evaluate the code with pylint as well.
|
|
145
145
|
|
|
146
|
+
# Support
|
|
147
|
+
|
|
148
|
+
There are many ways to support a project - starring⭐️ the GitHub repo is just one 🙏
|
|
149
|
+
|
|
150
|
+
You can also support this work on [Patreon](https://www.patreon.com/serengil?repo=lightphe) or [GitHub Sponsors](https://github.com/sponsors/serengil).
|
|
151
|
+
|
|
152
|
+
<a href="https://www.patreon.com/serengil?repo=lightphe">
|
|
153
|
+
<img src="https://raw.githubusercontent.com/serengil/LightPHE/master/icons/patreon.png" width="30%" height="30%">
|
|
154
|
+
</a>
|
|
155
|
+
|
|
146
156
|
# Citation
|
|
147
157
|
|
|
148
158
|
Please cite LightPHE in your publications if it helps your research. Here is its BibTex entry:
|
|
@@ -157,8 +167,8 @@ Please cite LightPHE in your publications if it helps your research. Here is its
|
|
|
157
167
|
}
|
|
158
168
|
```
|
|
159
169
|
|
|
160
|
-
Also, if you use LightPHE in your projects, please add `
|
|
170
|
+
Also, if you use LightPHE in your projects, please add `lightphe` in the `requirements.txt`.
|
|
161
171
|
|
|
162
172
|
# License
|
|
163
173
|
|
|
164
|
-
LightPHE is licensed under the MIT License - see [`LICENSE`](LICENSE) for more details.
|
|
174
|
+
LightPHE is licensed under the MIT License - see [`LICENSE`](https://github.com/serengil/LightPHE/blob/master/LICENSE) for more details.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Optional, Union
|
|
3
|
+
|
|
4
|
+
from lightphe.models.Ciphertext import Ciphertext
|
|
5
|
+
from lightphe.models.Algorithm import Algorithm
|
|
6
|
+
from lightphe.cryptosystems.RSA import RSA
|
|
7
|
+
from lightphe.cryptosystems.ElGamal import ElGamal
|
|
8
|
+
from lightphe.cryptosystems.Paillier import Paillier
|
|
9
|
+
from lightphe.cryptosystems.DamgardJurik import DamgardJurik
|
|
10
|
+
from lightphe.cryptosystems.OkamotoUchiyama import OkamotoUchiyama
|
|
11
|
+
from lightphe.cryptosystems.Benaloh import Benaloh
|
|
12
|
+
from lightphe.cryptosystems.NaccacheStern import NaccacheStern
|
|
13
|
+
from lightphe.cryptosystems.GoldwasserMicali import GoldwasserMicali
|
|
14
|
+
from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
|
|
15
|
+
from lightphe.commons.logger import Logger
|
|
16
|
+
|
|
17
|
+
# pylint: disable=eval-used
|
|
18
|
+
|
|
19
|
+
logger = Logger()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LightPHE:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
algorithm_name: str,
|
|
26
|
+
keys: Optional[dict] = None,
|
|
27
|
+
key_file: Optional[str] = None,
|
|
28
|
+
key_size: Optional[int] = None,
|
|
29
|
+
):
|
|
30
|
+
"""
|
|
31
|
+
Build LightPHE class
|
|
32
|
+
Args:
|
|
33
|
+
algorithm_name (str): RSA | ElGamal | Exponential-ElGamal | EllipticCurve-ElGamal
|
|
34
|
+
| Paillier | Damgard-Jurik | Okamoto-Uchiyama | Benaloh | Naccache-Stern
|
|
35
|
+
| Goldwasser-Micali
|
|
36
|
+
keys (dict): optional private-public key pair
|
|
37
|
+
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
38
|
+
key_size (int): key size in bits
|
|
39
|
+
"""
|
|
40
|
+
self.algorithm_name = algorithm_name
|
|
41
|
+
|
|
42
|
+
if key_file is not None:
|
|
43
|
+
keys = self.restore_keys(target_file=key_file)
|
|
44
|
+
|
|
45
|
+
self.cs = self.build_cryptosystem(
|
|
46
|
+
algorithm_name=algorithm_name, keys=keys, key_size=key_size
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def build_cryptosystem(
|
|
50
|
+
self,
|
|
51
|
+
algorithm_name: str,
|
|
52
|
+
keys: Optional[dict] = None,
|
|
53
|
+
key_size: Optional[int] = None,
|
|
54
|
+
) -> Union[
|
|
55
|
+
RSA,
|
|
56
|
+
ElGamal,
|
|
57
|
+
Paillier,
|
|
58
|
+
DamgardJurik,
|
|
59
|
+
OkamotoUchiyama,
|
|
60
|
+
Benaloh,
|
|
61
|
+
NaccacheStern,
|
|
62
|
+
EllipticCurveElGamal,
|
|
63
|
+
]:
|
|
64
|
+
"""
|
|
65
|
+
Build a cryptosystem among partially homomorphic algorithms
|
|
66
|
+
Args:
|
|
67
|
+
algorithm_name (str): RSA | ElGamal | Exponential-ElGamal | EllipticCurve-ElGamal
|
|
68
|
+
| Paillier | Damgard-Jurik | Okamoto-Uchiyama | Benaloh | Naccache-Stern
|
|
69
|
+
| Goldwasser-Micali
|
|
70
|
+
keys (dict): optional private-public key pair
|
|
71
|
+
key_file (str): if keys are exported, you can load them into cryptosystem
|
|
72
|
+
key_size (int): key size in bits
|
|
73
|
+
Returns
|
|
74
|
+
cryptosystem
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
if key_size is None:
|
|
78
|
+
key_size = self.recommend_key_size(algorithm_name=algorithm_name)
|
|
79
|
+
|
|
80
|
+
# build cryptosystem
|
|
81
|
+
if algorithm_name == Algorithm.RSA:
|
|
82
|
+
cs = RSA(keys=keys, key_size=key_size)
|
|
83
|
+
elif algorithm_name == Algorithm.ElGamal:
|
|
84
|
+
cs = ElGamal(keys=keys, key_size=key_size)
|
|
85
|
+
elif algorithm_name == Algorithm.ExponentialElGamal:
|
|
86
|
+
cs = ElGamal(keys=keys, key_size=key_size, exponential=True)
|
|
87
|
+
elif algorithm_name == Algorithm.EllipticCurveElGamal:
|
|
88
|
+
cs = EllipticCurveElGamal(keys=keys, key_size=key_size)
|
|
89
|
+
elif algorithm_name == Algorithm.Paillier:
|
|
90
|
+
cs = Paillier(keys=keys, key_size=key_size)
|
|
91
|
+
elif algorithm_name == Algorithm.DamgardJurik:
|
|
92
|
+
cs = DamgardJurik(keys=keys, key_size=key_size)
|
|
93
|
+
elif algorithm_name == Algorithm.OkamotoUchiyama:
|
|
94
|
+
cs = OkamotoUchiyama(keys=keys, key_size=key_size)
|
|
95
|
+
elif algorithm_name == Algorithm.Benaloh:
|
|
96
|
+
cs = Benaloh(keys=keys, key_size=key_size)
|
|
97
|
+
elif algorithm_name == Algorithm.NaccacheStern:
|
|
98
|
+
cs = NaccacheStern(keys=keys, key_size=key_size)
|
|
99
|
+
elif algorithm_name == Algorithm.GoldwasserMicali:
|
|
100
|
+
cs = GoldwasserMicali(keys=keys, key_size=key_size)
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f"unimplemented algorithm - {algorithm_name}")
|
|
103
|
+
return cs
|
|
104
|
+
|
|
105
|
+
def encrypt(self, plaintext: int) -> Ciphertext:
|
|
106
|
+
"""
|
|
107
|
+
Encrypt a plaintext with a built cryptosystem
|
|
108
|
+
Args:
|
|
109
|
+
plaintext (int): message
|
|
110
|
+
Returns
|
|
111
|
+
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
112
|
+
"""
|
|
113
|
+
ciphertext = self.cs.encrypt(plaintext=plaintext)
|
|
114
|
+
return Ciphertext(algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext)
|
|
115
|
+
|
|
116
|
+
def decrypt(self, ciphertext: Ciphertext) -> int:
|
|
117
|
+
"""
|
|
118
|
+
Decrypt a ciphertext with a buit cryptosystem
|
|
119
|
+
Args:
|
|
120
|
+
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
121
|
+
Returns:
|
|
122
|
+
plaintext (int): restored message
|
|
123
|
+
"""
|
|
124
|
+
return self.cs.decrypt(ciphertext=ciphertext.value)
|
|
125
|
+
|
|
126
|
+
def regenerate_ciphertext(self, ciphertext: Ciphertext) -> Ciphertext:
|
|
127
|
+
"""
|
|
128
|
+
Generate a different ciphertext belonging to same plaintext
|
|
129
|
+
Args:
|
|
130
|
+
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
131
|
+
Returns:
|
|
132
|
+
ciphertext (from lightphe.models.Ciphertext import Ciphertext): encrypted message
|
|
133
|
+
"""
|
|
134
|
+
ciphertext_new = self.cs.reencrypt(ciphertext=ciphertext.value)
|
|
135
|
+
return Ciphertext(
|
|
136
|
+
algorithm_name=self.algorithm_name, keys=self.cs.keys, value=ciphertext_new
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def export_keys(self, target_file: str, public: bool = False) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Export keys to a file
|
|
142
|
+
Args:
|
|
143
|
+
target_file (str): target file name
|
|
144
|
+
public (bool): set this to True if you will publish this
|
|
145
|
+
to publicly.
|
|
146
|
+
"""
|
|
147
|
+
keys = self.cs.keys
|
|
148
|
+
if public is True and keys.get("private_key") is not None:
|
|
149
|
+
del keys["private_key"]
|
|
150
|
+
|
|
151
|
+
if public is False:
|
|
152
|
+
logger.warn(
|
|
153
|
+
"You did not set public arg to True. So, exported key has private key information."
|
|
154
|
+
"Do not share this to anyone"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
with open(target_file, "w", encoding="UTF-8") as file:
|
|
158
|
+
file.write(json.dumps(keys))
|
|
159
|
+
|
|
160
|
+
def restore_keys(self, target_file: str) -> dict:
|
|
161
|
+
"""
|
|
162
|
+
Restore keys from a file
|
|
163
|
+
Args:
|
|
164
|
+
target_file (str): target file name
|
|
165
|
+
Returns:
|
|
166
|
+
keys (dict): private public key pair
|
|
167
|
+
"""
|
|
168
|
+
with open(target_file, "r", encoding="UTF-8") as file:
|
|
169
|
+
dict_str = file.read()
|
|
170
|
+
|
|
171
|
+
keys = eval(dict_str)
|
|
172
|
+
if not isinstance(keys, dict):
|
|
173
|
+
raise ValueError(
|
|
174
|
+
f"The content of the file {target_file} does not represent a valid dictionary."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if "private_key" in keys.keys():
|
|
178
|
+
logger.info(f"private-public key pair is restored from {target_file}")
|
|
179
|
+
elif "public_key" in keys.keys():
|
|
180
|
+
logger.info(f"public key is restored from {target_file}")
|
|
181
|
+
else:
|
|
182
|
+
raise ValueError(f"File {target_file} must have public_key key")
|
|
183
|
+
return keys
|
|
184
|
+
|
|
185
|
+
def recommend_key_size(self, algorithm_name: str) -> int:
|
|
186
|
+
"""
|
|
187
|
+
Recommend a key size in bits if it is not mentioned by the user
|
|
188
|
+
Args:
|
|
189
|
+
algorithm_name (str): algorithm name
|
|
190
|
+
Returns
|
|
191
|
+
key_size (int)
|
|
192
|
+
"""
|
|
193
|
+
if algorithm_name == Algorithm.RSA:
|
|
194
|
+
key_size = 1024
|
|
195
|
+
elif algorithm_name == Algorithm.ElGamal:
|
|
196
|
+
key_size = 1024
|
|
197
|
+
elif algorithm_name == Algorithm.ExponentialElGamal:
|
|
198
|
+
key_size = 1024
|
|
199
|
+
# 160-bit ECC is equivalent to 1024-bit RSA
|
|
200
|
+
elif algorithm_name == Algorithm.EllipticCurveElGamal:
|
|
201
|
+
key_size = 160
|
|
202
|
+
elif algorithm_name == Algorithm.Paillier:
|
|
203
|
+
key_size = 1024
|
|
204
|
+
elif algorithm_name == Algorithm.DamgardJurik:
|
|
205
|
+
key_size = 1024
|
|
206
|
+
elif algorithm_name == Algorithm.OkamotoUchiyama:
|
|
207
|
+
key_size = 1024
|
|
208
|
+
# n should be several hundred bits or more
|
|
209
|
+
elif algorithm_name == Algorithm.GoldwasserMicali:
|
|
210
|
+
key_size = 100
|
|
211
|
+
# Benaloh and Naccache-Stern require to solve DLP in decryption
|
|
212
|
+
# so small key is recommended
|
|
213
|
+
elif algorithm_name == Algorithm.Benaloh:
|
|
214
|
+
key_size = 50
|
|
215
|
+
elif algorithm_name == Algorithm.NaccacheStern:
|
|
216
|
+
key_size = 37
|
|
217
|
+
else:
|
|
218
|
+
raise ValueError(f"unimplemented algorithm - {algorithm_name}")
|
|
219
|
+
return key_size
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Logger:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
log_level = os.environ.get("LIGHTPHE_LOG_LEVEL", str(logging.INFO))
|
|
9
|
+
try:
|
|
10
|
+
self.log_level = int(log_level)
|
|
11
|
+
except Exception as err:
|
|
12
|
+
self.dump_log(
|
|
13
|
+
f"Exception while parsing $LIGHTPHE_LOG_LEVEL."
|
|
14
|
+
f"Expected int but it is {log_level} ({str(err)})"
|
|
15
|
+
)
|
|
16
|
+
self.log_level = logging.INFO
|
|
17
|
+
|
|
18
|
+
def debug(self, message):
|
|
19
|
+
if self.log_level <= logging.DEBUG:
|
|
20
|
+
self.dump_log(message)
|
|
21
|
+
|
|
22
|
+
def info(self, message):
|
|
23
|
+
if self.log_level <= logging.INFO:
|
|
24
|
+
self.dump_log(message)
|
|
25
|
+
|
|
26
|
+
def warn(self, message):
|
|
27
|
+
if self.log_level <= logging.WARNING:
|
|
28
|
+
self.dump_log(message)
|
|
29
|
+
|
|
30
|
+
def error(self, message):
|
|
31
|
+
if self.log_level <= logging.ERROR:
|
|
32
|
+
self.dump_log(message)
|
|
33
|
+
|
|
34
|
+
def critical(self, message):
|
|
35
|
+
if self.log_level <= logging.CRITICAL:
|
|
36
|
+
self.dump_log(message)
|
|
37
|
+
|
|
38
|
+
def dump_log(self, message):
|
|
39
|
+
print(f"{str(datetime.now())[2:-7]} - {message}")
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from math import gcd
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import sympy
|
|
5
|
+
from lightphe.models.Homomorphic import Homomorphic
|
|
6
|
+
from lightphe.commons.logger import Logger
|
|
7
|
+
|
|
8
|
+
logger = Logger()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Benaloh(Homomorphic):
|
|
12
|
+
def __init__(self, keys: Optional[dict] = None, key_size: int = 50):
|
|
13
|
+
"""
|
|
14
|
+
Args:
|
|
15
|
+
keys (dict): private - public key pair.
|
|
16
|
+
set this to None if you want to generate random keys.
|
|
17
|
+
key_size (int): key size in bits. default is less than other cryptosystems
|
|
18
|
+
because decryption of Benaloh requires to solve DLP :/
|
|
19
|
+
"""
|
|
20
|
+
self.keys = keys or self.generate_keys(key_size)
|
|
21
|
+
self.plaintext_modulo = self.keys["public_key"]["r"]
|
|
22
|
+
self.ciphertext_modulo = self.keys["public_key"]["n"]
|
|
23
|
+
|
|
24
|
+
def generate_keys(self, key_size: int) -> dict:
|
|
25
|
+
"""
|
|
26
|
+
Generate public and private keys of Paillier cryptosystem
|
|
27
|
+
Args:
|
|
28
|
+
key_size (int): key size in bits
|
|
29
|
+
Returns:
|
|
30
|
+
keys (dict): having private_key and public_key keys
|
|
31
|
+
"""
|
|
32
|
+
keys = {}
|
|
33
|
+
keys["private_key"] = {}
|
|
34
|
+
keys["public_key"] = {}
|
|
35
|
+
|
|
36
|
+
x = 1
|
|
37
|
+
while x == 1:
|
|
38
|
+
# picking a prime p
|
|
39
|
+
p = sympy.randprime(200, 2**key_size)
|
|
40
|
+
|
|
41
|
+
# picking a prime q
|
|
42
|
+
q = sympy.randprime(100, p)
|
|
43
|
+
|
|
44
|
+
n = p * q
|
|
45
|
+
phi = (p - 1) * (q - 1)
|
|
46
|
+
|
|
47
|
+
r = p - 1
|
|
48
|
+
while gcd(q - 1, r) != 1:
|
|
49
|
+
r = int(r / gcd(q - 1, r))
|
|
50
|
+
|
|
51
|
+
if not (
|
|
52
|
+
# r should divide p-1 without remainder
|
|
53
|
+
(p - 1) % r == 0
|
|
54
|
+
# r and (p - 1) / r must be coprimes
|
|
55
|
+
and gcd(r, int((p - 1) / r)) == 1
|
|
56
|
+
# r and q-1 must be coprimes
|
|
57
|
+
and gcd(r, q - 1) == 1
|
|
58
|
+
):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
y = random.randint(2, n)
|
|
62
|
+
if gcd(y, n) != 1:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
# to guarantee correct decryption
|
|
66
|
+
prime_factors = sympy.factorint(r).keys()
|
|
67
|
+
decryption_guaranteed = True
|
|
68
|
+
for prime_factor in prime_factors:
|
|
69
|
+
# none of r's prime factor should satisfy the condition
|
|
70
|
+
if pow(y, int(phi / prime_factor), n) == 1:
|
|
71
|
+
decryption_guaranteed = False
|
|
72
|
+
|
|
73
|
+
if decryption_guaranteed is False:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
x = pow(y, int(phi / r), n)
|
|
77
|
+
if x != 1:
|
|
78
|
+
break
|
|
79
|
+
|
|
80
|
+
keys["public_key"]["y"] = y
|
|
81
|
+
keys["public_key"]["r"] = r
|
|
82
|
+
keys["public_key"]["n"] = n
|
|
83
|
+
|
|
84
|
+
keys["private_key"]["p"] = p
|
|
85
|
+
keys["private_key"]["q"] = q
|
|
86
|
+
keys["private_key"]["phi"] = phi
|
|
87
|
+
keys["private_key"]["x"] = x
|
|
88
|
+
|
|
89
|
+
return keys
|
|
90
|
+
|
|
91
|
+
def generate_random_key(self) -> int:
|
|
92
|
+
"""
|
|
93
|
+
Generate random key for encryption
|
|
94
|
+
"""
|
|
95
|
+
n = self.keys["public_key"]["n"]
|
|
96
|
+
while True:
|
|
97
|
+
u = random.randint(1, n)
|
|
98
|
+
if gcd(u, n) == 1:
|
|
99
|
+
break
|
|
100
|
+
return u
|
|
101
|
+
|
|
102
|
+
def encrypt(self, plaintext: int, random_key: Optional[int] = None) -> int:
|
|
103
|
+
"""
|
|
104
|
+
Encrypt a given plaintext for optionally given random key with Benaloh
|
|
105
|
+
Args:
|
|
106
|
+
plaintext (int): message to encrypt
|
|
107
|
+
random_key (int): Benaloh requires a random key
|
|
108
|
+
Random key will be generated automatically if you do not set this.
|
|
109
|
+
Returns:
|
|
110
|
+
ciphertext (int): encrypted message
|
|
111
|
+
"""
|
|
112
|
+
y = self.keys["public_key"]["y"]
|
|
113
|
+
r = self.keys["public_key"]["r"]
|
|
114
|
+
n = self.keys["public_key"]["n"]
|
|
115
|
+
|
|
116
|
+
u = random_key or self.generate_random_key()
|
|
117
|
+
|
|
118
|
+
if plaintext > r:
|
|
119
|
+
plaintext = plaintext % r
|
|
120
|
+
logger.debug(
|
|
121
|
+
f"Benaloh lets you to encrypt messages in [0, {r=}]."
|
|
122
|
+
f"But your plaintext exceeds this limit."
|
|
123
|
+
f"New plaintext is {plaintext}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
c = (pow(y, plaintext, n) * pow(u, r, n)) % n
|
|
127
|
+
|
|
128
|
+
if gcd(c, n) != 1:
|
|
129
|
+
logger.debug("ciphertext is not co-prime with n!")
|
|
130
|
+
|
|
131
|
+
return c
|
|
132
|
+
|
|
133
|
+
def decrypt(self, ciphertext: int) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Decrypt a given ciphertext with Benaloh
|
|
136
|
+
Args:
|
|
137
|
+
ciphertext (int): encrypted message
|
|
138
|
+
Returns:
|
|
139
|
+
plaintext (int): restored message
|
|
140
|
+
"""
|
|
141
|
+
n = self.keys["public_key"]["n"]
|
|
142
|
+
r = self.keys["public_key"]["r"]
|
|
143
|
+
phi = self.keys["private_key"]["phi"]
|
|
144
|
+
x = self.keys["private_key"]["x"]
|
|
145
|
+
|
|
146
|
+
a = pow(ciphertext, int(phi / r), n)
|
|
147
|
+
|
|
148
|
+
md = 0
|
|
149
|
+
while True:
|
|
150
|
+
if pow(x, md, n) == a:
|
|
151
|
+
break
|
|
152
|
+
md = md + 1
|
|
153
|
+
if md > r:
|
|
154
|
+
raise ValueError(f"Message cannot be restored in [{0}, {n}]")
|
|
155
|
+
return md
|
|
156
|
+
|
|
157
|
+
def add(self, ciphertext1: int, ciphertext2: int) -> int:
|
|
158
|
+
"""
|
|
159
|
+
Perform homomorphic addition on encrypted data.
|
|
160
|
+
Result of this must be equal to E(m1 + m2)
|
|
161
|
+
Encryption calculations are done in module n
|
|
162
|
+
Args:
|
|
163
|
+
ciphertext1 (int): 1st ciphertext created with Benaloh
|
|
164
|
+
ciphertext2 (int): 2nd ciphertext created with Benaloh
|
|
165
|
+
Returns:
|
|
166
|
+
ciphertext3 (int): 3rd ciphertext created with Benaloh
|
|
167
|
+
"""
|
|
168
|
+
n = self.keys["public_key"]["n"]
|
|
169
|
+
return (ciphertext1 * ciphertext2) % n
|
|
170
|
+
|
|
171
|
+
def multiply(self, ciphertext1: int, ciphertext2: int) -> int:
|
|
172
|
+
raise ValueError("Benaloh is not homomorphic with respect to the multiplication")
|
|
173
|
+
|
|
174
|
+
def xor(self, ciphertext1: int, ciphertext2: int) -> int:
|
|
175
|
+
raise ValueError("Benaloh is not homomorphic with respect to the exclusive or")
|
|
176
|
+
|
|
177
|
+
def multiply_by_contant(self, ciphertext: int, constant: int) -> int:
|
|
178
|
+
"""
|
|
179
|
+
Multiply a ciphertext with a plain constant.
|
|
180
|
+
Result of this must be equal to E(m1 * constant) where E(m1) = ciphertext
|
|
181
|
+
Encryption calculations are done in module n squared.
|
|
182
|
+
Args:
|
|
183
|
+
ciphertext (int): ciphertext created with Benaloh
|
|
184
|
+
constant (int): known plain constant
|
|
185
|
+
Returns:
|
|
186
|
+
ciphertext (int): new ciphertext created with Benaloh
|
|
187
|
+
"""
|
|
188
|
+
# raise ValueError("Benaloh is not supporting multiplying by a constant")
|
|
189
|
+
n = self.keys["public_key"]["n"]
|
|
190
|
+
if constant > self.plaintext_modulo:
|
|
191
|
+
constant = constant % self.plaintext_modulo
|
|
192
|
+
logger.debug(
|
|
193
|
+
f"Benaloh can encrypt messages [1, {self.plaintext_modulo}]. "
|
|
194
|
+
f"Seems constant exceeded this limit. New constant is {constant}"
|
|
195
|
+
)
|
|
196
|
+
return pow(ciphertext, constant, n)
|
|
197
|
+
|
|
198
|
+
def reencrypt(self, ciphertext: int) -> int:
|
|
199
|
+
"""
|
|
200
|
+
Re-generate ciphertext with re-encryption. Many ciphertext will be decrypted to same plaintext.
|
|
201
|
+
Args:
|
|
202
|
+
ciphertext (int): given ciphertext
|
|
203
|
+
Returns:
|
|
204
|
+
new ciphertext (int): different ciphertext for same plaintext
|
|
205
|
+
"""
|
|
206
|
+
neutral_element = 0
|
|
207
|
+
neutral_encrypted = self.encrypt(plaintext=neutral_element)
|
|
208
|
+
return self.add(ciphertext1=ciphertext, ciphertext2=neutral_encrypted)
|