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/rsa.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 RSA (Rivest-Shamir-Adleman) library.
|
|
6
4
|
|
|
7
5
|
<https://en.wikipedia.org/wiki/RSA_cryptosystem>
|
|
@@ -11,20 +9,14 @@ from __future__ import annotations
|
|
|
11
9
|
|
|
12
10
|
import dataclasses
|
|
13
11
|
import logging
|
|
14
|
-
# import pdb
|
|
15
12
|
from typing import Self
|
|
16
13
|
|
|
17
|
-
import gmpy2
|
|
18
|
-
|
|
19
|
-
from . import base, modmath, aes
|
|
20
|
-
|
|
21
|
-
__author__ = 'balparda@github.com'
|
|
22
|
-
__version__: str = base.__version__ # version comes from base!
|
|
23
|
-
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
14
|
+
import gmpy2
|
|
24
15
|
|
|
16
|
+
from . import aes, base, modmath
|
|
25
17
|
|
|
26
18
|
_SMALL_ENCRYPTION_EXPONENT = 7
|
|
27
|
-
_BIG_ENCRYPTION_EXPONENT = 2
|
|
19
|
+
_BIG_ENCRYPTION_EXPONENT = 2**16 + 1 # 65537
|
|
28
20
|
|
|
29
21
|
_MAX_KEY_GENERATION_FAILURES = 15
|
|
30
22
|
|
|
@@ -46,6 +38,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
46
38
|
Attributes:
|
|
47
39
|
public_modulus (int): modulus (p * q), ≥ 6
|
|
48
40
|
encrypt_exp (int): encryption exponent, 3 ≤ e < modulus, (e * decrypt) % ((p-1) * (q-1)) == 1
|
|
41
|
+
|
|
49
42
|
"""
|
|
50
43
|
|
|
51
44
|
public_modulus: int
|
|
@@ -56,13 +49,13 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
56
49
|
|
|
57
50
|
Raises:
|
|
58
51
|
InputError: invalid inputs
|
|
52
|
+
|
|
59
53
|
"""
|
|
60
|
-
|
|
61
|
-
if self.public_modulus < 6 or modmath.IsPrime(self.public_modulus):
|
|
54
|
+
if self.public_modulus < 6 or modmath.IsPrime(self.public_modulus): # noqa: PLR2004
|
|
62
55
|
# only a full factors check can prove modulus is product of only 2 primes, which is impossible
|
|
63
56
|
# to do for large numbers here; the private key checks the relationship though
|
|
64
57
|
raise base.InputError(f'invalid public_modulus: {self}')
|
|
65
|
-
if not 2 < self.encrypt_exp < self.public_modulus or not modmath.IsPrime(self.encrypt_exp):
|
|
58
|
+
if not 2 < self.encrypt_exp < self.public_modulus or not modmath.IsPrime(self.encrypt_exp): # noqa: PLR2004
|
|
66
59
|
# technically, encrypt_exp < phi, but again the private key tests for this explicitly
|
|
67
60
|
raise base.InputError(f'invalid encrypt_exp: {self}')
|
|
68
61
|
|
|
@@ -71,11 +64,14 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
71
64
|
|
|
72
65
|
Returns:
|
|
73
66
|
string representation of RSAPublicKey
|
|
67
|
+
|
|
74
68
|
"""
|
|
75
|
-
return (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
return (
|
|
70
|
+
'RSAPublicKey('
|
|
71
|
+
f'bits={self.public_modulus.bit_length()}, '
|
|
72
|
+
f'public_modulus={base.IntToEncoded(self.public_modulus)}, '
|
|
73
|
+
f'encrypt_exp={base.IntToEncoded(self.encrypt_exp)})'
|
|
74
|
+
)
|
|
79
75
|
|
|
80
76
|
@property
|
|
81
77
|
def modulus_size(self) -> int:
|
|
@@ -97,12 +93,13 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
97
93
|
|
|
98
94
|
Raises:
|
|
99
95
|
InputError: invalid inputs
|
|
96
|
+
|
|
100
97
|
"""
|
|
101
98
|
# test inputs
|
|
102
99
|
if not 0 < message < self.public_modulus:
|
|
103
100
|
raise base.InputError(f'invalid message: {message=}')
|
|
104
101
|
# encrypt
|
|
105
|
-
return int(gmpy2.powmod(message, self.encrypt_exp, self.public_modulus))
|
|
102
|
+
return int(gmpy2.powmod(message, self.encrypt_exp, self.public_modulus))
|
|
106
103
|
|
|
107
104
|
def Encrypt(self, plaintext: bytes, /, *, associated_data: bytes | None = None) -> bytes:
|
|
108
105
|
"""Encrypt `plaintext` and return `ciphertext`.
|
|
@@ -128,9 +125,6 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
128
125
|
Padded(ct, k) + AES-256-GCM(key=SHA512(r)[32:], plaintext,
|
|
129
126
|
associated_data="prefix" + len(aad) + aad + Padded(ct, k))
|
|
130
127
|
|
|
131
|
-
Raises:
|
|
132
|
-
InputError: invalid inputs
|
|
133
|
-
CryptoError: internal crypto failures
|
|
134
128
|
"""
|
|
135
129
|
# generate random r and encrypt it
|
|
136
130
|
r: int = 0
|
|
@@ -138,7 +132,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
138
132
|
r = base.RandBits(self.public_modulus.bit_length())
|
|
139
133
|
k: int = self.modulus_size
|
|
140
134
|
ct: bytes = base.IntToFixedBytes(self.RawEncrypt(r), k)
|
|
141
|
-
assert len(ct) == k, 'should never happen: c_kem should be exactly k bytes'
|
|
135
|
+
assert len(ct) == k, 'should never happen: c_kem should be exactly k bytes' # noqa: S101
|
|
142
136
|
# encrypt plaintext with AES-256-GCM using SHA512(r)[32:] as key; return ct || Encrypt(...)
|
|
143
137
|
ss: bytes = base.Hash512(base.IntToFixedBytes(r, k))
|
|
144
138
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
@@ -160,13 +154,12 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
160
154
|
True if signature is valid, False otherwise;
|
|
161
155
|
(signature ** encrypt_exp) mod modulus == message
|
|
162
156
|
|
|
163
|
-
Raises:
|
|
164
|
-
InputError: invalid inputs
|
|
165
157
|
"""
|
|
166
158
|
return self.RawEncrypt(signature) == message
|
|
167
159
|
|
|
168
160
|
def _DomainSeparatedHash(
|
|
169
|
-
|
|
161
|
+
self, message: bytes, associated_data: bytes | None, salt: bytes, /
|
|
162
|
+
) -> int:
|
|
170
163
|
"""Compute the domain-separated hash for signing and verifying.
|
|
171
164
|
|
|
172
165
|
Args:
|
|
@@ -180,10 +173,11 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
180
173
|
|
|
181
174
|
Raises:
|
|
182
175
|
CryptoError: hash output is out of range
|
|
176
|
+
|
|
183
177
|
"""
|
|
184
178
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
185
179
|
la: bytes = base.IntToFixedBytes(len(aad), 8)
|
|
186
|
-
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes'
|
|
180
|
+
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes' # noqa: PLR2004, S101
|
|
187
181
|
y: int = base.BytesToInt(base.Hash512(_RSA_SIGNATURE_HASH_PREFIX + la + aad + message + salt))
|
|
188
182
|
if not 1 < y < self.public_modulus or base.GCD(y, self.public_modulus) != 1:
|
|
189
183
|
# will only reasonably happen if modulus is small
|
|
@@ -191,7 +185,8 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
191
185
|
return y
|
|
192
186
|
|
|
193
187
|
def Verify(
|
|
194
|
-
|
|
188
|
+
self, message: bytes, signature: bytes, /, *, associated_data: bytes | None = None
|
|
189
|
+
) -> bool:
|
|
195
190
|
"""Verify a `signature` for `message`. True if OK; False if failed verification.
|
|
196
191
|
|
|
197
192
|
• Let k = ceil(log2(n))/8 be the modulus size in bytes.
|
|
@@ -210,25 +205,34 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
210
205
|
|
|
211
206
|
Raises:
|
|
212
207
|
InputError: invalid inputs
|
|
213
|
-
|
|
208
|
+
|
|
214
209
|
"""
|
|
215
210
|
k: int = self.modulus_size
|
|
216
|
-
if k <= 64:
|
|
211
|
+
if k <= 64: # noqa: PLR2004
|
|
217
212
|
raise base.InputError(f'modulus too small for signing operations: {k} bytes')
|
|
218
213
|
if len(signature) != (64 + k):
|
|
219
214
|
logging.info(f'invalid signature length: {len(signature)} ; expected {64 + k}')
|
|
220
215
|
return False
|
|
221
216
|
try:
|
|
222
217
|
return self.RawVerify(
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
self._DomainSeparatedHash(message, associated_data, signature[:64]),
|
|
219
|
+
base.BytesToInt(signature[64:]),
|
|
220
|
+
)
|
|
225
221
|
except base.InputError as err:
|
|
226
222
|
logging.info(err)
|
|
227
223
|
return False
|
|
228
224
|
|
|
229
225
|
@classmethod
|
|
230
226
|
def Copy(cls, other: RSAPublicKey, /) -> Self:
|
|
231
|
-
"""Initialize a public key by taking the public parts of a public/private key.
|
|
227
|
+
"""Initialize a public key by taking the public parts of a public/private key.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
other (RSAPublicKey): object to copy from
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Self: a new RSAPublicKey
|
|
234
|
+
|
|
235
|
+
"""
|
|
232
236
|
return cls(public_modulus=other.public_modulus, encrypt_exp=other.encrypt_exp)
|
|
233
237
|
|
|
234
238
|
|
|
@@ -243,6 +247,7 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
243
247
|
Attributes:
|
|
244
248
|
random_key (int): random value key, 2 ≤ k < modulus
|
|
245
249
|
key_inverse (int): inverse for `random_key` in relation to the RSA public key, 2 ≤ i < modulus
|
|
250
|
+
|
|
246
251
|
"""
|
|
247
252
|
|
|
248
253
|
random_key: int
|
|
@@ -254,11 +259,14 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
254
259
|
Raises:
|
|
255
260
|
InputError: invalid inputs
|
|
256
261
|
CryptoError: modulus math is inconsistent with values
|
|
262
|
+
|
|
257
263
|
"""
|
|
258
|
-
super(RSAObfuscationPair, self).__post_init__()
|
|
259
|
-
if (
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
super(RSAObfuscationPair, self).__post_init__()
|
|
265
|
+
if (
|
|
266
|
+
not 1 < self.random_key < self.public_modulus
|
|
267
|
+
or not 1 < self.key_inverse < self.public_modulus
|
|
268
|
+
or self.random_key in {self.key_inverse, self.encrypt_exp, self.public_modulus}
|
|
269
|
+
):
|
|
262
270
|
raise base.InputError(f'invalid keys: {self}')
|
|
263
271
|
if (self.random_key * self.key_inverse) % self.public_modulus != 1:
|
|
264
272
|
raise base.CryptoError(f'inconsistent keys: {self}')
|
|
@@ -268,11 +276,14 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
268
276
|
|
|
269
277
|
Returns:
|
|
270
278
|
string representation of RSAObfuscationPair without leaking secrets
|
|
279
|
+
|
|
271
280
|
"""
|
|
272
|
-
return (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
281
|
+
return (
|
|
282
|
+
'RSAObfuscationPair('
|
|
283
|
+
f'{super(RSAObfuscationPair, self).__str__()}, '
|
|
284
|
+
f'random_key={base.ObfuscateSecret(self.random_key)}, '
|
|
285
|
+
f'key_inverse={base.ObfuscateSecret(self.key_inverse)})'
|
|
286
|
+
)
|
|
276
287
|
|
|
277
288
|
def ObfuscateMessage(self, message: int, /) -> int:
|
|
278
289
|
"""Convert message to an obfuscated message to be signed by this key's owner.
|
|
@@ -287,13 +298,15 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
287
298
|
|
|
288
299
|
Raises:
|
|
289
300
|
InputError: invalid inputs
|
|
301
|
+
|
|
290
302
|
"""
|
|
291
303
|
# test inputs
|
|
292
304
|
if not 0 < message < self.public_modulus:
|
|
293
305
|
raise base.InputError(f'invalid message: {message=}')
|
|
294
306
|
# encrypt
|
|
295
|
-
return (
|
|
296
|
-
|
|
307
|
+
return (
|
|
308
|
+
message * int(gmpy2.powmod(self.random_key, self.encrypt_exp, self.public_modulus))
|
|
309
|
+
) % self.public_modulus
|
|
297
310
|
|
|
298
311
|
def RevealOriginalSignature(self, message: int, signature: int, /) -> int:
|
|
299
312
|
"""Recover original signature for `message` from obfuscated `signature`.
|
|
@@ -309,8 +322,8 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
309
322
|
signature * key_inverse mod modulus
|
|
310
323
|
|
|
311
324
|
Raises:
|
|
312
|
-
InputError: invalid inputs
|
|
313
325
|
CryptoError: some signatures were invalid (either plain or obfuscated)
|
|
326
|
+
|
|
314
327
|
"""
|
|
315
328
|
# verify that obfuscated signature is valid
|
|
316
329
|
obfuscated: int = self.ObfuscateMessage(message)
|
|
@@ -324,7 +337,7 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
324
337
|
|
|
325
338
|
@classmethod
|
|
326
339
|
def New(cls, key: RSAPublicKey, /) -> Self:
|
|
327
|
-
"""
|
|
340
|
+
"""Generate new obfuscation pair for this `key`, respecting the size of the public modulus.
|
|
328
341
|
|
|
329
342
|
Args:
|
|
330
343
|
key (RSAPublicKey): public RSA key to use as base for a new RSAObfuscationPair
|
|
@@ -334,15 +347,18 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
334
347
|
|
|
335
348
|
Raises:
|
|
336
349
|
CryptoError: failed generation
|
|
350
|
+
|
|
337
351
|
"""
|
|
338
352
|
# find a suitable random key based on the bit_length
|
|
339
353
|
random_key: int = 0
|
|
340
354
|
key_inverse: int = 0
|
|
341
355
|
failures: int = 0
|
|
342
|
-
while (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
356
|
+
while (
|
|
357
|
+
not random_key
|
|
358
|
+
or not key_inverse
|
|
359
|
+
or random_key in {key.encrypt_exp, key_inverse}
|
|
360
|
+
or key_inverse == key.encrypt_exp
|
|
361
|
+
):
|
|
346
362
|
random_key = base.RandBits(key.public_modulus.bit_length() - 1)
|
|
347
363
|
try:
|
|
348
364
|
key_inverse = modmath.ModInv(random_key, key.public_modulus)
|
|
@@ -354,12 +370,15 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
354
370
|
logging.warning(err)
|
|
355
371
|
# build object
|
|
356
372
|
return cls(
|
|
357
|
-
|
|
358
|
-
|
|
373
|
+
public_modulus=key.public_modulus,
|
|
374
|
+
encrypt_exp=key.encrypt_exp,
|
|
375
|
+
random_key=random_key,
|
|
376
|
+
key_inverse=key_inverse,
|
|
377
|
+
)
|
|
359
378
|
|
|
360
379
|
|
|
361
380
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
362
|
-
class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
381
|
+
class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer):
|
|
363
382
|
"""RSA (Rivest-Shamir-Adleman) private key.
|
|
364
383
|
|
|
365
384
|
No measures are taken here to prevent timing attacks.
|
|
@@ -375,6 +394,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
375
394
|
remainder_p (int): pre-computed, = d % (p - 1), 2 ≤ r_p < modulus
|
|
376
395
|
remainder_q (int): pre-computed, = d % (q - 1), 2 ≤ r_q < modulus
|
|
377
396
|
q_inverse_p (int): pre-computed, = ModInv(q, p), 2 ≤ q_i_p < modulus
|
|
397
|
+
|
|
378
398
|
"""
|
|
379
399
|
|
|
380
400
|
modulus_p: int
|
|
@@ -391,34 +411,41 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
391
411
|
Raises:
|
|
392
412
|
InputError: invalid inputs
|
|
393
413
|
CryptoError: modulus math is inconsistent with values
|
|
414
|
+
|
|
394
415
|
"""
|
|
395
|
-
super(RSAPrivateKey, self).__post_init__()
|
|
416
|
+
super(RSAPrivateKey, self).__post_init__()
|
|
396
417
|
phi: int = (self.modulus_p - 1) * (self.modulus_q - 1)
|
|
397
418
|
min_prime_distance: int = 1 << (self.public_modulus.bit_length() // 4) # ≈ n**(1/4)
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
419
|
+
if (
|
|
420
|
+
self.modulus_p < 2 # noqa: PLR0916, PLR2004
|
|
421
|
+
or not modmath.IsPrime(self.modulus_p)
|
|
422
|
+
or self.modulus_q < 3 # noqa: PLR2004
|
|
423
|
+
or not modmath.IsPrime(self.modulus_q)
|
|
424
|
+
or self.modulus_q <= self.modulus_p
|
|
425
|
+
or (self.modulus_q - self.modulus_p) < min_prime_distance
|
|
426
|
+
or self.encrypt_exp in {self.modulus_p, self.modulus_q}
|
|
427
|
+
or self.encrypt_exp >= phi
|
|
428
|
+
or self.decrypt_exp in {self.encrypt_exp, self.modulus_p, self.modulus_q, phi}
|
|
429
|
+
):
|
|
405
430
|
# encrypt_exp has to be less than phi;
|
|
406
|
-
# if p
|
|
431
|
+
# if p - q < 2*(n**(1/4)) then solving for p and q is trivial
|
|
407
432
|
raise base.InputError(f'invalid modulus_p or modulus_q: {self}')
|
|
408
433
|
min_decrypt_length: int = self.public_modulus.bit_length() // 2 + 1
|
|
409
|
-
if not (2
|
|
434
|
+
if not (2**min_decrypt_length) < self.decrypt_exp < self.public_modulus:
|
|
410
435
|
# if decrypt_exp < public_modulus**(1/4)/3, then decrypt_exp can be computed efficiently
|
|
411
436
|
# from public_modulus and encrypt_exp so we make sure it is larger than public_modulus**(1/2)
|
|
412
437
|
raise base.InputError(f'invalid decrypt_exp: {self}')
|
|
413
|
-
if self.remainder_p < 2 or self.
|
|
438
|
+
if self.remainder_p < 2 or self.remainder_q < 2 or self.q_inverse_p < 2: # noqa: PLR2004
|
|
414
439
|
raise base.InputError(f'trivial remainder_p/remainder_q/q_inverse_p: {self}')
|
|
415
440
|
if self.modulus_p * self.modulus_q != self.public_modulus:
|
|
416
441
|
raise base.CryptoError(f'inconsistent modulus_p * modulus_q: {self}')
|
|
417
442
|
if (self.encrypt_exp * self.decrypt_exp) % phi != 1:
|
|
418
443
|
raise base.CryptoError(f'inconsistent exponents: {self}')
|
|
419
|
-
if (
|
|
420
|
-
|
|
421
|
-
|
|
444
|
+
if (
|
|
445
|
+
self.remainder_p != self.decrypt_exp % (self.modulus_p - 1)
|
|
446
|
+
or self.remainder_q != self.decrypt_exp % (self.modulus_q - 1)
|
|
447
|
+
or (self.q_inverse_p * self.modulus_q) % self.modulus_p != 1
|
|
448
|
+
):
|
|
422
449
|
raise base.CryptoError(f'inconsistent speedup remainder_p/remainder_q/q_inverse_p: {self}')
|
|
423
450
|
|
|
424
451
|
def __str__(self) -> str:
|
|
@@ -426,12 +453,15 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
426
453
|
|
|
427
454
|
Returns:
|
|
428
455
|
string representation of RSAPrivateKey without leaking secrets
|
|
456
|
+
|
|
429
457
|
"""
|
|
430
|
-
return (
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
458
|
+
return (
|
|
459
|
+
'RSAPrivateKey('
|
|
460
|
+
f'{super(RSAPrivateKey, self).__str__()}, '
|
|
461
|
+
f'modulus_p={base.ObfuscateSecret(self.modulus_p)}, '
|
|
462
|
+
f'modulus_q={base.ObfuscateSecret(self.modulus_q)}, '
|
|
463
|
+
f'decrypt_exp={base.ObfuscateSecret(self.decrypt_exp)})'
|
|
464
|
+
)
|
|
435
465
|
|
|
436
466
|
def RawDecrypt(self, ciphertext: int, /) -> int:
|
|
437
467
|
"""Decrypt `ciphertext` with this private key.
|
|
@@ -448,14 +478,15 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
448
478
|
|
|
449
479
|
Raises:
|
|
450
480
|
InputError: invalid inputs
|
|
481
|
+
|
|
451
482
|
"""
|
|
452
483
|
# test inputs
|
|
453
484
|
if not 0 <= ciphertext < self.public_modulus:
|
|
454
485
|
raise base.InputError(f'invalid message: {ciphertext=}')
|
|
455
486
|
# decrypt using CRT (Chinese Remainder Theorem); 4x speedup; all the below is equivalent
|
|
456
487
|
# of doing: return pow(ciphertext, self.decrypt_exp, self.public_modulus)
|
|
457
|
-
m_p: int = int(gmpy2.powmod(ciphertext % self.modulus_p, self.remainder_p, self.modulus_p))
|
|
458
|
-
m_q: int = int(gmpy2.powmod(ciphertext % self.modulus_q, self.remainder_q, self.modulus_q))
|
|
488
|
+
m_p: int = int(gmpy2.powmod(ciphertext % self.modulus_p, self.remainder_p, self.modulus_p))
|
|
489
|
+
m_q: int = int(gmpy2.powmod(ciphertext % self.modulus_q, self.remainder_q, self.modulus_q))
|
|
459
490
|
h: int = (self.q_inverse_p * (m_p - m_q)) % self.modulus_p
|
|
460
491
|
return (m_q + h * self.modulus_q) % self.public_modulus
|
|
461
492
|
|
|
@@ -479,7 +510,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
479
510
|
|
|
480
511
|
Raises:
|
|
481
512
|
InputError: invalid inputs
|
|
482
|
-
|
|
513
|
+
|
|
483
514
|
"""
|
|
484
515
|
k: int = self.modulus_size
|
|
485
516
|
if len(ciphertext) < (k + 32):
|
|
@@ -508,6 +539,7 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
508
539
|
|
|
509
540
|
Raises:
|
|
510
541
|
InputError: invalid inputs
|
|
542
|
+
|
|
511
543
|
"""
|
|
512
544
|
# test inputs
|
|
513
545
|
if not 0 < message < self.public_modulus:
|
|
@@ -538,15 +570,15 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
538
570
|
|
|
539
571
|
Raises:
|
|
540
572
|
InputError: invalid inputs
|
|
541
|
-
|
|
573
|
+
|
|
542
574
|
"""
|
|
543
575
|
k: int = self.modulus_size
|
|
544
|
-
if k <= 64:
|
|
576
|
+
if k <= 64: # noqa: PLR2004
|
|
545
577
|
raise base.InputError(f'modulus too small for signing operations: {k} bytes')
|
|
546
578
|
salt: bytes = base.RandBytes(64)
|
|
547
579
|
s_int: int = self.RawSign(self._DomainSeparatedHash(message, associated_data, salt))
|
|
548
580
|
s_bytes: bytes = base.IntToFixedBytes(s_int, k)
|
|
549
|
-
assert len(s_bytes) == k, 'should never happen: s_bytes should be exactly k bytes'
|
|
581
|
+
assert len(s_bytes) == k, 'should never happen: s_bytes should be exactly k bytes' # noqa: S101
|
|
550
582
|
return salt + s_bytes
|
|
551
583
|
|
|
552
584
|
@classmethod
|
|
@@ -562,9 +594,10 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
562
594
|
Raises:
|
|
563
595
|
InputError: invalid inputs
|
|
564
596
|
CryptoError: failed generation
|
|
597
|
+
|
|
565
598
|
"""
|
|
566
599
|
# test inputs
|
|
567
|
-
if bit_length < 11:
|
|
600
|
+
if bit_length < 11: # noqa: PLR2004
|
|
568
601
|
raise base.InputError(f'invalid bit length: {bit_length=}')
|
|
569
602
|
# generate primes / modulus
|
|
570
603
|
failures: int = 0
|
|
@@ -580,18 +613,21 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
580
613
|
modulus = p * q
|
|
581
614
|
# build object
|
|
582
615
|
phi: int = (p - 1) * (q - 1)
|
|
583
|
-
prime_exp: int = (
|
|
584
|
-
|
|
616
|
+
prime_exp: int = (
|
|
617
|
+
_SMALL_ENCRYPTION_EXPONENT
|
|
618
|
+
if phi <= _BIG_ENCRYPTION_EXPONENT
|
|
619
|
+
else _BIG_ENCRYPTION_EXPONENT
|
|
620
|
+
)
|
|
585
621
|
decrypt_exp: int = modmath.ModInv(prime_exp, phi)
|
|
586
622
|
return cls(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
623
|
+
modulus_p=p,
|
|
624
|
+
modulus_q=q,
|
|
625
|
+
public_modulus=modulus,
|
|
626
|
+
encrypt_exp=prime_exp,
|
|
627
|
+
decrypt_exp=decrypt_exp,
|
|
628
|
+
remainder_p=decrypt_exp % (p - 1),
|
|
629
|
+
remainder_q=decrypt_exp % (q - 1),
|
|
630
|
+
q_inverse_p=modmath.ModInv(q, p),
|
|
595
631
|
)
|
|
596
632
|
except (base.InputError, modmath.ModularDivideError) as err:
|
|
597
633
|
failures += 1
|