transcrypto 1.7.0__py3-none-any.whl → 2.0.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 +1 -1
- transcrypto/cli/__init__.py +3 -0
- transcrypto/cli/aeshash.py +370 -0
- transcrypto/cli/bidsecret.py +336 -0
- transcrypto/cli/clibase.py +183 -0
- transcrypto/cli/intmath.py +429 -0
- transcrypto/cli/publicalgos.py +878 -0
- transcrypto/core/__init__.py +3 -0
- transcrypto/{aes.py → core/aes.py} +17 -29
- transcrypto/core/bid.py +161 -0
- transcrypto/{dsa.py → core/dsa.py} +28 -27
- transcrypto/{elgamal.py → core/elgamal.py} +33 -32
- transcrypto/core/hashes.py +96 -0
- transcrypto/core/key.py +735 -0
- transcrypto/{modmath.py → core/modmath.py} +91 -17
- transcrypto/{rsa.py → core/rsa.py} +51 -50
- transcrypto/{sss.py → core/sss.py} +27 -26
- transcrypto/profiler.py +29 -13
- transcrypto/transcrypto.py +60 -1996
- transcrypto/utils/__init__.py +3 -0
- transcrypto/utils/base.py +72 -0
- transcrypto/utils/human.py +278 -0
- transcrypto/utils/logging.py +139 -0
- transcrypto/utils/saferandom.py +102 -0
- transcrypto/utils/stats.py +360 -0
- transcrypto/utils/timer.py +175 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/METADATA +111 -109
- transcrypto-2.0.0.dist-info/RECORD +33 -0
- transcrypto/base.py +0 -1918
- transcrypto-1.7.0.dist-info/RECORD +0 -17
- /transcrypto/{constants.py → core/constants.py} +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/entry_points.txt +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,8 @@ from collections import abc
|
|
|
12
12
|
|
|
13
13
|
import gmpy2
|
|
14
14
|
|
|
15
|
-
from . import
|
|
15
|
+
from transcrypto.core import constants
|
|
16
|
+
from transcrypto.utils import base, saferandom
|
|
16
17
|
|
|
17
18
|
_MAX_PRIMALITY_SAFETY = 100 # this is an absurd number, just to have a max
|
|
18
19
|
|
|
@@ -21,6 +22,79 @@ class ModularDivideError(base.Error):
|
|
|
21
22
|
"""Divide-by-zero-like exception (TransCrypto)."""
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
def GCD(a: int, b: int, /) -> int:
|
|
26
|
+
"""Greatest Common Divisor for `a` and `b`, integers ≥0. Uses the Euclid method.
|
|
27
|
+
|
|
28
|
+
O(log(min(a, b)))
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
a (int): integer a ≥ 0
|
|
32
|
+
b (int): integer b ≥ 0 (can't be both zero)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
gcd(a, b)
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
base.InputError: invalid inputs
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
# test inputs
|
|
42
|
+
if a < 0 or b < 0 or (not a and not b):
|
|
43
|
+
raise base.InputError(f'negative input or undefined gcd(0, 0): {a=} , {b=}')
|
|
44
|
+
# algo needs to start with a >= b
|
|
45
|
+
if a < b:
|
|
46
|
+
a, b = b, a
|
|
47
|
+
# euclid
|
|
48
|
+
while b:
|
|
49
|
+
r: int = a % b
|
|
50
|
+
a, b = b, r
|
|
51
|
+
return a
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def ExtendedGCD(a: int, b: int, /) -> tuple[int, int, int]:
|
|
55
|
+
"""Greatest Common Divisor Extended for `a` and `b`, integers ≥0. Uses the Euclid method.
|
|
56
|
+
|
|
57
|
+
O(log(min(a, b)))
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
a (int): integer a ≥ 0
|
|
61
|
+
b (int): integer b ≥ 0 (can't be both zero)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
(gcd, x, y) so that a * x + b * y = gcd
|
|
65
|
+
x and y may be negative integers or zero but won't be both zero.
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
base.InputError: invalid inputs
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
# test inputs
|
|
72
|
+
if a < 0 or b < 0 or (not a and not b):
|
|
73
|
+
raise base.InputError(f'negative input or undefined gcd(0, 0): {a=} , {b=}')
|
|
74
|
+
# algo needs to start with a >= b (but we remember if we did swap)
|
|
75
|
+
swapped = False
|
|
76
|
+
if a < b:
|
|
77
|
+
a, b = b, a
|
|
78
|
+
swapped = True
|
|
79
|
+
# trivial case
|
|
80
|
+
if not b:
|
|
81
|
+
return (a, 0 if swapped else 1, 1 if swapped else 0)
|
|
82
|
+
# euclid
|
|
83
|
+
x1: int = 0
|
|
84
|
+
x2: int = 1
|
|
85
|
+
y1: int = 1
|
|
86
|
+
y2: int = 0
|
|
87
|
+
q: int
|
|
88
|
+
r: int
|
|
89
|
+
x: int
|
|
90
|
+
y: int
|
|
91
|
+
while b:
|
|
92
|
+
q, r = divmod(a, b)
|
|
93
|
+
x, y = x2 - q * x1, y2 - q * y1
|
|
94
|
+
a, b, x1, x2, y1, y2 = b, r, x, x1, y, y1
|
|
95
|
+
return (a, y2 if swapped else x2, x2 if swapped else y2)
|
|
96
|
+
|
|
97
|
+
|
|
24
98
|
def ModInv(x: int, m: int, /) -> int:
|
|
25
99
|
"""Modular inverse of `x` mod `m`: a `y` such that (x * y) % m == 1 if GCD(x, m) == 1.
|
|
26
100
|
|
|
@@ -33,7 +107,7 @@ def ModInv(x: int, m: int, /) -> int:
|
|
|
33
107
|
this only exists if GCD(x, m) == 1, so to guarantee an inverse `m` must be prime
|
|
34
108
|
|
|
35
109
|
Raises:
|
|
36
|
-
InputError: invalid modulus or x
|
|
110
|
+
base.InputError: invalid modulus or x
|
|
37
111
|
ModularDivideError: divide-by-zero, i.e., GCD(x, m) != 1 or x == 0
|
|
38
112
|
|
|
39
113
|
"""
|
|
@@ -47,7 +121,7 @@ def ModInv(x: int, m: int, /) -> int:
|
|
|
47
121
|
if reduced_x == 1: # trivial degenerate case
|
|
48
122
|
return 1
|
|
49
123
|
# compute actual extended GCD and see if we will have an inverse
|
|
50
|
-
gcd, y, w =
|
|
124
|
+
gcd, y, w = ExtendedGCD(reduced_x, m)
|
|
51
125
|
if gcd != 1:
|
|
52
126
|
raise ModularDivideError(f'invalid inverse {x=} mod {m=} with {gcd=}')
|
|
53
127
|
assert y and w and y >= -m, f'should never happen: {x=} mod {m=} -> {w=} ; {y=}' # noqa: PT018, S101
|
|
@@ -67,7 +141,7 @@ def ModDiv(x: int, y: int, m: int, /) -> int:
|
|
|
67
141
|
this only exists if GCD(y, m) == 1, so to guarantee an inverse `m` must be prime
|
|
68
142
|
|
|
69
143
|
Raises:
|
|
70
|
-
InputError: invalid modulus or x or y
|
|
144
|
+
base.InputError: invalid modulus or x or y
|
|
71
145
|
ModularDivideError: divide-by-zero, i.e., GCD(y, m) != 1 or y == 0
|
|
72
146
|
|
|
73
147
|
"""
|
|
@@ -105,7 +179,7 @@ def CRTPair(a1: int, m1: int, a2: int, m2: int) -> int:
|
|
|
105
179
|
the least non-negative solution `x` such that a1 = x % m1 and a2 = x % m2 and 0 ≤ x < m1 * m2
|
|
106
180
|
|
|
107
181
|
Raises:
|
|
108
|
-
InputError: invalid inputs
|
|
182
|
+
base.InputError: invalid inputs
|
|
109
183
|
ModularDivideError: moduli are not co-prime, i.e. gcd(m1, m2) != 1
|
|
110
184
|
|
|
111
185
|
"""
|
|
@@ -137,7 +211,7 @@ def ModExp(x: int, y: int, m: int, /) -> int:
|
|
|
137
211
|
(x ** y) mod m
|
|
138
212
|
|
|
139
213
|
Raises:
|
|
140
|
-
InputError: invalid inputs
|
|
214
|
+
base.InputError: invalid inputs
|
|
141
215
|
|
|
142
216
|
"""
|
|
143
217
|
# test inputs
|
|
@@ -184,7 +258,7 @@ def ModPolynomial(x: int, polynomial: abc.Reversible[int], m: int, /) -> int:
|
|
|
184
258
|
f(x) mod m
|
|
185
259
|
|
|
186
260
|
Raises:
|
|
187
|
-
InputError: invalid inputs
|
|
261
|
+
base.InputError: invalid inputs
|
|
188
262
|
|
|
189
263
|
"""
|
|
190
264
|
# test inputs
|
|
@@ -228,7 +302,7 @@ def ModLagrangeInterpolate(x: int, points: dict[int, int], m: int, /) -> int:
|
|
|
228
302
|
y-value solution for f(x) mod m given `points` mapping
|
|
229
303
|
|
|
230
304
|
Raises:
|
|
231
|
-
InputError: invalid inputs
|
|
305
|
+
base.InputError: invalid inputs
|
|
232
306
|
|
|
233
307
|
"""
|
|
234
308
|
# test inputs
|
|
@@ -275,7 +349,7 @@ def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = N
|
|
|
275
349
|
False if certainly not prime ; True if (probabilistically) prime
|
|
276
350
|
|
|
277
351
|
Raises:
|
|
278
|
-
InputError: invalid inputs
|
|
352
|
+
base.InputError: invalid inputs
|
|
279
353
|
|
|
280
354
|
"""
|
|
281
355
|
# test inputs and test for trivial cases: 1, 2, 3, divisible by 2
|
|
@@ -294,7 +368,7 @@ def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = N
|
|
|
294
368
|
safety = min(safety, max_safety)
|
|
295
369
|
witnesses = set()
|
|
296
370
|
while len(witnesses) < safety:
|
|
297
|
-
witnesses.add(
|
|
371
|
+
witnesses.add(saferandom.RandInt(2, n - 2))
|
|
298
372
|
# we have our witnesses: do the actual Fermat algo
|
|
299
373
|
for w in sorted(witnesses):
|
|
300
374
|
if not 2 <= w <= (n - 2): # noqa: PLR2004
|
|
@@ -323,7 +397,7 @@ def _MillerRabinWitnesses(n: int, /) -> set[int]: # noqa: PLR0911
|
|
|
323
397
|
{witness1, witness2, ...} for either "certainty" of primality or error chance < 10**25
|
|
324
398
|
|
|
325
399
|
Raises:
|
|
326
|
-
InputError: invalid inputs
|
|
400
|
+
base.InputError: invalid inputs
|
|
327
401
|
|
|
328
402
|
"""
|
|
329
403
|
# test inputs
|
|
@@ -364,7 +438,7 @@ def _MillerRabinSR(n: int, /) -> tuple[int, int]:
|
|
|
364
438
|
(s, r) so that (2 ** s) * r == (n - 1)
|
|
365
439
|
|
|
366
440
|
Raises:
|
|
367
|
-
InputError: invalid inputs
|
|
441
|
+
base.InputError: invalid inputs
|
|
368
442
|
|
|
369
443
|
"""
|
|
370
444
|
# test inputs
|
|
@@ -395,7 +469,7 @@ def MillerRabinIsPrime(n: int, /, *, witnesses: set[int] | None = None) -> bool:
|
|
|
395
469
|
False if certainly not prime ; True if (probabilistically) prime
|
|
396
470
|
|
|
397
471
|
Raises:
|
|
398
|
-
InputError: invalid inputs
|
|
472
|
+
base.InputError: invalid inputs
|
|
399
473
|
|
|
400
474
|
"""
|
|
401
475
|
# test inputs and test for trivial cases: 1, 2, 3, divisible by 2
|
|
@@ -456,7 +530,7 @@ def PrimeGenerator(start: int, /) -> abc.Generator[int]:
|
|
|
456
530
|
prime numbers (int)
|
|
457
531
|
|
|
458
532
|
Raises:
|
|
459
|
-
InputError: invalid inputs
|
|
533
|
+
base.InputError: invalid inputs
|
|
460
534
|
|
|
461
535
|
"""
|
|
462
536
|
# test inputs and make sure we start at an odd number
|
|
@@ -508,8 +582,8 @@ def NBitRandomPrimes(n_bits: int, /, *, serial: bool = True, n_primes: int = 1)
|
|
|
508
582
|
set[int]: `n_primes` random primes with `n_bits` bits
|
|
509
583
|
|
|
510
584
|
Raises:
|
|
511
|
-
InputError: invalid inputs
|
|
512
|
-
Error: prime search failed
|
|
585
|
+
base.InputError: invalid inputs
|
|
586
|
+
base.Error: prime search failed
|
|
513
587
|
|
|
514
588
|
"""
|
|
515
589
|
# test inputs
|
|
@@ -563,7 +637,7 @@ def _PrimeSearchShard(n_bits: int) -> int | None:
|
|
|
563
637
|
|
|
564
638
|
"""
|
|
565
639
|
shard_len: int = max(2000, 6 * int(0.693 * n_bits)) # ~6x expected prime gap ~2^k (≈ 0.693*k)
|
|
566
|
-
pr: int =
|
|
640
|
+
pr: int = saferandom.RandBits(n_bits) | 1 # random position; make ODD
|
|
567
641
|
count: int = 0
|
|
568
642
|
while count < shard_len and pr.bit_length() == n_bits:
|
|
569
643
|
if IsPrime(pr):
|
|
@@ -13,7 +13,8 @@ from typing import Self
|
|
|
13
13
|
|
|
14
14
|
import gmpy2
|
|
15
15
|
|
|
16
|
-
from . import aes,
|
|
16
|
+
from transcrypto.core import aes, hashes, key, modmath
|
|
17
|
+
from transcrypto.utils import base, saferandom
|
|
17
18
|
|
|
18
19
|
_SMALL_ENCRYPTION_EXPONENT = 7
|
|
19
20
|
_BIG_ENCRYPTION_EXPONENT = 2**16 + 1 # 65537
|
|
@@ -26,7 +27,7 @@ _RSA_SIGNATURE_HASH_PREFIX = b'transcrypto.RSA.Signature.1.0\x00'
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
29
|
-
class RSAPublicKey(
|
|
30
|
+
class RSAPublicKey(key.CryptoKey, key.Encryptor, key.Verifier):
|
|
30
31
|
"""RSA (Rivest-Shamir-Adleman) key, with the public part of the key.
|
|
31
32
|
|
|
32
33
|
No measures are taken here to prevent timing attacks.
|
|
@@ -48,7 +49,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
48
49
|
"""Check data.
|
|
49
50
|
|
|
50
51
|
Raises:
|
|
51
|
-
InputError: invalid inputs
|
|
52
|
+
base.InputError: invalid inputs
|
|
52
53
|
|
|
53
54
|
"""
|
|
54
55
|
if self.public_modulus < 6 or modmath.IsPrime(self.public_modulus): # noqa: PLR2004
|
|
@@ -92,7 +93,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
92
93
|
ciphertext message (int, 1 ≤ c < modulus) = (m ** encrypt_exp) mod modulus
|
|
93
94
|
|
|
94
95
|
Raises:
|
|
95
|
-
InputError: invalid inputs
|
|
96
|
+
base.InputError: invalid inputs
|
|
96
97
|
|
|
97
98
|
"""
|
|
98
99
|
# test inputs
|
|
@@ -128,13 +129,13 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
128
129
|
"""
|
|
129
130
|
# generate random r and encrypt it
|
|
130
131
|
r: int = 0
|
|
131
|
-
while not 1 < r < self.public_modulus or
|
|
132
|
-
r =
|
|
132
|
+
while not 1 < r < self.public_modulus or modmath.GCD(r, self.public_modulus) != 1:
|
|
133
|
+
r = saferandom.RandBits(self.public_modulus.bit_length())
|
|
133
134
|
k: int = self.modulus_size
|
|
134
135
|
ct: bytes = base.IntToFixedBytes(self.RawEncrypt(r), k)
|
|
135
136
|
assert len(ct) == k, 'should never happen: c_kem should be exactly k bytes' # noqa: S101
|
|
136
137
|
# encrypt plaintext with AES-256-GCM using SHA512(r)[32:] as key; return ct || Encrypt(...)
|
|
137
|
-
ss: bytes =
|
|
138
|
+
ss: bytes = hashes.Hash512(base.IntToFixedBytes(r, k))
|
|
138
139
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
139
140
|
aad_prime: bytes = _RSA_ENCRYPTION_AAD_PREFIX + base.IntToFixedBytes(len(aad), 8) + aad + ct
|
|
140
141
|
return ct + aes.AESKey(key256=ss[32:]).Encrypt(plaintext, associated_data=aad_prime)
|
|
@@ -172,16 +173,16 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
172
173
|
Hash512("prefix" || len(aad) || aad || message || salt)
|
|
173
174
|
|
|
174
175
|
Raises:
|
|
175
|
-
CryptoError: hash output is out of range
|
|
176
|
+
key.CryptoError: hash output is out of range
|
|
176
177
|
|
|
177
178
|
"""
|
|
178
179
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
179
180
|
la: bytes = base.IntToFixedBytes(len(aad), 8)
|
|
180
181
|
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes' # noqa: PLR2004, S101
|
|
181
|
-
y: int = base.BytesToInt(
|
|
182
|
-
if not 1 < y < self.public_modulus or
|
|
182
|
+
y: int = base.BytesToInt(hashes.Hash512(_RSA_SIGNATURE_HASH_PREFIX + la + aad + message + salt))
|
|
183
|
+
if not 1 < y < self.public_modulus or modmath.GCD(y, self.public_modulus) != 1:
|
|
183
184
|
# will only reasonably happen if modulus is small
|
|
184
|
-
raise
|
|
185
|
+
raise key.CryptoError(f'hash output {y} is out of range/invalid {self.public_modulus}')
|
|
185
186
|
return y
|
|
186
187
|
|
|
187
188
|
def Verify(
|
|
@@ -204,7 +205,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
204
205
|
True if signature is valid, False otherwise
|
|
205
206
|
|
|
206
207
|
Raises:
|
|
207
|
-
InputError: invalid inputs
|
|
208
|
+
base.InputError: invalid inputs
|
|
208
209
|
|
|
209
210
|
"""
|
|
210
211
|
k: int = self.modulus_size
|
|
@@ -257,8 +258,8 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
257
258
|
"""Check data.
|
|
258
259
|
|
|
259
260
|
Raises:
|
|
260
|
-
InputError: invalid inputs
|
|
261
|
-
CryptoError: modulus math is inconsistent with values
|
|
261
|
+
base.InputError: invalid inputs
|
|
262
|
+
key.CryptoError: modulus math is inconsistent with values
|
|
262
263
|
|
|
263
264
|
"""
|
|
264
265
|
super(RSAObfuscationPair, self).__post_init__()
|
|
@@ -269,7 +270,7 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
269
270
|
):
|
|
270
271
|
raise base.InputError(f'invalid keys: {self}')
|
|
271
272
|
if (self.random_key * self.key_inverse) % self.public_modulus != 1:
|
|
272
|
-
raise
|
|
273
|
+
raise key.CryptoError(f'inconsistent keys: {self}')
|
|
273
274
|
|
|
274
275
|
def __str__(self) -> str:
|
|
275
276
|
"""Safe (no secrets) string representation of the RSAObfuscationPair.
|
|
@@ -281,8 +282,8 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
281
282
|
return (
|
|
282
283
|
'RSAObfuscationPair('
|
|
283
284
|
f'{super(RSAObfuscationPair, self).__str__()}, '
|
|
284
|
-
f'random_key={
|
|
285
|
-
f'key_inverse={
|
|
285
|
+
f'random_key={hashes.ObfuscateSecret(self.random_key)}, '
|
|
286
|
+
f'key_inverse={hashes.ObfuscateSecret(self.key_inverse)})'
|
|
286
287
|
)
|
|
287
288
|
|
|
288
289
|
def ObfuscateMessage(self, message: int, /) -> int:
|
|
@@ -297,7 +298,7 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
297
298
|
obfuscated message (int, 1 ≤ o < modulus) = (m * (random_key ** encrypt_exp)) mod modulus
|
|
298
299
|
|
|
299
300
|
Raises:
|
|
300
|
-
InputError: invalid inputs
|
|
301
|
+
base.InputError: invalid inputs
|
|
301
302
|
|
|
302
303
|
"""
|
|
303
304
|
# test inputs
|
|
@@ -322,31 +323,31 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
322
323
|
signature * key_inverse mod modulus
|
|
323
324
|
|
|
324
325
|
Raises:
|
|
325
|
-
CryptoError: some signatures were invalid (either plain or obfuscated)
|
|
326
|
+
key.CryptoError: some signatures were invalid (either plain or obfuscated)
|
|
326
327
|
|
|
327
328
|
"""
|
|
328
329
|
# verify that obfuscated signature is valid
|
|
329
330
|
obfuscated: int = self.ObfuscateMessage(message)
|
|
330
331
|
if not self.RawVerify(obfuscated, signature):
|
|
331
|
-
raise
|
|
332
|
+
raise key.CryptoError(f'obfuscated message was not signed: {message=} ; {signature=}')
|
|
332
333
|
# compute signature for original message and check it
|
|
333
334
|
original: int = (signature * self.key_inverse) % self.public_modulus
|
|
334
335
|
if not self.RawVerify(message, original):
|
|
335
|
-
raise
|
|
336
|
+
raise key.CryptoError(f'failed signature recovery: {message=} ; {signature=}')
|
|
336
337
|
return original
|
|
337
338
|
|
|
338
339
|
@classmethod
|
|
339
|
-
def New(cls,
|
|
340
|
-
"""Generate new obfuscation pair for this `
|
|
340
|
+
def New(cls, rsa_key: RSAPublicKey, /) -> Self:
|
|
341
|
+
"""Generate new obfuscation pair for this `rsa_key`, respecting the size of the public modulus.
|
|
341
342
|
|
|
342
343
|
Args:
|
|
343
|
-
|
|
344
|
+
rsa_key (RSAPublicKey): public RSA key to use as base for a new RSAObfuscationPair
|
|
344
345
|
|
|
345
346
|
Returns:
|
|
346
347
|
RSAObfuscationPair object ready for use
|
|
347
348
|
|
|
348
349
|
Raises:
|
|
349
|
-
CryptoError: failed generation
|
|
350
|
+
key.CryptoError: failed generation
|
|
350
351
|
|
|
351
352
|
"""
|
|
352
353
|
# find a suitable random key based on the bit_length
|
|
@@ -356,29 +357,29 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
356
357
|
while (
|
|
357
358
|
not random_key
|
|
358
359
|
or not key_inverse
|
|
359
|
-
or random_key in {
|
|
360
|
-
or key_inverse ==
|
|
360
|
+
or random_key in {rsa_key.encrypt_exp, key_inverse}
|
|
361
|
+
or key_inverse == rsa_key.encrypt_exp
|
|
361
362
|
):
|
|
362
|
-
random_key =
|
|
363
|
+
random_key = saferandom.RandBits(rsa_key.public_modulus.bit_length() - 1)
|
|
363
364
|
try:
|
|
364
|
-
key_inverse = modmath.ModInv(random_key,
|
|
365
|
+
key_inverse = modmath.ModInv(random_key, rsa_key.public_modulus)
|
|
365
366
|
except modmath.ModularDivideError as err:
|
|
366
367
|
key_inverse = 0
|
|
367
368
|
failures += 1
|
|
368
369
|
if failures >= _MAX_KEY_GENERATION_FAILURES:
|
|
369
|
-
raise
|
|
370
|
+
raise key.CryptoError(f'failed key generation {failures} times') from err
|
|
370
371
|
logging.warning(err)
|
|
371
372
|
# build object
|
|
372
373
|
return cls(
|
|
373
|
-
public_modulus=
|
|
374
|
-
encrypt_exp=
|
|
374
|
+
public_modulus=rsa_key.public_modulus,
|
|
375
|
+
encrypt_exp=rsa_key.encrypt_exp,
|
|
375
376
|
random_key=random_key,
|
|
376
377
|
key_inverse=key_inverse,
|
|
377
378
|
)
|
|
378
379
|
|
|
379
380
|
|
|
380
381
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
381
|
-
class RSAPrivateKey(RSAPublicKey,
|
|
382
|
+
class RSAPrivateKey(RSAPublicKey, key.Decryptor, key.Signer):
|
|
382
383
|
"""RSA (Rivest-Shamir-Adleman) private key.
|
|
383
384
|
|
|
384
385
|
No measures are taken here to prevent timing attacks.
|
|
@@ -409,8 +410,8 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
409
410
|
"""Check data.
|
|
410
411
|
|
|
411
412
|
Raises:
|
|
412
|
-
InputError: invalid inputs
|
|
413
|
-
CryptoError: modulus math is inconsistent with values
|
|
413
|
+
base.InputError: invalid inputs
|
|
414
|
+
key.CryptoError: modulus math is inconsistent with values
|
|
414
415
|
|
|
415
416
|
"""
|
|
416
417
|
super(RSAPrivateKey, self).__post_init__()
|
|
@@ -438,15 +439,15 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
438
439
|
if self.remainder_p < 2 or self.remainder_q < 2 or self.q_inverse_p < 2: # noqa: PLR2004
|
|
439
440
|
raise base.InputError(f'trivial remainder_p/remainder_q/q_inverse_p: {self}')
|
|
440
441
|
if self.modulus_p * self.modulus_q != self.public_modulus:
|
|
441
|
-
raise
|
|
442
|
+
raise key.CryptoError(f'inconsistent modulus_p * modulus_q: {self}')
|
|
442
443
|
if (self.encrypt_exp * self.decrypt_exp) % phi != 1:
|
|
443
|
-
raise
|
|
444
|
+
raise key.CryptoError(f'inconsistent exponents: {self}')
|
|
444
445
|
if (
|
|
445
446
|
self.remainder_p != self.decrypt_exp % (self.modulus_p - 1)
|
|
446
447
|
or self.remainder_q != self.decrypt_exp % (self.modulus_q - 1)
|
|
447
448
|
or (self.q_inverse_p * self.modulus_q) % self.modulus_p != 1
|
|
448
449
|
):
|
|
449
|
-
raise
|
|
450
|
+
raise key.CryptoError(f'inconsistent speedup remainder_p/remainder_q/q_inverse_p: {self}')
|
|
450
451
|
|
|
451
452
|
def __str__(self) -> str:
|
|
452
453
|
"""Safe (no secrets) string representation of the RSAPrivateKey.
|
|
@@ -458,9 +459,9 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
458
459
|
return (
|
|
459
460
|
'RSAPrivateKey('
|
|
460
461
|
f'{super(RSAPrivateKey, self).__str__()}, '
|
|
461
|
-
f'modulus_p={
|
|
462
|
-
f'modulus_q={
|
|
463
|
-
f'decrypt_exp={
|
|
462
|
+
f'modulus_p={hashes.ObfuscateSecret(self.modulus_p)}, '
|
|
463
|
+
f'modulus_q={hashes.ObfuscateSecret(self.modulus_q)}, '
|
|
464
|
+
f'decrypt_exp={hashes.ObfuscateSecret(self.decrypt_exp)})'
|
|
464
465
|
)
|
|
465
466
|
|
|
466
467
|
def RawDecrypt(self, ciphertext: int, /) -> int:
|
|
@@ -477,7 +478,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
477
478
|
decrypted message (int, 1 ≤ m < modulus) = (m ** decrypt_exp) mod modulus
|
|
478
479
|
|
|
479
480
|
Raises:
|
|
480
|
-
InputError: invalid inputs
|
|
481
|
+
base.InputError: invalid inputs
|
|
481
482
|
|
|
482
483
|
"""
|
|
483
484
|
# test inputs
|
|
@@ -509,7 +510,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
509
510
|
bytes: Decrypted plaintext bytes
|
|
510
511
|
|
|
511
512
|
Raises:
|
|
512
|
-
InputError: invalid inputs
|
|
513
|
+
base.InputError: invalid inputs
|
|
513
514
|
|
|
514
515
|
"""
|
|
515
516
|
k: int = self.modulus_size
|
|
@@ -518,7 +519,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
518
519
|
# split ciphertext in two parts: the first k bytes is ct, the rest is AES-256-GCM
|
|
519
520
|
rsa_ct, aes_ct = ciphertext[:k], ciphertext[k:]
|
|
520
521
|
r: int = self.RawDecrypt(base.BytesToInt(rsa_ct))
|
|
521
|
-
ss: bytes =
|
|
522
|
+
ss: bytes = hashes.Hash512(base.IntToFixedBytes(r, k))
|
|
522
523
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
523
524
|
aad_prime: bytes = _RSA_ENCRYPTION_AAD_PREFIX + base.IntToFixedBytes(len(aad), 8) + aad + rsa_ct
|
|
524
525
|
return aes.AESKey(key256=ss[32:]).Decrypt(aes_ct, associated_data=aad_prime)
|
|
@@ -538,7 +539,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
538
539
|
identical to Decrypt()
|
|
539
540
|
|
|
540
541
|
Raises:
|
|
541
|
-
InputError: invalid inputs
|
|
542
|
+
base.InputError: invalid inputs
|
|
542
543
|
|
|
543
544
|
"""
|
|
544
545
|
# test inputs
|
|
@@ -569,13 +570,13 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
569
570
|
bytes: Signature; salt || Padded(s, k) - see above
|
|
570
571
|
|
|
571
572
|
Raises:
|
|
572
|
-
InputError: invalid inputs
|
|
573
|
+
base.InputError: invalid inputs
|
|
573
574
|
|
|
574
575
|
"""
|
|
575
576
|
k: int = self.modulus_size
|
|
576
577
|
if k <= 64: # noqa: PLR2004
|
|
577
578
|
raise base.InputError(f'modulus too small for signing operations: {k} bytes')
|
|
578
|
-
salt: bytes =
|
|
579
|
+
salt: bytes = saferandom.RandBytes(64)
|
|
579
580
|
s_int: int = self.RawSign(self._DomainSeparatedHash(message, associated_data, salt))
|
|
580
581
|
s_bytes: bytes = base.IntToFixedBytes(s_int, k)
|
|
581
582
|
assert len(s_bytes) == k, 'should never happen: s_bytes should be exactly k bytes' # noqa: S101
|
|
@@ -592,8 +593,8 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
592
593
|
RSAPrivateKey object ready for use
|
|
593
594
|
|
|
594
595
|
Raises:
|
|
595
|
-
InputError: invalid inputs
|
|
596
|
-
CryptoError: failed generation
|
|
596
|
+
base.InputError: invalid inputs
|
|
597
|
+
key.CryptoError: failed generation
|
|
597
598
|
|
|
598
599
|
"""
|
|
599
600
|
# test inputs
|
|
@@ -632,5 +633,5 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
|
632
633
|
except (base.InputError, modmath.ModularDivideError) as err:
|
|
633
634
|
failures += 1
|
|
634
635
|
if failures >= _MAX_KEY_GENERATION_FAILURES:
|
|
635
|
-
raise
|
|
636
|
+
raise key.CryptoError(f'failed key generation {failures} times') from err
|
|
636
637
|
logging.warning(err)
|