transcrypto 1.6.0__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 +587 -442
- transcrypto/constants.py +20070 -1906
- transcrypto/dsa.py +132 -99
- transcrypto/elgamal.py +116 -84
- transcrypto/modmath.py +88 -78
- transcrypto/profiler.py +225 -175
- transcrypto/rsa.py +126 -90
- transcrypto/sss.py +122 -70
- transcrypto/transcrypto.py +2361 -1419
- {transcrypto-1.6.0.dist-info → transcrypto-1.7.0.dist-info}/METADATA +78 -58
- transcrypto-1.7.0.dist-info/RECORD +17 -0
- {transcrypto-1.6.0.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 -1228
- transcrypto-1.6.0.dist-info/RECORD +0 -18
- transcrypto-1.6.0.dist-info/top_level.txt +0 -1
- {transcrypto-1.6.0.dist-info → transcrypto-1.7.0.dist-info}/licenses/LICENSE +0 -0
transcrypto/dsa.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 DSA (Digital Signature Algorithm) library.
|
|
6
4
|
|
|
7
5
|
<https://en.wikipedia.org/wiki/Digital_Signature_Algorithm>
|
|
@@ -17,18 +15,12 @@ import dataclasses
|
|
|
17
15
|
import logging
|
|
18
16
|
import multiprocessing
|
|
19
17
|
import os
|
|
20
|
-
# import pdb
|
|
21
18
|
from typing import Self
|
|
22
19
|
|
|
23
|
-
import gmpy2
|
|
20
|
+
import gmpy2
|
|
24
21
|
|
|
25
22
|
from . import base, constants, modmath
|
|
26
23
|
|
|
27
|
-
__author__ = 'balparda@github.com'
|
|
28
|
-
__version__: str = base.__version__ # version comes from base!
|
|
29
|
-
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
30
|
-
|
|
31
|
-
|
|
32
24
|
_MAX_KEY_GENERATION_FAILURES = 15
|
|
33
25
|
|
|
34
26
|
# fixed prefixes: do NOT ever change! will break all encryption and signature schemes
|
|
@@ -36,8 +28,9 @@ _DSA_SIGNATURE_HASH_PREFIX = b'transcrypto.DSA.Signature.1.0\x00'
|
|
|
36
28
|
|
|
37
29
|
|
|
38
30
|
def NBitRandomDSAPrimes(
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
p_bits: int, q_bits: int, /, *, serial: bool = True
|
|
32
|
+
) -> tuple[int, int, int]:
|
|
33
|
+
"""Generate 2 random DSA primes p & q with `x_bits` size and (p-1)%q==0.
|
|
41
34
|
|
|
42
35
|
Uses an aggressive small-prime wheel sieve:
|
|
43
36
|
Before any Miller-Rabin we reject p = m·q + 1 if it is divisible by a small prime.
|
|
@@ -76,9 +69,11 @@ def NBitRandomDSAPrimes(
|
|
|
76
69
|
|
|
77
70
|
Raises:
|
|
78
71
|
InputError: invalid inputs
|
|
72
|
+
Error: prime search failed
|
|
73
|
+
|
|
79
74
|
"""
|
|
80
75
|
# test inputs
|
|
81
|
-
if q_bits < 11:
|
|
76
|
+
if q_bits < 11: # noqa: PLR2004
|
|
82
77
|
raise base.InputError(f'invalid q_bits length: {q_bits=}')
|
|
83
78
|
if p_bits < q_bits + 11:
|
|
84
79
|
raise base.InputError(f'invalid p_bits length: {p_bits=}')
|
|
@@ -88,7 +83,7 @@ def NBitRandomDSAPrimes(
|
|
|
88
83
|
n_workers: int = min(4, os.cpu_count() or 1)
|
|
89
84
|
pr: int | None = None
|
|
90
85
|
m: int | None = None
|
|
91
|
-
if serial or n_workers <= 1 or p_bits < 200:
|
|
86
|
+
if serial or n_workers <= 1 or p_bits < 200: # noqa: PLR2004
|
|
92
87
|
# do one worker
|
|
93
88
|
while pr is None or m is None or pr.bit_length() != p_bits:
|
|
94
89
|
pr, m = _PrimePSearchShard(q, p_bits)
|
|
@@ -97,10 +92,12 @@ def NBitRandomDSAPrimes(
|
|
|
97
92
|
multiprocessing.set_start_method('fork', force=True)
|
|
98
93
|
with concurrent.futures.ProcessPoolExecutor(max_workers=n_workers) as pool:
|
|
99
94
|
workers: set[concurrent.futures.Future[tuple[int | None, int | None]]] = {
|
|
100
|
-
|
|
95
|
+
pool.submit(_PrimePSearchShard, q, p_bits) for _ in range(n_workers)
|
|
96
|
+
}
|
|
101
97
|
while workers:
|
|
102
98
|
done: set[concurrent.futures.Future[tuple[int | None, int | None]]] = concurrent.futures.wait(
|
|
103
|
-
|
|
99
|
+
workers, return_when=concurrent.futures.FIRST_COMPLETED
|
|
100
|
+
)[0]
|
|
104
101
|
for worker in done:
|
|
105
102
|
workers.remove(worker)
|
|
106
103
|
pr, m = worker.result()
|
|
@@ -113,7 +110,7 @@ def NBitRandomDSAPrimes(
|
|
|
113
110
|
|
|
114
111
|
|
|
115
112
|
def _PrimePSearchShard(q: int, p_bits: int) -> tuple[int | None, int | None]:
|
|
116
|
-
"""Search for a `p_bits` random prime, starting from a random point, for ~
|
|
113
|
+
"""Search for a `p_bits` random prime, starting from a random point, for ~6x expected prime gap.
|
|
117
114
|
|
|
118
115
|
Args:
|
|
119
116
|
q (int): Prime `q` for DSA
|
|
@@ -121,27 +118,26 @@ def _PrimePSearchShard(q: int, p_bits: int) -> tuple[int | None, int | None]:
|
|
|
121
118
|
|
|
122
119
|
Returns:
|
|
123
120
|
tuple[int | None, int | None]: either the prime `p` and multiple `m` or None if no prime found
|
|
121
|
+
|
|
124
122
|
"""
|
|
125
123
|
q_bits: int = q.bit_length()
|
|
126
|
-
shard_len: int = max(2000, 6 * int(0.693 * p_bits)) # ~
|
|
124
|
+
shard_len: int = max(2000, 6 * int(0.693 * p_bits)) # ~6x expected prime gap ~2^k (≈ 0.693*k)
|
|
127
125
|
# find range of multiples to use
|
|
128
126
|
min_p: int = 2 ** (p_bits - 1)
|
|
129
|
-
max_p: int = 2
|
|
127
|
+
max_p: int = 2**p_bits - 1
|
|
130
128
|
min_m: int = min_p // q + 2
|
|
131
129
|
max_m: int = max_p // q - 2
|
|
132
|
-
assert max_m - min_m > 1000 # make sure we'll have options!
|
|
130
|
+
assert max_m - min_m > 1000 # make sure we'll have options! # noqa: PLR2004, S101
|
|
133
131
|
# make list of small primes to use for sieving
|
|
134
132
|
approx_q_root: int = 1 << (q_bits // 2)
|
|
135
133
|
pr: int
|
|
136
134
|
forbidden: dict[int, int] = { # (modulus: forbidden residue)
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
pr: ((-modmath.ModInv(q % pr, pr)) % pr)
|
|
136
|
+
for pr in constants.FIRST_5K_PRIMES_SORTED[1 : min(1000, approx_q_root)]
|
|
137
|
+
} # skip pr==2
|
|
139
138
|
|
|
140
139
|
def _PassesSieve(m: int) -> bool:
|
|
141
|
-
for r, f in forbidden.items()
|
|
142
|
-
if m % r == f:
|
|
143
|
-
return False
|
|
144
|
-
return True
|
|
140
|
+
return all(m % r != f for r, f in forbidden.items())
|
|
145
141
|
|
|
146
142
|
# try searching starting here
|
|
147
143
|
m: int = base.RandInt(min_m, max_m)
|
|
@@ -154,9 +150,8 @@ def _PrimePSearchShard(q: int, p_bits: int) -> tuple[int | None, int | None]:
|
|
|
154
150
|
if pr > max_p:
|
|
155
151
|
break
|
|
156
152
|
# first do a quick sieve test
|
|
157
|
-
if _PassesSieve(m):
|
|
158
|
-
|
|
159
|
-
return (pr, m) # found a suitable prime set!
|
|
153
|
+
if _PassesSieve(m) and modmath.IsPrime(pr): # passed sieve, do full test
|
|
154
|
+
return (pr, m) # found a suitable prime set!
|
|
160
155
|
count += 1
|
|
161
156
|
m += 2 # next even number
|
|
162
157
|
return (None, None)
|
|
@@ -172,6 +167,7 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
172
167
|
prime_modulus (int): prime modulus (p), > prime_seed
|
|
173
168
|
prime_seed (int): prime seed (q), ≥ 7
|
|
174
169
|
group_base (int): shared encryption group public base, 3 ≤ g < prime_modulus
|
|
170
|
+
|
|
175
171
|
"""
|
|
176
172
|
|
|
177
173
|
prime_modulus: int
|
|
@@ -183,16 +179,17 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
183
179
|
|
|
184
180
|
Raises:
|
|
185
181
|
InputError: invalid inputs
|
|
182
|
+
|
|
186
183
|
"""
|
|
187
|
-
|
|
188
|
-
if self.prime_seed < 7 or not modmath.IsPrime(self.prime_seed):
|
|
184
|
+
if self.prime_seed < 7 or not modmath.IsPrime(self.prime_seed): # noqa: PLR2004
|
|
189
185
|
raise base.InputError(f'invalid prime_seed: {self}')
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
186
|
+
if (
|
|
187
|
+
self.prime_modulus <= self.prime_seed
|
|
188
|
+
or self.prime_modulus % self.prime_seed != 1
|
|
189
|
+
or not modmath.IsPrime(self.prime_modulus)
|
|
190
|
+
):
|
|
193
191
|
raise base.InputError(f'invalid prime_modulus: {self}')
|
|
194
|
-
if
|
|
195
|
-
self.group_base == self.prime_seed):
|
|
192
|
+
if not 2 < self.group_base < self.prime_modulus or self.group_base == self.prime_seed: # noqa: PLR2004
|
|
196
193
|
raise base.InputError(f'invalid group_base: {self}')
|
|
197
194
|
|
|
198
195
|
def __str__(self) -> str:
|
|
@@ -200,21 +197,24 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
200
197
|
|
|
201
198
|
Returns:
|
|
202
199
|
string representation of DSASharedPublicKey
|
|
200
|
+
|
|
203
201
|
"""
|
|
204
|
-
return (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
202
|
+
return (
|
|
203
|
+
'DSASharedPublicKey('
|
|
204
|
+
f'bits=[{self.prime_modulus.bit_length()}, {self.prime_seed.bit_length()}], '
|
|
205
|
+
f'prime_modulus={base.IntToEncoded(self.prime_modulus)}, '
|
|
206
|
+
f'prime_seed={base.IntToEncoded(self.prime_seed)}, '
|
|
207
|
+
f'group_base={base.IntToEncoded(self.group_base)})'
|
|
208
|
+
)
|
|
209
209
|
|
|
210
210
|
@property
|
|
211
211
|
def modulus_size(self) -> tuple[int, int]:
|
|
212
212
|
"""Modulus size in bytes. The number of bytes used in Sign/Verify."""
|
|
213
|
-
return ((self.prime_modulus.bit_length() + 7) // 8,
|
|
214
|
-
(self.prime_seed.bit_length() + 7) // 8)
|
|
213
|
+
return ((self.prime_modulus.bit_length() + 7) // 8, (self.prime_seed.bit_length() + 7) // 8)
|
|
215
214
|
|
|
216
215
|
def _DomainSeparatedHash(
|
|
217
|
-
|
|
216
|
+
self, message: bytes, associated_data: bytes | None, salt: bytes, /
|
|
217
|
+
) -> int:
|
|
218
218
|
"""Compute the domain-separated hash for signing and verifying.
|
|
219
219
|
|
|
220
220
|
Args:
|
|
@@ -228,12 +228,12 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
228
228
|
|
|
229
229
|
Raises:
|
|
230
230
|
CryptoError: hash output is out of range
|
|
231
|
+
|
|
231
232
|
"""
|
|
232
233
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
233
234
|
la: bytes = base.IntToFixedBytes(len(aad), 8)
|
|
234
|
-
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes'
|
|
235
|
-
y: int = base.BytesToInt(
|
|
236
|
-
base.Hash512(_DSA_SIGNATURE_HASH_PREFIX + la + aad + message + salt))
|
|
235
|
+
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes' # noqa: PLR2004, S101
|
|
236
|
+
y: int = base.BytesToInt(base.Hash512(_DSA_SIGNATURE_HASH_PREFIX + la + aad + message + salt))
|
|
237
237
|
if not 1 < y < self.prime_seed - 1:
|
|
238
238
|
# will only reasonably happen if prime seed is small
|
|
239
239
|
raise base.CryptoError(f'hash output {y} is out of range/invalid {self.prime_seed}')
|
|
@@ -251,16 +251,14 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
251
251
|
Returns:
|
|
252
252
|
DSASharedPublicKey object ready for use
|
|
253
253
|
|
|
254
|
-
Raises:
|
|
255
|
-
InputError: invalid inputs
|
|
256
254
|
"""
|
|
257
255
|
# test inputs and generate primes
|
|
258
256
|
p, q, m = NBitRandomDSAPrimes(p_bits, q_bits)
|
|
259
257
|
# generate random number, create object (should never fail)
|
|
260
258
|
g: int = 0
|
|
261
|
-
while g <
|
|
259
|
+
while g < 3: # noqa: PLR2004
|
|
262
260
|
h: int = base.RandBits(p_bits - 1)
|
|
263
|
-
g = int(gmpy2.powmod(h, m, p))
|
|
261
|
+
g = int(gmpy2.powmod(h, m, p))
|
|
264
262
|
return cls(prime_modulus=p, prime_seed=q, group_base=g)
|
|
265
263
|
|
|
266
264
|
|
|
@@ -272,6 +270,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
272
270
|
|
|
273
271
|
Attributes:
|
|
274
272
|
individual_base (int): individual encryption public base, 3 ≤ i < prime_modulus
|
|
273
|
+
|
|
275
274
|
"""
|
|
276
275
|
|
|
277
276
|
individual_base: int
|
|
@@ -281,10 +280,13 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
281
280
|
|
|
282
281
|
Raises:
|
|
283
282
|
InputError: invalid inputs
|
|
283
|
+
|
|
284
284
|
"""
|
|
285
|
-
super(DSAPublicKey, self).__post_init__()
|
|
286
|
-
if
|
|
287
|
-
|
|
285
|
+
super(DSAPublicKey, self).__post_init__()
|
|
286
|
+
if not 2 < self.individual_base < self.prime_modulus or self.individual_base in { # noqa: PLR2004
|
|
287
|
+
self.group_base,
|
|
288
|
+
self.prime_seed,
|
|
289
|
+
}:
|
|
288
290
|
raise base.InputError(f'invalid individual_base: {self}')
|
|
289
291
|
|
|
290
292
|
def __str__(self) -> str:
|
|
@@ -292,21 +294,27 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
292
294
|
|
|
293
295
|
Returns:
|
|
294
296
|
string representation of DSAPublicKey
|
|
297
|
+
|
|
295
298
|
"""
|
|
296
|
-
return (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
+
return (
|
|
300
|
+
'DSAPublicKey('
|
|
301
|
+
f'{super(DSAPublicKey, self).__str__()}, '
|
|
302
|
+
f'individual_base={base.IntToEncoded(self.individual_base)})'
|
|
303
|
+
)
|
|
299
304
|
|
|
300
305
|
def _MakeEphemeralKey(self) -> tuple[int, int]:
|
|
301
306
|
"""Make an ephemeral key adequate to be used with DSA.
|
|
302
307
|
|
|
303
308
|
Returns:
|
|
304
309
|
(key, key_inverse), where 3 ≤ k < p_seed and (k*i) % p_seed == 1
|
|
310
|
+
|
|
305
311
|
"""
|
|
306
312
|
ephemeral_key: int = 0
|
|
307
313
|
bit_length: int = self.prime_seed.bit_length()
|
|
308
|
-
while
|
|
309
|
-
|
|
314
|
+
while not 2 < ephemeral_key < self.prime_seed or ephemeral_key in { # noqa: PLR2004
|
|
315
|
+
self.group_base,
|
|
316
|
+
self.individual_base,
|
|
317
|
+
}:
|
|
310
318
|
ephemeral_key = base.RandBits(bit_length - 1)
|
|
311
319
|
return (ephemeral_key, modmath.ModInv(ephemeral_key, self.prime_seed))
|
|
312
320
|
|
|
@@ -326,23 +334,26 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
326
334
|
|
|
327
335
|
Raises:
|
|
328
336
|
InputError: invalid inputs
|
|
337
|
+
|
|
329
338
|
"""
|
|
330
339
|
# test inputs
|
|
331
340
|
if not 0 < message < self.prime_seed:
|
|
332
341
|
raise base.InputError(f'invalid message: {message=}')
|
|
333
|
-
if
|
|
334
|
-
not 2 <= signature[1] < self.prime_seed):
|
|
342
|
+
if not 2 <= signature[0] < self.prime_seed or not 2 <= signature[1] < self.prime_seed: # noqa: PLR2004
|
|
335
343
|
raise base.InputError(f'invalid signature: {signature=}')
|
|
336
344
|
# verify
|
|
337
345
|
inv: int = modmath.ModInv(signature[1], self.prime_seed)
|
|
338
|
-
a: int = int(
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
346
|
+
a: int = int(
|
|
347
|
+
gmpy2.powmod(self.group_base, (message * inv) % self.prime_seed, self.prime_modulus)
|
|
348
|
+
)
|
|
349
|
+
b: int = int(
|
|
350
|
+
gmpy2.powmod(self.individual_base, (signature[0] * inv) % self.prime_seed, self.prime_modulus)
|
|
351
|
+
)
|
|
342
352
|
return ((a * b) % self.prime_modulus) % self.prime_seed == signature[0]
|
|
343
353
|
|
|
344
354
|
def Verify(
|
|
345
|
-
|
|
355
|
+
self, message: bytes, signature: bytes, /, *, associated_data: bytes | None = None
|
|
356
|
+
) -> bool:
|
|
346
357
|
"""Verify a `signature` for `message`. True if OK; False if failed verification.
|
|
347
358
|
|
|
348
359
|
• Let k = ceil(log2(n))/8 be the modulus size in bytes.
|
|
@@ -361,40 +372,51 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
361
372
|
|
|
362
373
|
Raises:
|
|
363
374
|
InputError: invalid inputs
|
|
364
|
-
|
|
375
|
+
|
|
365
376
|
"""
|
|
366
377
|
k: int = self.modulus_size[1] # use prime_seed size
|
|
367
|
-
if k <= 64:
|
|
378
|
+
if k <= 64: # noqa: PLR2004
|
|
368
379
|
raise base.InputError(f'modulus/seed too small for signing operations: {k} bytes')
|
|
369
380
|
if len(signature) != (64 + k + k):
|
|
370
381
|
logging.info(f'invalid signature length: {len(signature)} ; expected {64 + k + k}')
|
|
371
382
|
return False
|
|
372
383
|
try:
|
|
373
384
|
return self.RawVerify(
|
|
374
|
-
|
|
375
|
-
|
|
385
|
+
self._DomainSeparatedHash(message, associated_data, signature[:64]),
|
|
386
|
+
(base.BytesToInt(signature[64 : 64 + k]), base.BytesToInt(signature[64 + k :])),
|
|
387
|
+
)
|
|
376
388
|
except base.InputError as err:
|
|
377
389
|
logging.info(err)
|
|
378
390
|
return False
|
|
379
391
|
|
|
380
392
|
@classmethod
|
|
381
393
|
def Copy(cls, other: DSAPublicKey, /) -> Self:
|
|
382
|
-
"""Initialize a public key by taking the public parts of a public/private key.
|
|
394
|
+
"""Initialize a public key by taking the public parts of a public/private key.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
other (DSAPublicKey): object to copy from
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
Self: a new DSAPublicKey
|
|
401
|
+
|
|
402
|
+
"""
|
|
383
403
|
return cls(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
404
|
+
prime_modulus=other.prime_modulus,
|
|
405
|
+
prime_seed=other.prime_seed,
|
|
406
|
+
group_base=other.group_base,
|
|
407
|
+
individual_base=other.individual_base,
|
|
408
|
+
)
|
|
388
409
|
|
|
389
410
|
|
|
390
411
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
391
|
-
class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
412
|
+
class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
392
413
|
"""DSA private key.
|
|
393
414
|
|
|
394
415
|
No measures are taken here to prevent timing attacks.
|
|
395
416
|
|
|
396
417
|
Attributes:
|
|
397
|
-
decrypt_exp (int): individual decryption exponent, 3 ≤ i <
|
|
418
|
+
decrypt_exp (int): individual decryption exponent, 3 ≤ i < prime_seed
|
|
419
|
+
|
|
398
420
|
"""
|
|
399
421
|
|
|
400
422
|
decrypt_exp: int
|
|
@@ -405,12 +427,15 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
405
427
|
Raises:
|
|
406
428
|
InputError: invalid inputs
|
|
407
429
|
CryptoError: modulus math is inconsistent with values
|
|
430
|
+
|
|
408
431
|
"""
|
|
409
|
-
super(DSAPrivateKey, self).__post_init__()
|
|
410
|
-
if
|
|
411
|
-
|
|
432
|
+
super(DSAPrivateKey, self).__post_init__()
|
|
433
|
+
if not 2 < self.decrypt_exp < self.prime_seed or self.decrypt_exp in { # noqa: PLR2004
|
|
434
|
+
self.group_base,
|
|
435
|
+
self.individual_base,
|
|
436
|
+
}:
|
|
412
437
|
raise base.InputError(f'invalid decrypt_exp: {self}')
|
|
413
|
-
if gmpy2.powmod(self.group_base, self.decrypt_exp, self.prime_modulus) != self.individual_base:
|
|
438
|
+
if gmpy2.powmod(self.group_base, self.decrypt_exp, self.prime_modulus) != self.individual_base:
|
|
414
439
|
raise base.CryptoError(f'inconsistent g**d % p == i: {self}')
|
|
415
440
|
|
|
416
441
|
def __str__(self) -> str:
|
|
@@ -418,10 +443,13 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
418
443
|
|
|
419
444
|
Returns:
|
|
420
445
|
string representation of DSAPrivateKey without leaking secrets
|
|
446
|
+
|
|
421
447
|
"""
|
|
422
|
-
return (
|
|
423
|
-
|
|
424
|
-
|
|
448
|
+
return (
|
|
449
|
+
'DSAPrivateKey('
|
|
450
|
+
f'{super(DSAPrivateKey, self).__str__()}, '
|
|
451
|
+
f'decrypt_exp={base.ObfuscateSecret(self.decrypt_exp)})'
|
|
452
|
+
)
|
|
425
453
|
|
|
426
454
|
def RawSign(self, message: int, /) -> tuple[int, int]:
|
|
427
455
|
"""Sign `message` with this private key.
|
|
@@ -438,6 +466,7 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
438
466
|
|
|
439
467
|
Raises:
|
|
440
468
|
InputError: invalid inputs
|
|
469
|
+
|
|
441
470
|
"""
|
|
442
471
|
# test inputs
|
|
443
472
|
if not 0 < message < self.prime_seed:
|
|
@@ -445,9 +474,9 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
445
474
|
# sign
|
|
446
475
|
a: int = 0
|
|
447
476
|
b: int = 0
|
|
448
|
-
while a < 2 or b < 2:
|
|
477
|
+
while a < 2 or b < 2: # noqa: PLR2004
|
|
449
478
|
ephemeral_key, ephemeral_inv = self._MakeEphemeralKey()
|
|
450
|
-
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus) % self.prime_seed)
|
|
479
|
+
a = int(gmpy2.powmod(self.group_base, ephemeral_key, self.prime_modulus) % self.prime_seed)
|
|
451
480
|
b = (ephemeral_inv * ((message + a * self.decrypt_exp) % self.prime_seed)) % self.prime_seed
|
|
452
481
|
return (a, b)
|
|
453
482
|
|
|
@@ -474,15 +503,15 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
474
503
|
|
|
475
504
|
Raises:
|
|
476
505
|
InputError: invalid inputs
|
|
477
|
-
|
|
506
|
+
|
|
478
507
|
"""
|
|
479
508
|
k: int = self.modulus_size[1] # use prime_seed size
|
|
480
|
-
if k <= 64:
|
|
509
|
+
if k <= 64: # noqa: PLR2004
|
|
481
510
|
raise base.InputError(f'modulus/seed too small for signing operations: {k} bytes')
|
|
482
511
|
salt: bytes = base.RandBytes(64)
|
|
483
512
|
s_int: tuple[int, int] = self.RawSign(self._DomainSeparatedHash(message, associated_data, salt))
|
|
484
513
|
s_bytes: bytes = base.IntToFixedBytes(s_int[0], k) + base.IntToFixedBytes(s_int[1], k)
|
|
485
|
-
assert len(s_bytes) == 2 * k, 'should never happen: s_bytes should be exactly 2k bytes'
|
|
514
|
+
assert len(s_bytes) == 2 * k, 'should never happen: s_bytes should be exactly 2k bytes' # noqa: S101
|
|
486
515
|
return salt + s_bytes
|
|
487
516
|
|
|
488
517
|
@classmethod
|
|
@@ -498,10 +527,11 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
498
527
|
Raises:
|
|
499
528
|
InputError: invalid inputs
|
|
500
529
|
CryptoError: failed generation
|
|
530
|
+
|
|
501
531
|
"""
|
|
502
532
|
# test inputs
|
|
503
533
|
bit_length: int = shared_key.prime_seed.bit_length()
|
|
504
|
-
if bit_length < 11:
|
|
534
|
+
if bit_length < 11: # noqa: PLR2004
|
|
505
535
|
raise base.InputError(f'invalid q_bit length: {bit_length=}')
|
|
506
536
|
# loop until we have an object
|
|
507
537
|
failures: int = 0
|
|
@@ -509,17 +539,20 @@ class DSAPrivateKey(DSAPublicKey, base.Signer): # pylint: disable=too-many-ance
|
|
|
509
539
|
try:
|
|
510
540
|
# generate private key differing from group_base
|
|
511
541
|
decrypt_exp: int = 0
|
|
512
|
-
while (
|
|
513
|
-
|
|
542
|
+
while (
|
|
543
|
+
not 2 < decrypt_exp < shared_key.prime_seed or decrypt_exp == shared_key.group_base # noqa: PLR2004
|
|
544
|
+
):
|
|
514
545
|
decrypt_exp = base.RandBits(bit_length - 1)
|
|
515
546
|
# make the object
|
|
516
547
|
return cls(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
548
|
+
prime_modulus=shared_key.prime_modulus,
|
|
549
|
+
prime_seed=shared_key.prime_seed,
|
|
550
|
+
group_base=shared_key.group_base,
|
|
551
|
+
individual_base=int(
|
|
552
|
+
gmpy2.powmod(shared_key.group_base, decrypt_exp, shared_key.prime_modulus)
|
|
553
|
+
),
|
|
554
|
+
decrypt_exp=decrypt_exp,
|
|
555
|
+
)
|
|
523
556
|
except base.InputError as err:
|
|
524
557
|
failures += 1
|
|
525
558
|
if failures >= _MAX_KEY_GENERATION_FAILURES:
|