lightphe 0.0.23__tar.gz → 0.0.24__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.23 → lightphe-0.0.24}/PKG-INFO +2 -2
- {lightphe-0.0.23 → lightphe-0.0.24}/README.md +1 -1
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/__init__.py +5 -1
- lightphe-0.0.24/lightphe/commons/key_validator.py +97 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/Benaloh.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/BonehGohNissim.py +21 -15
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/DamgardJurik.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/ElGamal.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/EllipticCurveElGamal.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/GoldwasserMicali.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/NaccacheStern.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/OkamotoUchiyama.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/Paillier.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/RSA.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/SanderYoungYung.py +5 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Homomorphic.py +7 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/PKG-INFO +2 -2
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/SOURCES.txt +2 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/requires.txt +1 -1
- {lightphe-0.0.23 → lightphe-0.0.24}/requirements.txt +1 -1
- {lightphe-0.0.23 → lightphe-0.0.24}/setup.py +1 -1
- lightphe-0.0.24/tests/test_key_validator.py +100 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/LICENSE +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/__init__.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/logger.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/commons/phe_utils.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/cryptosystems/__init__.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Algorithm.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Ciphertext.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/Tensor.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe/models/__init__.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/dependency_links.txt +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/lightphe.egg-info/top_level.txt +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/setup.cfg +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/__init__.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_benaloh.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_bonehgohnissim.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_ciphertexts.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_cloud.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_damgard.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_elgamal.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_ellipticcurveelgamal.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_exporting_keys.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_goldwasser.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_keys.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_large_keys.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_naccache.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_okamoto.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_paillier.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_rsa.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_salary.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/tests/test_sanderyoungyung.py +0 -0
- {lightphe-0.0.23 → lightphe-0.0.24}/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.24
|
|
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
|
|
@@ -156,7 +156,7 @@ assert cs.decrypt(c3) == m1 * m2
|
|
|
156
156
|
|
|
157
157
|
### Ciphertext Regeneration
|
|
158
158
|
|
|
159
|
-
The most of
|
|
159
|
+
The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
|
|
160
160
|
|
|
161
161
|
```python
|
|
162
162
|
c1_prime = cs.regenerate_ciphertext(c1)
|
|
@@ -140,7 +140,7 @@ assert cs.decrypt(c3) == m1 * m2
|
|
|
140
140
|
|
|
141
141
|
### Ciphertext Regeneration
|
|
142
142
|
|
|
143
|
-
The most of
|
|
143
|
+
The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
|
|
144
144
|
|
|
145
145
|
```python
|
|
146
146
|
c1_prime = cs.regenerate_ciphertext(c1)
|
|
@@ -16,6 +16,7 @@ from lightphe.models.Ciphertext import Ciphertext
|
|
|
16
16
|
from lightphe.models.Algorithm import Algorithm
|
|
17
17
|
from lightphe.models.Tensor import Fraction, EncryptedTensor
|
|
18
18
|
from lightphe.commons import phe_utils
|
|
19
|
+
from lightphe.commons.key_validator import validate_keys
|
|
19
20
|
from lightphe.commons.logger import Logger
|
|
20
21
|
|
|
21
22
|
# cryptosystems
|
|
@@ -36,7 +37,7 @@ from lightphe.cryptosystems.BonehGohNissim import BonehGohNissim
|
|
|
36
37
|
|
|
37
38
|
logger = Logger(module="lightphe/__init__.py")
|
|
38
39
|
|
|
39
|
-
VERSION = "0.0.
|
|
40
|
+
VERSION = "0.0.24"
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
class LightPHE:
|
|
@@ -90,6 +91,9 @@ class LightPHE:
|
|
|
90
91
|
if key_file is not None:
|
|
91
92
|
keys = self.restore_keys(target_file=key_file)
|
|
92
93
|
|
|
94
|
+
if keys is not None:
|
|
95
|
+
validate_keys(algorithm_name=algorithm_name, keys=keys)
|
|
96
|
+
|
|
93
97
|
self.cs: Homomorphic = self.__build_cryptosystem(
|
|
94
98
|
algorithm_name=algorithm_name,
|
|
95
99
|
keys=keys,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Dict, Type
|
|
2
|
+
|
|
3
|
+
from lightphe.models.Algorithm import Algorithm
|
|
4
|
+
from lightphe.models.Homomorphic import Homomorphic
|
|
5
|
+
from lightphe.cryptosystems.RSA import RSA
|
|
6
|
+
from lightphe.cryptosystems.ElGamal import ElGamal
|
|
7
|
+
from lightphe.cryptosystems.EllipticCurveElGamal import EllipticCurveElGamal
|
|
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.SanderYoungYung import SanderYoungYung
|
|
15
|
+
from lightphe.cryptosystems.BonehGohNissim import BonehGohNissim
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Map user-facing algorithm names to the class that owns the REQUIRED_KEYS spec.
|
|
19
|
+
# The spec itself lives as a class attribute on each cryptosystem.
|
|
20
|
+
# Exponential-ElGamal reuses ElGamal's key structure.
|
|
21
|
+
_ALGORITHM_TO_CLASS: Dict[str, Type[Homomorphic]] = {
|
|
22
|
+
Algorithm.RSA: RSA,
|
|
23
|
+
Algorithm.ElGamal: ElGamal,
|
|
24
|
+
Algorithm.ExponentialElGamal: ElGamal,
|
|
25
|
+
Algorithm.EllipticCurveElGamal: EllipticCurveElGamal,
|
|
26
|
+
Algorithm.Paillier: Paillier,
|
|
27
|
+
Algorithm.DamgardJurik: DamgardJurik,
|
|
28
|
+
Algorithm.OkamotoUchiyama: OkamotoUchiyama,
|
|
29
|
+
Algorithm.Benaloh: Benaloh,
|
|
30
|
+
Algorithm.NaccacheStern: NaccacheStern,
|
|
31
|
+
Algorithm.GoldwasserMicali: GoldwasserMicali,
|
|
32
|
+
Algorithm.SanderYoungYung: SanderYoungYung,
|
|
33
|
+
Algorithm.BonehGohNissim: BonehGohNissim,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_keys(algorithm_name: str, keys: dict) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Validate that user-supplied keys carry the required sub-fields for the
|
|
40
|
+
given cryptosystem. `public_key` is always required (every cryptosystem
|
|
41
|
+
needs it for encryption); `private_key` is optional (absent in
|
|
42
|
+
encryption-only setups). Whichever is present must contain all its
|
|
43
|
+
required sub-fields.
|
|
44
|
+
|
|
45
|
+
The required-field spec is read from the cryptosystem class's
|
|
46
|
+
`REQUIRED_KEYS` attribute (a dict with "public_key", "private_key", and
|
|
47
|
+
an optional "nested" entry for dict-valued public_key sub-fields).
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
algorithm_name (str): user-facing algorithm name (e.g. "RSA")
|
|
51
|
+
keys (dict): user-supplied keys dict
|
|
52
|
+
Raises:
|
|
53
|
+
ValueError: when required fields are missing or structure is wrong
|
|
54
|
+
"""
|
|
55
|
+
cls = _ALGORITHM_TO_CLASS.get(algorithm_name)
|
|
56
|
+
if cls is None:
|
|
57
|
+
return
|
|
58
|
+
spec = cls.REQUIRED_KEYS
|
|
59
|
+
|
|
60
|
+
if not isinstance(keys, dict):
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"{algorithm_name} keys must be a dict, got {type(keys).__name__}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if "public_key" not in keys:
|
|
66
|
+
raise ValueError(f"{algorithm_name} keys must contain 'public_key'")
|
|
67
|
+
|
|
68
|
+
for kind in ("public_key", "private_key"):
|
|
69
|
+
if kind not in keys:
|
|
70
|
+
continue
|
|
71
|
+
if not isinstance(keys[kind], dict):
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"{algorithm_name} '{kind}' must be a dict, "
|
|
74
|
+
f"got {type(keys[kind]).__name__}"
|
|
75
|
+
)
|
|
76
|
+
missing = [f for f in spec[kind] if f not in keys[kind]]
|
|
77
|
+
if missing:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"{algorithm_name} '{kind}' is missing required fields: {missing}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
nested = spec.get("nested") or {}
|
|
83
|
+
for parent, required in nested.items():
|
|
84
|
+
if "public_key" not in keys or parent not in keys["public_key"]:
|
|
85
|
+
continue
|
|
86
|
+
sub = keys["public_key"][parent]
|
|
87
|
+
if not isinstance(sub, dict):
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"{algorithm_name} 'public_key.{parent}' must be a dict, "
|
|
90
|
+
f"got {type(sub).__name__}"
|
|
91
|
+
)
|
|
92
|
+
missing = [f for f in required if f not in sub]
|
|
93
|
+
if missing:
|
|
94
|
+
raise ValueError(
|
|
95
|
+
f"{algorithm_name} 'public_key.{parent}' is missing required "
|
|
96
|
+
f"fields: {missing}"
|
|
97
|
+
)
|
|
@@ -15,6 +15,11 @@ logger = Logger(module="lightphe/cryptosystems/Benaloh.py")
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Benaloh(Homomorphic):
|
|
18
|
+
REQUIRED_KEYS = {
|
|
19
|
+
"public_key": ["y", "r", "n"],
|
|
20
|
+
"private_key": ["p", "q", "phi", "x"],
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
def __init__(
|
|
19
24
|
self,
|
|
20
25
|
keys: Optional[dict] = None,
|
|
@@ -5,6 +5,7 @@ from typing import Optional, Tuple, Union
|
|
|
5
5
|
|
|
6
6
|
# third-party dependencies
|
|
7
7
|
from lightecc import LightECC
|
|
8
|
+
from lightecc.commons.errors import InvalidCurveOrder, PointNotOnCurve
|
|
8
9
|
from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
|
|
9
10
|
from lightecc.commons.pairing import _fp2_pow, _fp2_mul
|
|
10
11
|
import sympy
|
|
@@ -25,6 +26,12 @@ class BonehGohNissim(Homomorphic):
|
|
|
25
26
|
Ref: https://sefiks.com/2026/04/02/a-step-by-step-somewhat-homomorphic-encryption-example-with-boneh-goh-nissim-in-python/
|
|
26
27
|
"""
|
|
27
28
|
|
|
29
|
+
REQUIRED_KEYS = {
|
|
30
|
+
"public_key": ["curve", "G", "u", "h", "l"],
|
|
31
|
+
"private_key": ["q1", "q2"],
|
|
32
|
+
"nested": {"curve": ["a", "b", "p", "G", "n"]},
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
def __init__(
|
|
29
36
|
self,
|
|
30
37
|
keys: Optional[dict] = None,
|
|
@@ -106,22 +113,21 @@ class BonehGohNissim(Homomorphic):
|
|
|
106
113
|
|
|
107
114
|
G = self.__find_generator(p, max_tries=max_tries)
|
|
108
115
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
logger.debug("nG is the point at infinity, as expected.")
|
|
121
|
-
logger.debug(f"q1 = {q1}, q2 = {q2}, n = {n}, p = {p}, l = {l}")
|
|
122
|
-
break
|
|
116
|
+
try:
|
|
117
|
+
self.ec = LightECC(
|
|
118
|
+
form_name="weierstrass",
|
|
119
|
+
curve_name="custom",
|
|
120
|
+
config={"a": a, "b": b, "p": p, "G": G, "n": n},
|
|
121
|
+
)
|
|
122
|
+
except (InvalidCurveOrder, PointNotOnCurve):
|
|
123
|
+
logger.debug(
|
|
124
|
+
f"Curve order validation failed for attempt {key_attempt + 1}, retrying"
|
|
125
|
+
)
|
|
126
|
+
continue
|
|
123
127
|
|
|
124
|
-
logger.debug(
|
|
128
|
+
logger.debug("Curve order validated by LightECC.")
|
|
129
|
+
logger.debug(f"q1 = {q1}, q2 = {q2}, n = {n}, p = {p}, l = {l}")
|
|
130
|
+
break
|
|
125
131
|
else:
|
|
126
132
|
raise Exception(f"Failed to generate keys after {max_tries} attempts")
|
|
127
133
|
|
|
@@ -15,6 +15,11 @@ class DamgardJurik(Homomorphic):
|
|
|
15
15
|
Ref: https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-damgard-jurik-in-python/
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
REQUIRED_KEYS = {
|
|
19
|
+
"public_key": ["g", "n", "s"],
|
|
20
|
+
"private_key": ["phi"],
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
def __init__(self, s: int = 2, keys: Optional[dict] = None, key_size: Optional[int] = None):
|
|
19
24
|
"""
|
|
20
25
|
Args:
|
|
@@ -19,6 +19,11 @@ class ElGamal(Homomorphic):
|
|
|
19
19
|
Ref: https://sefiks.com/2023/03/27/a-step-by-step-partially-homomorphic-encryption-example-with-elgamal-in-python/
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
REQUIRED_KEYS = {
|
|
23
|
+
"public_key": ["p", "g", "y"],
|
|
24
|
+
"private_key": ["x"],
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
def __init__(self, keys: Optional[dict] = None, exponential=False, key_size: Optional[int] = None):
|
|
23
28
|
"""
|
|
24
29
|
Args:
|
|
@@ -22,6 +22,11 @@ class EllipticCurveElGamal(Homomorphic):
|
|
|
22
22
|
Ref: https://sefiks.com/2018/08/21/elliptic-curve-elgamal-encryption/
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
REQUIRED_KEYS = {
|
|
26
|
+
"public_key": ["Qa"],
|
|
27
|
+
"private_key": ["ka"],
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
def __init__(
|
|
26
31
|
self,
|
|
27
32
|
keys: Optional[dict] = None,
|
|
@@ -23,6 +23,11 @@ class GoldwasserMicali(Homomorphic):
|
|
|
23
23
|
Ref: sefiks.com/2023/10/27/a-step-by-step-partially-homomorphic-encryption-example-with-goldwasser-micali-in-python/
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
+
REQUIRED_KEYS = {
|
|
27
|
+
"public_key": ["n", "x"],
|
|
28
|
+
"private_key": ["p", "q"],
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
def __init__(
|
|
27
32
|
self,
|
|
28
33
|
keys: Optional[dict] = None,
|
|
@@ -20,6 +20,11 @@ class NaccacheStern(Homomorphic):
|
|
|
20
20
|
Original paper: https://dl.acm.org/doi/pdf/10.1145/288090.288106
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
+
REQUIRED_KEYS = {
|
|
24
|
+
"public_key": ["n", "g", "sigma"],
|
|
25
|
+
"private_key": ["a", "b", "p", "q", "phi", "prime_set"],
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
def __init__(
|
|
24
29
|
self,
|
|
25
30
|
keys: Optional[dict] = None,
|
|
@@ -14,6 +14,11 @@ class OkamotoUchiyama(Homomorphic):
|
|
|
14
14
|
Ref: https://sefiks.com/2023/10/20/a-step-by-step-partially-homomorphic-encryption-example-with-okamoto-uchiyama-in-python/
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
REQUIRED_KEYS = {
|
|
18
|
+
"public_key": ["n", "g", "h"],
|
|
19
|
+
"private_key": ["p", "q"],
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
|
|
18
23
|
"""
|
|
19
24
|
Args:
|
|
@@ -15,6 +15,11 @@ class Paillier(Homomorphic):
|
|
|
15
15
|
Ref: https://sefiks.com/2023/04/03/a-step-by-step-partially-homomorphic-encryption-example-with-paillier-in-python/
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
REQUIRED_KEYS = {
|
|
19
|
+
"public_key": ["g", "n"],
|
|
20
|
+
"private_key": ["phi"],
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
def __init__(self, keys: Optional[dict] = None, key_size: Optional[int] = None):
|
|
19
24
|
"""
|
|
20
25
|
Args:
|
|
@@ -14,6 +14,11 @@ class RSA(Homomorphic):
|
|
|
14
14
|
Ref: https://sefiks.com/2023/03/06/a-step-by-step-partially-homomorphic-encryption-example-with-rsa-in-python/
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
REQUIRED_KEYS = {
|
|
18
|
+
"public_key": ["n", "e"],
|
|
19
|
+
"private_key": ["d"],
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
def __init__(
|
|
18
23
|
self,
|
|
19
24
|
keys: Optional[dict] = None,
|
|
@@ -21,6 +21,11 @@ class SanderYoungYung(Homomorphic):
|
|
|
21
21
|
Ref: https://sefiks.com/2026/04/02/a-step-by-step-partially-homomorphic-sander-young-yung-example-in-python/
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
REQUIRED_KEYS = {
|
|
25
|
+
"public_key": ["n", "x", "l"],
|
|
26
|
+
"private_key": ["p", "q"],
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
def __init__(
|
|
25
30
|
self,
|
|
26
31
|
keys: Optional[dict] = None,
|
|
@@ -17,6 +17,13 @@ class Homomorphic(ABC):
|
|
|
17
17
|
plaintext_modulo: int
|
|
18
18
|
ciphertext_modulo: int
|
|
19
19
|
|
|
20
|
+
# Cryptosystems must define REQUIRED_KEYS as a dict with:
|
|
21
|
+
# - "public_key": list of required sub-fields in keys["public_key"]
|
|
22
|
+
# - "private_key": list of required sub-fields in keys["private_key"]
|
|
23
|
+
# - "nested" (optional): dict mapping a public_key sub-field that itself
|
|
24
|
+
# holds a dict to its required sub-fields.
|
|
25
|
+
REQUIRED_KEYS: dict
|
|
26
|
+
|
|
20
27
|
@abstractmethod
|
|
21
28
|
def generate_keys(
|
|
22
29
|
self,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lightphe
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.24
|
|
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
|
|
@@ -156,7 +156,7 @@ assert cs.decrypt(c3) == m1 * m2
|
|
|
156
156
|
|
|
157
157
|
### Ciphertext Regeneration
|
|
158
158
|
|
|
159
|
-
The most of
|
|
159
|
+
The most of homomorphic algorithms allow you to regenerate ciphertext while you are not breaking its plaintext restoration. You may consider to do this re-generation many times to have stronger ciphertexts.
|
|
160
160
|
|
|
161
161
|
```python
|
|
162
162
|
c1_prime = cs.regenerate_ciphertext(c1)
|
|
@@ -9,6 +9,7 @@ lightphe.egg-info/dependency_links.txt
|
|
|
9
9
|
lightphe.egg-info/requires.txt
|
|
10
10
|
lightphe.egg-info/top_level.txt
|
|
11
11
|
lightphe/commons/__init__.py
|
|
12
|
+
lightphe/commons/key_validator.py
|
|
12
13
|
lightphe/commons/logger.py
|
|
13
14
|
lightphe/commons/phe_utils.py
|
|
14
15
|
lightphe/cryptosystems/Benaloh.py
|
|
@@ -38,6 +39,7 @@ tests/test_elgamal.py
|
|
|
38
39
|
tests/test_ellipticcurveelgamal.py
|
|
39
40
|
tests/test_exporting_keys.py
|
|
40
41
|
tests/test_goldwasser.py
|
|
42
|
+
tests/test_key_validator.py
|
|
41
43
|
tests/test_keys.py
|
|
42
44
|
tests/test_large_keys.py
|
|
43
45
|
tests/test_naccache.py
|
|
@@ -8,7 +8,7 @@ with open("requirements.txt", "r", encoding="utf-8") as f:
|
|
|
8
8
|
|
|
9
9
|
setuptools.setup(
|
|
10
10
|
name="lightphe",
|
|
11
|
-
version="0.0.
|
|
11
|
+
version="0.0.24",
|
|
12
12
|
author="Sefik Ilkin Serengil",
|
|
13
13
|
author_email="serengil@gmail.com",
|
|
14
14
|
description="A Lightweight Partially Homomorphic Encryption Library for Python",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from lightphe import LightPHE
|
|
6
|
+
from lightphe.commons.logger import Logger
|
|
7
|
+
|
|
8
|
+
logger = Logger(module="tests/test_key_validator.py")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
ALGORITHMS = [
|
|
12
|
+
("RSA", 50),
|
|
13
|
+
("ElGamal", 50),
|
|
14
|
+
("Exponential-ElGamal", 50),
|
|
15
|
+
("EllipticCurve-ElGamal", 50),
|
|
16
|
+
("Paillier", 50),
|
|
17
|
+
("Damgard-Jurik", 50),
|
|
18
|
+
("Okamoto-Uchiyama", 50),
|
|
19
|
+
("Benaloh", 50),
|
|
20
|
+
("Naccache-Stern", 50),
|
|
21
|
+
("Goldwasser-Micali", 50),
|
|
22
|
+
("Sander-Young-Yung", 50),
|
|
23
|
+
("Boneh-Goh-Nissim", 50),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("algorithm_name,key_size", ALGORITHMS)
|
|
28
|
+
def test_missing_public_key_field_is_rejected(algorithm_name, key_size):
|
|
29
|
+
"""Dropping a required public_key sub-field must raise ValueError."""
|
|
30
|
+
cs = LightPHE(algorithm_name=algorithm_name, key_size=key_size, max_tries=10000)
|
|
31
|
+
field = cs.cs.REQUIRED_KEYS["public_key"][0]
|
|
32
|
+
|
|
33
|
+
broken = copy.deepcopy(cs.cs.keys)
|
|
34
|
+
del broken["public_key"][field]
|
|
35
|
+
|
|
36
|
+
with pytest.raises(ValueError, match=f"missing required fields.*{field}"):
|
|
37
|
+
LightPHE(algorithm_name=algorithm_name, keys=broken)
|
|
38
|
+
|
|
39
|
+
logger.info(f"✅ {algorithm_name}: missing public_key.{field} rejected")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize("algorithm_name,key_size", ALGORITHMS)
|
|
43
|
+
def test_missing_private_key_field_is_rejected(algorithm_name, key_size):
|
|
44
|
+
"""Dropping a required private_key sub-field must raise ValueError."""
|
|
45
|
+
cs = LightPHE(algorithm_name=algorithm_name, key_size=key_size, max_tries=10000)
|
|
46
|
+
field = cs.cs.REQUIRED_KEYS["private_key"][0]
|
|
47
|
+
|
|
48
|
+
broken = copy.deepcopy(cs.cs.keys)
|
|
49
|
+
del broken["private_key"][field]
|
|
50
|
+
|
|
51
|
+
with pytest.raises(ValueError, match=f"missing required fields.*{field}"):
|
|
52
|
+
LightPHE(algorithm_name=algorithm_name, keys=broken)
|
|
53
|
+
|
|
54
|
+
logger.info(f"✅ {algorithm_name}: missing private_key.{field} rejected")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_missing_public_key_dict_is_rejected():
|
|
58
|
+
"""A keys dict without public_key must be rejected."""
|
|
59
|
+
with pytest.raises(ValueError, match="must contain 'public_key'"):
|
|
60
|
+
LightPHE(algorithm_name="RSA", keys={})
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_private_key_only_is_rejected():
|
|
64
|
+
"""Supplying private_key without public_key must be rejected."""
|
|
65
|
+
cs = LightPHE(algorithm_name="RSA", key_size=50)
|
|
66
|
+
private_only = {"private_key": copy.deepcopy(cs.cs.keys["private_key"])}
|
|
67
|
+
with pytest.raises(ValueError, match="must contain 'public_key'"):
|
|
68
|
+
LightPHE(algorithm_name="RSA", keys=private_only)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_non_dict_keys_is_rejected():
|
|
72
|
+
"""A non-dict `keys` argument must be rejected."""
|
|
73
|
+
with pytest.raises(ValueError, match="keys must be a dict"):
|
|
74
|
+
LightPHE(algorithm_name="RSA", keys="not-a-dict")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_public_key_only_is_accepted():
|
|
78
|
+
"""Supplying only a full public_key must be accepted (encryption-only use)."""
|
|
79
|
+
cs = LightPHE(algorithm_name="RSA", key_size=50)
|
|
80
|
+
public_only = {"public_key": copy.deepcopy(cs.cs.keys["public_key"])}
|
|
81
|
+
# should not raise
|
|
82
|
+
LightPHE(algorithm_name="RSA", keys=public_only)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_bgn_nested_curve_field_is_rejected():
|
|
86
|
+
"""Dropping a required BGN curve sub-field must be rejected."""
|
|
87
|
+
cs = LightPHE(algorithm_name="Boneh-Goh-Nissim", key_size=50, max_tries=10000)
|
|
88
|
+
nested = cs.cs.REQUIRED_KEYS.get("nested", {}).get("curve", [])
|
|
89
|
+
assert nested, "BGN must declare nested curve required fields"
|
|
90
|
+
field = nested[0]
|
|
91
|
+
|
|
92
|
+
broken = copy.deepcopy(cs.cs.keys)
|
|
93
|
+
del broken["public_key"]["curve"][field]
|
|
94
|
+
|
|
95
|
+
with pytest.raises(
|
|
96
|
+
ValueError, match=rf"public_key\.curve.*missing required fields.*{field}"
|
|
97
|
+
):
|
|
98
|
+
LightPHE(algorithm_name="Boneh-Goh-Nissim", keys=broken)
|
|
99
|
+
|
|
100
|
+
logger.info(f"✅ BGN: missing public_key.curve.{field} rejected")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|