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.
Files changed (43) hide show
  1. {lightphe-0.0.1 → lightphe-0.0.2}/PKG-INFO +17 -7
  2. {lightphe-0.0.1 → lightphe-0.0.2}/README.md +16 -6
  3. lightphe-0.0.2/lightphe/__init__.py +219 -0
  4. lightphe-0.0.2/lightphe/commons/__init__.py +0 -0
  5. lightphe-0.0.2/lightphe/commons/logger.py +39 -0
  6. lightphe-0.0.2/lightphe/cryptosystems/Benaloh.py +208 -0
  7. lightphe-0.0.2/lightphe/cryptosystems/DamgardJurik.py +177 -0
  8. lightphe-0.0.2/lightphe/cryptosystems/ElGamal.py +202 -0
  9. lightphe-0.0.2/lightphe/cryptosystems/EllipticCurveElGamal.py +168 -0
  10. lightphe-0.0.2/lightphe/cryptosystems/GoldwasserMicali.py +166 -0
  11. lightphe-0.0.2/lightphe/cryptosystems/NaccacheStern.py +301 -0
  12. lightphe-0.0.2/lightphe/cryptosystems/OkamotoUchiyama.py +180 -0
  13. lightphe-0.0.2/lightphe/cryptosystems/Paillier.py +166 -0
  14. lightphe-0.0.2/lightphe/cryptosystems/RSA.py +142 -0
  15. lightphe-0.0.2/lightphe/cryptosystems/__init__.py +0 -0
  16. lightphe-0.0.2/lightphe/elliptic/Weierstrass.py +97 -0
  17. lightphe-0.0.2/lightphe/elliptic/__init__.py +0 -0
  18. lightphe-0.0.2/lightphe/models/Algorithm.py +12 -0
  19. lightphe-0.0.2/lightphe/models/Ciphertext.py +138 -0
  20. lightphe-0.0.2/lightphe/models/EllipticCurve.py +18 -0
  21. lightphe-0.0.2/lightphe/models/Homomorphic.py +53 -0
  22. lightphe-0.0.2/lightphe/models/__init__.py +0 -0
  23. {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/PKG-INFO +17 -7
  24. lightphe-0.0.2/lightphe.egg-info/SOURCES.txt +39 -0
  25. lightphe-0.0.2/lightphe.egg-info/top_level.txt +2 -0
  26. {lightphe-0.0.1 → lightphe-0.0.2}/setup.py +1 -1
  27. lightphe-0.0.2/tests/__init__.py +219 -0
  28. lightphe-0.0.2/tests/test_benaloh.py +63 -0
  29. lightphe-0.0.2/tests/test_damgard.py +63 -0
  30. lightphe-0.0.2/tests/test_elgamal.py +139 -0
  31. lightphe-0.0.2/tests/test_ellipticcurveelgamal.py +64 -0
  32. lightphe-0.0.2/tests/test_goldwasser.py +64 -0
  33. lightphe-0.0.2/tests/test_naccache.py +94 -0
  34. lightphe-0.0.2/tests/test_okamoto.py +61 -0
  35. lightphe-0.0.2/tests/test_paillier.py +97 -0
  36. lightphe-0.0.2/tests/test_rsa.py +65 -0
  37. lightphe-0.0.2/tests/test_salary.py +53 -0
  38. lightphe-0.0.1/lightphe.egg-info/SOURCES.txt +0 -8
  39. lightphe-0.0.1/lightphe.egg-info/top_level.txt +0 -1
  40. {lightphe-0.0.1 → lightphe-0.0.2}/LICENSE +0 -0
  41. {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/dependency_links.txt +0 -0
  42. {lightphe-0.0.1 → lightphe-0.0.2}/lightphe.egg-info/requires.txt +0 -0
  43. {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.1
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 [![PyPI](https://img.shields.io/pypi/v/lightphe.svg)](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 = 5
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 `LightPHE` in the `requirements.txt`.
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 [![PyPI](https://img.shields.io/pypi/v/lightphe.svg)](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 = 5
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 `LightPHE` in the `requirements.txt`.
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)