transcrypto 1.5.1__py3-none-any.whl → 1.7.0__py3-none-any.whl
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.
- transcrypto/__init__.py +7 -0
- transcrypto/aes.py +150 -44
- transcrypto/base.py +640 -411
- transcrypto/constants.py +20070 -1906
- transcrypto/dsa.py +132 -99
- transcrypto/elgamal.py +116 -84
- transcrypto/modmath.py +88 -78
- transcrypto/profiler.py +228 -180
- transcrypto/rsa.py +126 -90
- transcrypto/sss.py +122 -70
- transcrypto/transcrypto.py +2362 -1412
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/METADATA +78 -58
- transcrypto-1.7.0.dist-info/RECORD +17 -0
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/WHEEL +1 -2
- transcrypto-1.7.0.dist-info/entry_points.txt +4 -0
- transcrypto/safetrans.py +0 -1231
- transcrypto-1.5.1.dist-info/RECORD +0 -18
- transcrypto-1.5.1.dist-info/top_level.txt +0 -1
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/licenses/LICENSE +0 -0
transcrypto/elgamal.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
|
|
4
|
-
#
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
3
|
"""Balparda's TransCrypto El-Gamal library.
|
|
6
4
|
|
|
7
5
|
<https://en.wikipedia.org/wiki/ElGamal_encryption>
|
|
@@ -20,17 +18,11 @@ from __future__ import annotations
|
|
|
20
18
|
|
|
21
19
|
import dataclasses
|
|
22
20
|
import logging
|
|
23
|
-
# import pdb
|
|
24
21
|
from typing import Self
|
|
25
22
|
|
|
26
|
-
import gmpy2
|
|
27
|
-
|
|
28
|
-
from . import base, modmath, aes
|
|
29
|
-
|
|
30
|
-
__author__ = 'balparda@github.com'
|
|
31
|
-
__version__: str = base.__version__ # version comes from base!
|
|
32
|
-
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
23
|
+
import gmpy2
|
|
33
24
|
|
|
25
|
+
from . import aes, base, modmath
|
|
34
26
|
|
|
35
27
|
_MAX_KEY_GENERATION_FAILURES = 15
|
|
36
28
|
|
|
@@ -48,6 +40,7 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
48
40
|
Attributes:
|
|
49
41
|
prime_modulus (int): prime modulus, ≥ 7
|
|
50
42
|
group_base (int): shared encryption group public base, 3 ≤ g < prime_modulus
|
|
43
|
+
|
|
51
44
|
"""
|
|
52
45
|
|
|
53
46
|
prime_modulus: int
|
|
@@ -58,11 +51,11 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
58
51
|
|
|
59
52
|
Raises:
|
|
60
53
|
InputError: invalid inputs
|
|
54
|
+
|
|
61
55
|
"""
|
|
62
|
-
|
|
63
|
-
if self.prime_modulus < 7 or not modmath.IsPrime(self.prime_modulus):
|
|
56
|
+
if self.prime_modulus < 7 or not modmath.IsPrime(self.prime_modulus): # noqa: PLR2004
|
|
64
57
|
raise base.InputError(f'invalid prime_modulus: {self}')
|
|
65
|
-
if not 2 < self.group_base < self.prime_modulus - 1:
|
|
58
|
+
if not 2 < self.group_base < self.prime_modulus - 1: # noqa: PLR2004
|
|
66
59
|
raise base.InputError(f'invalid group_base: {self}')
|
|
67
60
|
|
|
68
61
|
def __str__(self) -> str:
|
|
@@ -70,11 +63,14 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
70
63
|
|
|
71
64
|
Returns:
|
|
72
65
|
string representation of ElGamalSharedPublicKey
|
|
66
|
+
|
|
73
67
|
"""
|
|
74
|
-
return (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
return (
|
|
69
|
+
'ElGamalSharedPublicKey('
|
|
70
|
+
f'bits={self.prime_modulus.bit_length()}, '
|
|
71
|
+
f'prime_modulus={base.IntToEncoded(self.prime_modulus)}, '
|
|
72
|
+
f'group_base={base.IntToEncoded(self.group_base)})'
|
|
73
|
+
)
|
|
78
74
|
|
|
79
75
|
@property
|
|
80
76
|
def modulus_size(self) -> int:
|
|
@@ -82,7 +78,8 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
82
78
|
return (self.prime_modulus.bit_length() + 7) // 8
|
|
83
79
|
|
|
84
80
|
def _DomainSeparatedHash(
|
|
85
|
-
|
|
81
|
+
self, message: bytes, associated_data: bytes | None, salt: bytes, /
|
|
82
|
+
) -> int:
|
|
86
83
|
"""Compute the domain-separated hash for signing and verifying.
|
|
87
84
|
|
|
88
85
|
Args:
|
|
@@ -96,12 +93,14 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
96
93
|
|
|
97
94
|
Raises:
|
|
98
95
|
CryptoError: hash output is out of range
|
|
96
|
+
|
|
99
97
|
"""
|
|
100
98
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
101
99
|
la: bytes = base.IntToFixedBytes(len(aad), 8)
|
|
102
|
-
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes'
|
|
100
|
+
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes' # noqa: PLR2004, S101
|
|
103
101
|
y: int = base.BytesToInt(
|
|
104
|
-
|
|
102
|
+
base.Hash512(_ELGAMAL_SIGNATURE_HASH_PREFIX + la + aad + message + salt)
|
|
103
|
+
)
|
|
105
104
|
if not 1 < y < self.prime_modulus:
|
|
106
105
|
# will only reasonably happen if modulus is small
|
|
107
106
|
raise base.CryptoError(f'hash output {y} is out of range/invalid {self.prime_modulus}')
|
|
@@ -119,14 +118,15 @@ class ElGamalSharedPublicKey(base.CryptoKey):
|
|
|
119
118
|
|
|
120
119
|
Raises:
|
|
121
120
|
InputError: invalid inputs
|
|
121
|
+
|
|
122
122
|
"""
|
|
123
123
|
# test inputs
|
|
124
|
-
if bit_length < 11:
|
|
124
|
+
if bit_length < 11: # noqa: PLR2004
|
|
125
125
|
raise base.InputError(f'invalid bit length: {bit_length=}')
|
|
126
126
|
# generate random prime and number, create object (should never fail)
|
|
127
127
|
p: int = modmath.NBitRandomPrimes(bit_length).pop()
|
|
128
128
|
g: int = 0
|
|
129
|
-
while not 2 < g < p
|
|
129
|
+
while not 2 < g < p: # noqa: PLR2004
|
|
130
130
|
g = base.RandBits(bit_length)
|
|
131
131
|
return cls(prime_modulus=p, group_base=g)
|
|
132
132
|
|
|
@@ -139,6 +139,7 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
139
139
|
|
|
140
140
|
Attributes:
|
|
141
141
|
individual_base (int): individual encryption public base, 3 ≤ i < prime_modulus
|
|
142
|
+
|
|
142
143
|
"""
|
|
143
144
|
|
|
144
145
|
individual_base: int
|
|
@@ -148,10 +149,13 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
148
149
|
|
|
149
150
|
Raises:
|
|
150
151
|
InputError: invalid inputs
|
|
152
|
+
|
|
151
153
|
"""
|
|
152
|
-
super(ElGamalPublicKey, self).__post_init__()
|
|
153
|
-
if (
|
|
154
|
-
|
|
154
|
+
super(ElGamalPublicKey, self).__post_init__()
|
|
155
|
+
if (
|
|
156
|
+
not 2 < self.individual_base < self.prime_modulus - 1 # noqa: PLR2004
|
|
157
|
+
or self.individual_base == self.group_base
|
|
158
|
+
):
|
|
155
159
|
raise base.InputError(f'invalid individual_base: {self}')
|
|
156
160
|
|
|
157
161
|
def __str__(self) -> str:
|
|
@@ -159,23 +163,29 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
159
163
|
|
|
160
164
|
Returns:
|
|
161
165
|
string representation of ElGamalPublicKey
|
|
166
|
+
|
|
162
167
|
"""
|
|
163
|
-
return (
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
return (
|
|
169
|
+
'ElGamalPublicKey('
|
|
170
|
+
f'{super(ElGamalPublicKey, self).__str__()}, '
|
|
171
|
+
f'individual_base={base.IntToEncoded(self.individual_base)})'
|
|
172
|
+
)
|
|
166
173
|
|
|
167
174
|
def _MakeEphemeralKey(self) -> tuple[int, int]:
|
|
168
175
|
"""Make an ephemeral key adequate to be used with El-Gamal.
|
|
169
176
|
|
|
170
177
|
Returns:
|
|
171
|
-
(key, key_inverse), where 2 ≤ k < modulus
|
|
178
|
+
(key, key_inverse), where 2 ≤ k < modulus and
|
|
172
179
|
GCD(k, modulus - 1) == 1 and (k*i) % (p-1) == 1
|
|
180
|
+
|
|
173
181
|
"""
|
|
174
182
|
ephemeral_key: int = 0
|
|
175
183
|
p_1: int = self.prime_modulus - 1
|
|
176
184
|
bit_length: int = self.prime_modulus.bit_length()
|
|
177
|
-
while
|
|
178
|
-
|
|
185
|
+
while not 1 < ephemeral_key < self.prime_modulus or ephemeral_key in {
|
|
186
|
+
self.group_base,
|
|
187
|
+
self.individual_base,
|
|
188
|
+
}:
|
|
179
189
|
ephemeral_key = base.RandBits(bit_length)
|
|
180
190
|
if base.GCD(ephemeral_key, p_1) != 1:
|
|
181
191
|
ephemeral_key = 0 # we have to try again
|
|
@@ -196,6 +206,7 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
196
206
|
|
|
197
207
|
Raises:
|
|
198
208
|
InputError: invalid inputs
|
|
209
|
+
|
|
199
210
|
"""
|
|
200
211
|
# test inputs
|
|
201
212
|
if not 0 < message < self.prime_modulus:
|
|
@@ -203,10 +214,10 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
203
214
|
# encrypt
|
|
204
215
|
a: int = 0
|
|
205
216
|
b: int = 0
|
|
206
|
-
while a < 2 or b < 2:
|
|
217
|
+
while a < 2 or b < 2: # noqa: PLR2004
|
|
207
218
|
ephemeral_key: int = self._MakeEphemeralKey()[0]
|
|
208
|
-
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus))
|
|
209
|
-
s: int = int(gmpy2.powmod(self.individual_base, ephemeral_key, self.prime_modulus))
|
|
219
|
+
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus))
|
|
220
|
+
s: int = int(gmpy2.powmod(self.individual_base, ephemeral_key, self.prime_modulus))
|
|
210
221
|
b = (message * s) % self.prime_modulus
|
|
211
222
|
return (a, b)
|
|
212
223
|
|
|
@@ -237,23 +248,19 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
237
248
|
associated_data="prefix" + len(aad) + aad +
|
|
238
249
|
Padded(ct1, k) + Padded(ct2, k))
|
|
239
250
|
|
|
240
|
-
Raises:
|
|
241
|
-
InputError: invalid inputs
|
|
242
|
-
CryptoError: internal crypto failures
|
|
243
251
|
"""
|
|
244
252
|
# generate random r and encrypt it
|
|
245
253
|
r: int = 0
|
|
246
|
-
while not 1 < r < self.prime_modulus
|
|
254
|
+
while not 1 < r < self.prime_modulus:
|
|
247
255
|
r = base.RandBits(self.prime_modulus.bit_length())
|
|
248
256
|
k: int = self.modulus_size
|
|
249
257
|
i_ct: tuple[int, int] = self.RawEncrypt(r)
|
|
250
258
|
ct: bytes = base.IntToFixedBytes(i_ct[0], k) + base.IntToFixedBytes(i_ct[1], k)
|
|
251
|
-
assert len(ct) == 2 * k, 'should never happen: c_kem should be exactly 2k bytes'
|
|
259
|
+
assert len(ct) == 2 * k, 'should never happen: c_kem should be exactly 2k bytes' # noqa: S101
|
|
252
260
|
# encrypt plaintext with AES-256-GCM using SHA512(r)[32:] as key; return ct || Encrypt(...)
|
|
253
261
|
ss: bytes = base.Hash512(base.IntToFixedBytes(r, k))
|
|
254
262
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
255
|
-
aad_prime: bytes = (
|
|
256
|
-
_ELGAMAL_ENCRYPTION_AAD_PREFIX + base.IntToFixedBytes(len(aad), 8) + aad + ct)
|
|
263
|
+
aad_prime: bytes = _ELGAMAL_ENCRYPTION_AAD_PREFIX + base.IntToFixedBytes(len(aad), 8) + aad + ct
|
|
257
264
|
return ct + aes.AESKey(key256=ss[32:]).Encrypt(plaintext, associated_data=aad_prime)
|
|
258
265
|
|
|
259
266
|
def RawVerify(self, message: int, signature: tuple[int, int], /) -> bool:
|
|
@@ -272,21 +279,22 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
272
279
|
|
|
273
280
|
Raises:
|
|
274
281
|
InputError: invalid inputs
|
|
282
|
+
|
|
275
283
|
"""
|
|
276
284
|
# test inputs
|
|
277
285
|
if not 0 < message < self.prime_modulus:
|
|
278
286
|
raise base.InputError(f'invalid message: {message=}')
|
|
279
|
-
if
|
|
280
|
-
not 2 <= signature[1] < self.prime_modulus - 1):
|
|
287
|
+
if not 2 <= signature[0] < self.prime_modulus or not 2 <= signature[1] < self.prime_modulus - 1: # noqa: PLR2004
|
|
281
288
|
raise base.InputError(f'invalid signature: {signature=}')
|
|
282
289
|
# verify
|
|
283
|
-
a: int = int(gmpy2.powmod(self.group_base, message, self.prime_modulus))
|
|
284
|
-
b: int = int(gmpy2.powmod(signature[0], signature[1], self.prime_modulus))
|
|
285
|
-
c: int = int(gmpy2.powmod(self.individual_base, signature[0], self.prime_modulus))
|
|
290
|
+
a: int = int(gmpy2.powmod(self.group_base, message, self.prime_modulus))
|
|
291
|
+
b: int = int(gmpy2.powmod(signature[0], signature[1], self.prime_modulus))
|
|
292
|
+
c: int = int(gmpy2.powmod(self.individual_base, signature[0], self.prime_modulus))
|
|
286
293
|
return a == (b * c) % self.prime_modulus
|
|
287
294
|
|
|
288
295
|
def Verify(
|
|
289
|
-
|
|
296
|
+
self, message: bytes, signature: bytes, /, *, associated_data: bytes | None = None
|
|
297
|
+
) -> bool:
|
|
290
298
|
"""Verify a `signature` for `message`. True if OK; False if failed verification.
|
|
291
299
|
|
|
292
300
|
• Let k = ceil(log2(n))/8 be the modulus size in bytes.
|
|
@@ -305,39 +313,50 @@ class ElGamalPublicKey(ElGamalSharedPublicKey, base.Encryptor, base.Verifier):
|
|
|
305
313
|
|
|
306
314
|
Raises:
|
|
307
315
|
InputError: invalid inputs
|
|
308
|
-
|
|
316
|
+
|
|
309
317
|
"""
|
|
310
318
|
k: int = self.modulus_size
|
|
311
|
-
if k <= 64:
|
|
319
|
+
if k <= 64: # noqa: PLR2004
|
|
312
320
|
raise base.InputError(f'modulus too small for signing operations: {k} bytes')
|
|
313
321
|
if len(signature) != (64 + k + k):
|
|
314
322
|
logging.info(f'invalid signature length: {len(signature)} ; expected {64 + k + k}')
|
|
315
323
|
return False
|
|
316
324
|
try:
|
|
317
325
|
return self.RawVerify(
|
|
318
|
-
|
|
319
|
-
|
|
326
|
+
self._DomainSeparatedHash(message, associated_data, signature[:64]),
|
|
327
|
+
(base.BytesToInt(signature[64 : 64 + k]), base.BytesToInt(signature[64 + k :])),
|
|
328
|
+
)
|
|
320
329
|
except base.InputError as err:
|
|
321
330
|
logging.info(err)
|
|
322
331
|
return False
|
|
323
332
|
|
|
324
333
|
@classmethod
|
|
325
334
|
def Copy(cls, other: ElGamalPublicKey, /) -> Self:
|
|
326
|
-
"""Initialize a public key by taking the public parts of a public/private key.
|
|
335
|
+
"""Initialize a public key by taking the public parts of a public/private key.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
other (ElGamalPublicKey): object to copy from
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Self: a new ElGamalPublicKey
|
|
342
|
+
|
|
343
|
+
"""
|
|
327
344
|
return cls(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
345
|
+
prime_modulus=other.prime_modulus,
|
|
346
|
+
group_base=other.group_base,
|
|
347
|
+
individual_base=other.individual_base,
|
|
348
|
+
)
|
|
331
349
|
|
|
332
350
|
|
|
333
351
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
334
|
-
class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer):
|
|
352
|
+
class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer):
|
|
335
353
|
"""El-Gamal private key.
|
|
336
354
|
|
|
337
355
|
BEWARE: This is **NOT** DSA! No measures are taken here to prevent timing attacks.
|
|
338
356
|
|
|
339
357
|
Attributes:
|
|
340
358
|
decrypt_exp (int): individual decryption exponent, 3 ≤ i < prime_modulus
|
|
359
|
+
|
|
341
360
|
"""
|
|
342
361
|
|
|
343
362
|
decrypt_exp: int
|
|
@@ -348,12 +367,15 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
348
367
|
Raises:
|
|
349
368
|
InputError: invalid inputs
|
|
350
369
|
CryptoError: modulus math is inconsistent with values
|
|
370
|
+
|
|
351
371
|
"""
|
|
352
|
-
super(ElGamalPrivateKey, self).__post_init__()
|
|
353
|
-
if
|
|
354
|
-
|
|
372
|
+
super(ElGamalPrivateKey, self).__post_init__()
|
|
373
|
+
if not 2 < self.decrypt_exp < self.prime_modulus - 1 or self.decrypt_exp in { # noqa: PLR2004
|
|
374
|
+
self.group_base,
|
|
375
|
+
self.individual_base,
|
|
376
|
+
}:
|
|
355
377
|
raise base.InputError(f'invalid decrypt_exp: {self}')
|
|
356
|
-
if gmpy2.powmod(self.group_base, self.decrypt_exp, self.prime_modulus) != self.individual_base:
|
|
378
|
+
if gmpy2.powmod(self.group_base, self.decrypt_exp, self.prime_modulus) != self.individual_base:
|
|
357
379
|
raise base.CryptoError(f'inconsistent g**e % p == i: {self}')
|
|
358
380
|
|
|
359
381
|
def __str__(self) -> str:
|
|
@@ -361,10 +383,13 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
361
383
|
|
|
362
384
|
Returns:
|
|
363
385
|
string representation of ElGamalPrivateKey without leaking secrets
|
|
386
|
+
|
|
364
387
|
"""
|
|
365
|
-
return (
|
|
366
|
-
|
|
367
|
-
|
|
388
|
+
return (
|
|
389
|
+
'ElGamalPrivateKey('
|
|
390
|
+
f'{super(ElGamalPrivateKey, self).__str__()}, '
|
|
391
|
+
f'decrypt_exp={base.ObfuscateSecret(self.decrypt_exp)})'
|
|
392
|
+
)
|
|
368
393
|
|
|
369
394
|
def RawDecrypt(self, ciphertext: tuple[int, int], /) -> int:
|
|
370
395
|
"""Decrypt `ciphertext` tuple with this private key.
|
|
@@ -380,14 +405,15 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
380
405
|
|
|
381
406
|
Raises:
|
|
382
407
|
InputError: invalid inputs
|
|
408
|
+
|
|
383
409
|
"""
|
|
384
410
|
# test inputs
|
|
385
|
-
if
|
|
386
|
-
not 2 <= ciphertext[1] < self.prime_modulus):
|
|
411
|
+
if not 2 <= ciphertext[0] < self.prime_modulus or not 2 <= ciphertext[1] < self.prime_modulus: # noqa: PLR2004
|
|
387
412
|
raise base.InputError(f'invalid message: {ciphertext=}')
|
|
388
413
|
# decrypt
|
|
389
414
|
csi: int = int(
|
|
390
|
-
|
|
415
|
+
gmpy2.powmod(ciphertext[0], self.prime_modulus - 1 - self.decrypt_exp, self.prime_modulus)
|
|
416
|
+
)
|
|
391
417
|
return (ciphertext[1] * csi) % self.prime_modulus
|
|
392
418
|
|
|
393
419
|
def Decrypt(self, ciphertext: bytes, /, *, associated_data: bytes | None = None) -> bytes:
|
|
@@ -412,18 +438,19 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
412
438
|
|
|
413
439
|
Raises:
|
|
414
440
|
InputError: invalid inputs
|
|
415
|
-
|
|
441
|
+
|
|
416
442
|
"""
|
|
417
443
|
k: int = self.modulus_size
|
|
418
444
|
if len(ciphertext) < (k + k + 32):
|
|
419
445
|
raise base.InputError(f'invalid ciphertext length: {len(ciphertext)} ; {k=}')
|
|
420
446
|
# split ciphertext in 3 parts: the first 2k bytes is ct, the rest is AES-256-GCM
|
|
421
|
-
ct1, ct2, aes_ct = ciphertext[:k], ciphertext[k:2 * k], ciphertext[2 * k:]
|
|
447
|
+
ct1, ct2, aes_ct = ciphertext[:k], ciphertext[k : 2 * k], ciphertext[2 * k :]
|
|
422
448
|
r: int = self.RawDecrypt((base.BytesToInt(ct1), base.BytesToInt(ct2)))
|
|
423
449
|
ss: bytes = base.Hash512(base.IntToFixedBytes(r, k))
|
|
424
450
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
425
451
|
aad_prime: bytes = (
|
|
426
|
-
|
|
452
|
+
_ELGAMAL_ENCRYPTION_AAD_PREFIX + base.IntToFixedBytes(len(aad), 8) + aad + ct1 + ct2
|
|
453
|
+
)
|
|
427
454
|
return aes.AESKey(key256=ss[32:]).Decrypt(aes_ct, associated_data=aad_prime)
|
|
428
455
|
|
|
429
456
|
def RawSign(self, message: int, /) -> tuple[int, int]:
|
|
@@ -441,6 +468,7 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
441
468
|
|
|
442
469
|
Raises:
|
|
443
470
|
InputError: invalid inputs
|
|
471
|
+
|
|
444
472
|
"""
|
|
445
473
|
# test inputs
|
|
446
474
|
if not 0 < message < self.prime_modulus:
|
|
@@ -449,9 +477,9 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
449
477
|
a: int = 0
|
|
450
478
|
b: int = 0
|
|
451
479
|
p_1: int = self.prime_modulus - 1
|
|
452
|
-
while a < 2 or b < 2:
|
|
480
|
+
while a < 2 or b < 2: # noqa: PLR2004
|
|
453
481
|
ephemeral_key, ephemeral_inv = self._MakeEphemeralKey()
|
|
454
|
-
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus))
|
|
482
|
+
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus))
|
|
455
483
|
b = (ephemeral_inv * ((message - a * self.decrypt_exp) % p_1)) % p_1
|
|
456
484
|
return (a, b)
|
|
457
485
|
|
|
@@ -478,15 +506,15 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
478
506
|
|
|
479
507
|
Raises:
|
|
480
508
|
InputError: invalid inputs
|
|
481
|
-
|
|
509
|
+
|
|
482
510
|
"""
|
|
483
511
|
k: int = self.modulus_size
|
|
484
|
-
if k <= 64:
|
|
512
|
+
if k <= 64: # noqa: PLR2004
|
|
485
513
|
raise base.InputError(f'modulus too small for signing operations: {k} bytes')
|
|
486
514
|
salt: bytes = base.RandBytes(64)
|
|
487
515
|
s_int: tuple[int, int] = self.RawSign(self._DomainSeparatedHash(message, associated_data, salt))
|
|
488
516
|
s_bytes: bytes = base.IntToFixedBytes(s_int[0], k) + base.IntToFixedBytes(s_int[1], k)
|
|
489
|
-
assert len(s_bytes) == 2 * k, 'should never happen: s_bytes should be exactly 2k bytes'
|
|
517
|
+
assert len(s_bytes) == 2 * k, 'should never happen: s_bytes should be exactly 2k bytes' # noqa: S101
|
|
490
518
|
return salt + s_bytes
|
|
491
519
|
|
|
492
520
|
@classmethod
|
|
@@ -502,10 +530,11 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
502
530
|
Raises:
|
|
503
531
|
InputError: invalid inputs
|
|
504
532
|
CryptoError: failed generation
|
|
533
|
+
|
|
505
534
|
"""
|
|
506
535
|
# test inputs
|
|
507
536
|
bit_length: int = shared_key.prime_modulus.bit_length()
|
|
508
|
-
if bit_length < 11:
|
|
537
|
+
if bit_length < 11: # noqa: PLR2004
|
|
509
538
|
raise base.InputError(f'invalid bit length: {bit_length=}')
|
|
510
539
|
# loop until we have an object
|
|
511
540
|
failures: int = 0
|
|
@@ -513,16 +542,19 @@ class ElGamalPrivateKey(ElGamalPublicKey, base.Decryptor, base.Signer): # pylin
|
|
|
513
542
|
try:
|
|
514
543
|
# generate private key differing from group_base
|
|
515
544
|
decrypt_exp: int = 0
|
|
516
|
-
while (
|
|
517
|
-
|
|
545
|
+
while (
|
|
546
|
+
not 2 < decrypt_exp < shared_key.prime_modulus or decrypt_exp == shared_key.group_base # noqa: PLR2004
|
|
547
|
+
):
|
|
518
548
|
decrypt_exp = base.RandBits(bit_length)
|
|
519
549
|
# make the object
|
|
520
550
|
return cls(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
551
|
+
prime_modulus=shared_key.prime_modulus,
|
|
552
|
+
group_base=shared_key.group_base,
|
|
553
|
+
individual_base=int(
|
|
554
|
+
gmpy2.powmod(shared_key.group_base, decrypt_exp, shared_key.prime_modulus)
|
|
555
|
+
),
|
|
556
|
+
decrypt_exp=decrypt_exp,
|
|
557
|
+
)
|
|
526
558
|
except base.InputError as err:
|
|
527
559
|
failures += 1
|
|
528
560
|
if failures >= _MAX_KEY_GENERATION_FAILURES:
|