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
|
@@ -25,7 +25,8 @@ from cryptography.hazmat.primitives import hashes as hazmat_hashes
|
|
|
25
25
|
from cryptography.hazmat.primitives.ciphers import algorithms, modes
|
|
26
26
|
from cryptography.hazmat.primitives.kdf import pbkdf2 as hazmat_pbkdf2
|
|
27
27
|
|
|
28
|
-
from . import
|
|
28
|
+
from transcrypto.core import hashes, key
|
|
29
|
+
from transcrypto.utils import base, saferandom
|
|
29
30
|
|
|
30
31
|
# these fixed salt/iterations are for password->key generation only; NEVER use them to
|
|
31
32
|
# build a database of passwords because it would not be safe; NEVER change them or the
|
|
@@ -41,7 +42,7 @@ assert _PASSWORD_ITERATIONS == (6075308 + 1) // 3, 'should never happen: constan
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
44
|
-
class AESKey(
|
|
45
|
+
class AESKey(key.CryptoKey, key.Encryptor, key.Decryptor):
|
|
45
46
|
"""Advanced Encryption Standard (AES) 256 bits key (32 bytes).
|
|
46
47
|
|
|
47
48
|
No measures are taken here to prevent timing attacks.
|
|
@@ -57,7 +58,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
57
58
|
"""Check data.
|
|
58
59
|
|
|
59
60
|
Raises:
|
|
60
|
-
InputError: invalid inputs
|
|
61
|
+
base.InputError: invalid inputs
|
|
61
62
|
|
|
62
63
|
"""
|
|
63
64
|
if len(self.key256) != 32: # noqa: PLR2004
|
|
@@ -70,7 +71,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
70
71
|
string representation of AESKey without leaking secrets
|
|
71
72
|
|
|
72
73
|
"""
|
|
73
|
-
return f'AESKey(key256={
|
|
74
|
+
return f'AESKey(key256={hashes.ObfuscateSecret(self.key256)})'
|
|
74
75
|
|
|
75
76
|
@classmethod
|
|
76
77
|
def FromStaticPassword(cls, str_password: str, /) -> Self:
|
|
@@ -98,7 +99,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
98
99
|
AESKey crypto key to use (URL-safe base64-encoded 32-byte key)
|
|
99
100
|
|
|
100
101
|
Raises:
|
|
101
|
-
InputError: empty password
|
|
102
|
+
base.InputError: empty password
|
|
102
103
|
|
|
103
104
|
"""
|
|
104
105
|
str_password = str_password.strip()
|
|
@@ -112,7 +113,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
112
113
|
)
|
|
113
114
|
return cls(key256=kdf.derive(str_password.encode('utf-8')))
|
|
114
115
|
|
|
115
|
-
class ECBEncoderClass(
|
|
116
|
+
class ECBEncoderClass(key.Encryptor, key.Decryptor):
|
|
116
117
|
"""The simplest encryption possible (UNSAFE if misused): 128 bit block AES-ECB, 256 bit key.
|
|
117
118
|
|
|
118
119
|
Note: Due to ECB encoding, this class is only safe-ish for blocks of random-looking data,
|
|
@@ -162,7 +163,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
162
163
|
bytes: Ciphertext, a block of 128 bits (16 bytes)
|
|
163
164
|
|
|
164
165
|
Raises:
|
|
165
|
-
InputError: invalid inputs
|
|
166
|
+
base.InputError: invalid inputs
|
|
166
167
|
|
|
167
168
|
"""
|
|
168
169
|
if associated_data is not None:
|
|
@@ -189,7 +190,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
189
190
|
bytes: Decrypted plaintext, a block of 128 bits (16 bytes)
|
|
190
191
|
|
|
191
192
|
Raises:
|
|
192
|
-
InputError: invalid inputs
|
|
193
|
+
base.InputError: invalid inputs
|
|
193
194
|
|
|
194
195
|
"""
|
|
195
196
|
if associated_data is not None:
|
|
@@ -227,7 +228,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
227
228
|
str: encrypted hexadecimal block (length==64)
|
|
228
229
|
|
|
229
230
|
Raises:
|
|
230
|
-
InputError: invalid inputs
|
|
231
|
+
base.InputError: invalid inputs
|
|
231
232
|
|
|
232
233
|
"""
|
|
233
234
|
if len(plaintext_hex) != 64: # noqa: PLR2004
|
|
@@ -262,7 +263,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
262
263
|
str: plaintext hexadecimal block (length==64)
|
|
263
264
|
|
|
264
265
|
Raises:
|
|
265
|
-
InputError: invalid inputs
|
|
266
|
+
base.InputError: invalid inputs
|
|
266
267
|
|
|
267
268
|
"""
|
|
268
269
|
if len(ciphertext_hex) != 64: # noqa: PLR2004
|
|
@@ -297,7 +298,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
297
298
|
must encode it within the returned bytes (or document how to retrieve it)
|
|
298
299
|
|
|
299
300
|
"""
|
|
300
|
-
iv: bytes =
|
|
301
|
+
iv: bytes = saferandom.RandBytes(16)
|
|
301
302
|
cipher: ciphers.Cipher[modes.GCM] = ciphers.Cipher(
|
|
302
303
|
algorithms.AES256(self.key256), modes.GCM(iv)
|
|
303
304
|
)
|
|
@@ -339,12 +340,14 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
339
340
|
bytes: Decrypted plaintext bytes
|
|
340
341
|
|
|
341
342
|
Raises:
|
|
342
|
-
InputError: invalid inputs
|
|
343
|
-
CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
343
|
+
base.InputError: invalid inputs
|
|
344
|
+
key.CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
344
345
|
|
|
345
346
|
"""
|
|
346
347
|
if len(ciphertext) < 32: # noqa: PLR2004
|
|
347
348
|
raise base.InputError(f'AES256+GCM should have ≥32 bytes IV/CT/tag: {len(ciphertext)}')
|
|
349
|
+
iv: bytes
|
|
350
|
+
tag: bytes
|
|
348
351
|
iv, tag = ciphertext[:16], ciphertext[-16:]
|
|
349
352
|
decryptor: ciphers.CipherContext = ciphers.Cipher(
|
|
350
353
|
algorithms.AES256(self.key256), modes.GCM(iv, tag)
|
|
@@ -354,19 +357,4 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
354
357
|
try:
|
|
355
358
|
return decryptor.update(ciphertext[16:-16]) + decryptor.finalize()
|
|
356
359
|
except crypt_exceptions.InvalidTag as err:
|
|
357
|
-
raise
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def _TestCryptoKeyEncoding(obj: base.CryptoKey, tp: type[base.CryptoKey]) -> None: # pyright: ignore[reportUnusedFunction]
|
|
361
|
-
"""Test encoding for a CryptoKey instance. Only for use from test modules."""
|
|
362
|
-
assert tp.FromJSON(obj.json) == obj # noqa: S101
|
|
363
|
-
assert tp.FromJSON(obj.formatted_json) == obj # noqa: S101
|
|
364
|
-
assert tp.Load(obj.blob) == obj # noqa: S101
|
|
365
|
-
assert tp.Load(obj.encoded) == obj # noqa: S101
|
|
366
|
-
assert tp.Load(obj.hex) == obj # noqa: S101
|
|
367
|
-
assert tp.Load(obj.raw) == obj # noqa: S101
|
|
368
|
-
key = AESKey(key256=b'x' * 32)
|
|
369
|
-
assert tp.Load(obj.Blob(key=key), key=key) == obj # noqa: S101
|
|
370
|
-
assert tp.Load(obj.Encoded(key=key), key=key) == obj # noqa: S101
|
|
371
|
-
assert tp.Load(obj.Hex(key=key), key=key) == obj # noqa: S101
|
|
372
|
-
assert tp.Load(obj.Raw(key=key), key=key) == obj # noqa: S101
|
|
360
|
+
raise key.CryptoError('failed decryption') from err
|
transcrypto/core/bid.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Balparda's TransCrypto bidding protocols."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
from typing import Self
|
|
9
|
+
|
|
10
|
+
from transcrypto.core import hashes, key
|
|
11
|
+
from transcrypto.utils import base, saferandom
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
15
|
+
class PublicBid512(key.CryptoKey):
|
|
16
|
+
"""Public commitment to a (cryptographically secure) bid that can be revealed/validated later.
|
|
17
|
+
|
|
18
|
+
Bid is computed as: public_hash = Hash512(public_key || private_key || secret_bid)
|
|
19
|
+
|
|
20
|
+
Everything is bytes. The public part is (public_key, public_hash) and the private
|
|
21
|
+
part is (private_key, secret_bid). The whole computation can be checked later.
|
|
22
|
+
|
|
23
|
+
No measures are taken here to prevent timing attacks (probably not a concern).
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
public_key (bytes): 512-bits random value
|
|
27
|
+
public_hash (bytes): SHA-512 hash of (public_key || private_key || secret_bid)
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
public_key: bytes
|
|
32
|
+
public_hash: bytes
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
"""Check data.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
base.InputError: invalid inputs
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
if len(self.public_key) != 64 or len(self.public_hash) != 64: # noqa: PLR2004
|
|
42
|
+
raise base.InputError(f'invalid public_key or public_hash: {self}')
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""Safe string representation of the PublicBid.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
string representation of PublicBid
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return (
|
|
52
|
+
'PublicBid512('
|
|
53
|
+
f'public_key={base.BytesToEncoded(self.public_key)}, '
|
|
54
|
+
f'public_hash={base.BytesToHex(self.public_hash)})'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def VerifyBid(self, private_key: bytes, secret: bytes, /) -> bool:
|
|
58
|
+
"""Verify a bid. True if OK; False if failed verification.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
private_key (bytes): 512-bits private key
|
|
62
|
+
secret (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if bid is valid, False otherwise
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# creating the PrivateBid object will validate everything; InputError we allow to propagate
|
|
70
|
+
PrivateBid512(
|
|
71
|
+
public_key=self.public_key,
|
|
72
|
+
public_hash=self.public_hash,
|
|
73
|
+
private_key=private_key,
|
|
74
|
+
secret_bid=secret,
|
|
75
|
+
)
|
|
76
|
+
return True # if we got here, all is good
|
|
77
|
+
except key.CryptoError:
|
|
78
|
+
return False # bid does not match the public commitment
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def Copy(cls, other: PublicBid512, /) -> Self:
|
|
82
|
+
"""Initialize a public bid by taking the public parts of a public/private bid.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
other (PublicBid512): the bid to copy from
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Self: an initialized PublicBid512
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
return cls(public_key=other.public_key, public_hash=other.public_hash)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
95
|
+
class PrivateBid512(PublicBid512):
|
|
96
|
+
"""Private bid that can be revealed and validated against a public commitment (see PublicBid).
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
private_key (bytes): 512-bits random value
|
|
100
|
+
secret_bid (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
private_key: bytes
|
|
105
|
+
secret_bid: bytes
|
|
106
|
+
|
|
107
|
+
def __post_init__(self) -> None:
|
|
108
|
+
"""Check data.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
base.InputError: invalid inputs
|
|
112
|
+
key.CryptoError: bid does not match the public commitment
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
super(PrivateBid512, self).__post_init__()
|
|
116
|
+
if len(self.private_key) != 64 or len(self.secret_bid) < 1: # noqa: PLR2004
|
|
117
|
+
raise base.InputError(f'invalid private_key or secret_bid: {self}')
|
|
118
|
+
if self.public_hash != hashes.Hash512(self.public_key + self.private_key + self.secret_bid):
|
|
119
|
+
raise key.CryptoError(f'inconsistent bid: {self}')
|
|
120
|
+
|
|
121
|
+
def __str__(self) -> str:
|
|
122
|
+
"""Safe (no secrets) string representation of the PrivateBid.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
string representation of PrivateBid without leaking secrets
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
return (
|
|
129
|
+
'PrivateBid512('
|
|
130
|
+
f'{super(PrivateBid512, self).__str__()}, '
|
|
131
|
+
f'private_key={hashes.ObfuscateSecret(self.private_key)}, '
|
|
132
|
+
f'secret_bid={hashes.ObfuscateSecret(self.secret_bid)})'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def New(cls, secret: bytes, /) -> Self:
|
|
137
|
+
"""Make the `secret` into a new bid.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
secret (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
PrivateBid object ready for use (use PublicBid.Copy() to get the public part)
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
base.InputError: invalid inputs
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
# test inputs
|
|
150
|
+
if len(secret) < 1:
|
|
151
|
+
raise base.InputError(f'invalid secret length: {len(secret)}')
|
|
152
|
+
# generate random values
|
|
153
|
+
public_key: bytes = saferandom.RandBytes(64) # 512 bits
|
|
154
|
+
private_key: bytes = saferandom.RandBytes(64) # 512 bits
|
|
155
|
+
# build object
|
|
156
|
+
return cls(
|
|
157
|
+
public_key=public_key,
|
|
158
|
+
public_hash=hashes.Hash512(public_key + private_key + secret),
|
|
159
|
+
private_key=private_key,
|
|
160
|
+
secret_bid=secret,
|
|
161
|
+
)
|
|
@@ -19,7 +19,8 @@ from typing import Self
|
|
|
19
19
|
|
|
20
20
|
import gmpy2
|
|
21
21
|
|
|
22
|
-
from . import
|
|
22
|
+
from transcrypto.core import constants, hashes, key, modmath
|
|
23
|
+
from transcrypto.utils import base, saferandom
|
|
23
24
|
|
|
24
25
|
_MAX_KEY_GENERATION_FAILURES = 15
|
|
25
26
|
|
|
@@ -68,8 +69,8 @@ def NBitRandomDSAPrimes(
|
|
|
68
69
|
that p % q == 1 and m == (p - 1) // q
|
|
69
70
|
|
|
70
71
|
Raises:
|
|
71
|
-
InputError: invalid inputs
|
|
72
|
-
Error: prime search failed
|
|
72
|
+
base.InputError: invalid inputs
|
|
73
|
+
base.Error: prime search failed
|
|
73
74
|
|
|
74
75
|
"""
|
|
75
76
|
# test inputs
|
|
@@ -140,7 +141,7 @@ def _PrimePSearchShard(q: int, p_bits: int) -> tuple[int | None, int | None]:
|
|
|
140
141
|
return all(m % r != f for r, f in forbidden.items())
|
|
141
142
|
|
|
142
143
|
# try searching starting here
|
|
143
|
-
m: int =
|
|
144
|
+
m: int = saferandom.RandInt(min_m, max_m)
|
|
144
145
|
if m % 2:
|
|
145
146
|
m += 1 # make even
|
|
146
147
|
count: int = 0
|
|
@@ -158,7 +159,7 @@ def _PrimePSearchShard(q: int, p_bits: int) -> tuple[int | None, int | None]:
|
|
|
158
159
|
|
|
159
160
|
|
|
160
161
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
161
|
-
class DSASharedPublicKey(
|
|
162
|
+
class DSASharedPublicKey(key.CryptoKey):
|
|
162
163
|
"""DSA shared public key. This key can be shared by a group.
|
|
163
164
|
|
|
164
165
|
No measures are taken here to prevent timing attacks.
|
|
@@ -178,7 +179,7 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
178
179
|
"""Check data.
|
|
179
180
|
|
|
180
181
|
Raises:
|
|
181
|
-
InputError: invalid inputs
|
|
182
|
+
base.InputError: invalid inputs
|
|
182
183
|
|
|
183
184
|
"""
|
|
184
185
|
if self.prime_seed < 7 or not modmath.IsPrime(self.prime_seed): # noqa: PLR2004
|
|
@@ -227,16 +228,16 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
227
228
|
Hash512("prefix" || len(aad) || aad || message || salt)
|
|
228
229
|
|
|
229
230
|
Raises:
|
|
230
|
-
CryptoError: hash output is out of range
|
|
231
|
+
key.CryptoError: hash output is out of range
|
|
231
232
|
|
|
232
233
|
"""
|
|
233
234
|
aad: bytes = b'' if associated_data is None else associated_data
|
|
234
235
|
la: bytes = base.IntToFixedBytes(len(aad), 8)
|
|
235
236
|
assert len(salt) == 64, 'should never happen: salt should be exactly 64 bytes' # noqa: PLR2004, S101
|
|
236
|
-
y: int = base.BytesToInt(
|
|
237
|
+
y: int = base.BytesToInt(hashes.Hash512(_DSA_SIGNATURE_HASH_PREFIX + la + aad + message + salt))
|
|
237
238
|
if not 1 < y < self.prime_seed - 1:
|
|
238
239
|
# will only reasonably happen if prime seed is small
|
|
239
|
-
raise
|
|
240
|
+
raise key.CryptoError(f'hash output {y} is out of range/invalid {self.prime_seed}')
|
|
240
241
|
return y
|
|
241
242
|
|
|
242
243
|
@classmethod
|
|
@@ -257,13 +258,13 @@ class DSASharedPublicKey(base.CryptoKey):
|
|
|
257
258
|
# generate random number, create object (should never fail)
|
|
258
259
|
g: int = 0
|
|
259
260
|
while g < 3: # noqa: PLR2004
|
|
260
|
-
h: int =
|
|
261
|
+
h: int = saferandom.RandBits(p_bits - 1)
|
|
261
262
|
g = int(gmpy2.powmod(h, m, p))
|
|
262
263
|
return cls(prime_modulus=p, prime_seed=q, group_base=g)
|
|
263
264
|
|
|
264
265
|
|
|
265
266
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
266
|
-
class DSAPublicKey(DSASharedPublicKey,
|
|
267
|
+
class DSAPublicKey(DSASharedPublicKey, key.Verifier):
|
|
267
268
|
"""DSA public key. This is an individual public key.
|
|
268
269
|
|
|
269
270
|
No measures are taken here to prevent timing attacks.
|
|
@@ -279,7 +280,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
279
280
|
"""Check data.
|
|
280
281
|
|
|
281
282
|
Raises:
|
|
282
|
-
InputError: invalid inputs
|
|
283
|
+
base.InputError: invalid inputs
|
|
283
284
|
|
|
284
285
|
"""
|
|
285
286
|
super(DSAPublicKey, self).__post_init__()
|
|
@@ -315,7 +316,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
315
316
|
self.group_base,
|
|
316
317
|
self.individual_base,
|
|
317
318
|
}:
|
|
318
|
-
ephemeral_key =
|
|
319
|
+
ephemeral_key = saferandom.RandBits(bit_length - 1)
|
|
319
320
|
return (ephemeral_key, modmath.ModInv(ephemeral_key, self.prime_seed))
|
|
320
321
|
|
|
321
322
|
def RawVerify(self, message: int, signature: tuple[int, int], /) -> bool:
|
|
@@ -333,7 +334,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
333
334
|
True if signature is valid, False otherwise
|
|
334
335
|
|
|
335
336
|
Raises:
|
|
336
|
-
InputError: invalid inputs
|
|
337
|
+
base.InputError: invalid inputs
|
|
337
338
|
|
|
338
339
|
"""
|
|
339
340
|
# test inputs
|
|
@@ -371,7 +372,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
371
372
|
True if signature is valid, False otherwise
|
|
372
373
|
|
|
373
374
|
Raises:
|
|
374
|
-
InputError: invalid inputs
|
|
375
|
+
base.InputError: invalid inputs
|
|
375
376
|
|
|
376
377
|
"""
|
|
377
378
|
k: int = self.modulus_size[1] # use prime_seed size
|
|
@@ -409,7 +410,7 @@ class DSAPublicKey(DSASharedPublicKey, base.Verifier):
|
|
|
409
410
|
|
|
410
411
|
|
|
411
412
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
412
|
-
class DSAPrivateKey(DSAPublicKey,
|
|
413
|
+
class DSAPrivateKey(DSAPublicKey, key.Signer):
|
|
413
414
|
"""DSA private key.
|
|
414
415
|
|
|
415
416
|
No measures are taken here to prevent timing attacks.
|
|
@@ -425,8 +426,8 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
425
426
|
"""Check data.
|
|
426
427
|
|
|
427
428
|
Raises:
|
|
428
|
-
InputError: invalid inputs
|
|
429
|
-
CryptoError: modulus math is inconsistent with values
|
|
429
|
+
base.InputError: invalid inputs
|
|
430
|
+
key.CryptoError: modulus math is inconsistent with values
|
|
430
431
|
|
|
431
432
|
"""
|
|
432
433
|
super(DSAPrivateKey, self).__post_init__()
|
|
@@ -436,7 +437,7 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
436
437
|
}:
|
|
437
438
|
raise base.InputError(f'invalid decrypt_exp: {self}')
|
|
438
439
|
if gmpy2.powmod(self.group_base, self.decrypt_exp, self.prime_modulus) != self.individual_base:
|
|
439
|
-
raise
|
|
440
|
+
raise key.CryptoError(f'inconsistent g**d % p == i: {self}')
|
|
440
441
|
|
|
441
442
|
def __str__(self) -> str:
|
|
442
443
|
"""Safe (no secrets) string representation of the DSAPrivateKey.
|
|
@@ -448,7 +449,7 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
448
449
|
return (
|
|
449
450
|
'DSAPrivateKey('
|
|
450
451
|
f'{super(DSAPrivateKey, self).__str__()}, '
|
|
451
|
-
f'decrypt_exp={
|
|
452
|
+
f'decrypt_exp={hashes.ObfuscateSecret(self.decrypt_exp)})'
|
|
452
453
|
)
|
|
453
454
|
|
|
454
455
|
def RawSign(self, message: int, /) -> tuple[int, int]:
|
|
@@ -465,7 +466,7 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
465
466
|
signed message tuple ((int, int), 2 ≤ s1,s2 < prime_seed
|
|
466
467
|
|
|
467
468
|
Raises:
|
|
468
|
-
InputError: invalid inputs
|
|
469
|
+
base.InputError: invalid inputs
|
|
469
470
|
|
|
470
471
|
"""
|
|
471
472
|
# test inputs
|
|
@@ -502,13 +503,13 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
502
503
|
bytes: Signature; salt || Padded(s, k) - see above
|
|
503
504
|
|
|
504
505
|
Raises:
|
|
505
|
-
InputError: invalid inputs
|
|
506
|
+
base.InputError: invalid inputs
|
|
506
507
|
|
|
507
508
|
"""
|
|
508
509
|
k: int = self.modulus_size[1] # use prime_seed size
|
|
509
510
|
if k <= 64: # noqa: PLR2004
|
|
510
511
|
raise base.InputError(f'modulus/seed too small for signing operations: {k} bytes')
|
|
511
|
-
salt: bytes =
|
|
512
|
+
salt: bytes = saferandom.RandBytes(64)
|
|
512
513
|
s_int: tuple[int, int] = self.RawSign(self._DomainSeparatedHash(message, associated_data, salt))
|
|
513
514
|
s_bytes: bytes = base.IntToFixedBytes(s_int[0], k) + base.IntToFixedBytes(s_int[1], k)
|
|
514
515
|
assert len(s_bytes) == 2 * k, 'should never happen: s_bytes should be exactly 2k bytes' # noqa: S101
|
|
@@ -525,8 +526,8 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
525
526
|
DSAPrivateKey object ready for use
|
|
526
527
|
|
|
527
528
|
Raises:
|
|
528
|
-
InputError: invalid inputs
|
|
529
|
-
CryptoError: failed generation
|
|
529
|
+
base.InputError: invalid inputs
|
|
530
|
+
key.CryptoError: failed generation
|
|
530
531
|
|
|
531
532
|
"""
|
|
532
533
|
# test inputs
|
|
@@ -542,7 +543,7 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
542
543
|
while (
|
|
543
544
|
not 2 < decrypt_exp < shared_key.prime_seed or decrypt_exp == shared_key.group_base # noqa: PLR2004
|
|
544
545
|
):
|
|
545
|
-
decrypt_exp =
|
|
546
|
+
decrypt_exp = saferandom.RandBits(bit_length - 1)
|
|
546
547
|
# make the object
|
|
547
548
|
return cls(
|
|
548
549
|
prime_modulus=shared_key.prime_modulus,
|
|
@@ -556,5 +557,5 @@ class DSAPrivateKey(DSAPublicKey, base.Signer):
|
|
|
556
557
|
except base.InputError as err:
|
|
557
558
|
failures += 1
|
|
558
559
|
if failures >= _MAX_KEY_GENERATION_FAILURES:
|
|
559
|
-
raise
|
|
560
|
+
raise key.CryptoError(f'failed key generation {failures} times') from err
|
|
560
561
|
logging.warning(err)
|