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
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Balparda's TransCrypto CLI: Public algorithms commands."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from transcrypto import transcrypto
|
|
10
|
+
from transcrypto.cli import clibase
|
|
11
|
+
from transcrypto.core import dsa, elgamal, rsa
|
|
12
|
+
|
|
13
|
+
# ================================== "RSA" COMMAND =================================================
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
rsa_app = typer.Typer(
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
help=(
|
|
19
|
+
'RSA (Rivest-Shamir-Adleman) asymmetric cryptography. '
|
|
20
|
+
'All methods require file key(s) as `-p`/`--key-path` (see provided examples). '
|
|
21
|
+
'All non-int inputs are raw, or you can use `--input-format <hex|b64|bin>`. '
|
|
22
|
+
'Attention: if you provide `-a`/`--aad` (associated data, AAD), '
|
|
23
|
+
'you will need to provide the same AAD when decrypting/verifying and it is NOT included '
|
|
24
|
+
'in the `ciphertext`/CT or `signature` returned by these methods! '
|
|
25
|
+
'No measures are taken here to prevent timing attacks.'
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
transcrypto.app.add_typer(rsa_app, name='rsa')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@rsa_app.command(
|
|
32
|
+
'new',
|
|
33
|
+
help=(
|
|
34
|
+
'Generate RSA private/public key pair with `bits` modulus size (prime sizes will be `bits`/2).'
|
|
35
|
+
),
|
|
36
|
+
epilog=(
|
|
37
|
+
'Example:\n\n\n\n'
|
|
38
|
+
'$ poetry run transcrypto -p rsa-key rsa new --bits 64 '
|
|
39
|
+
'# NEVER use such a small key: example only!\n\n'
|
|
40
|
+
"RSA private/public keys saved to 'rsa-key.priv/.pub'"
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
@clibase.CLIErrorGuard
|
|
44
|
+
def RSANew( # documentation is help/epilog/args # noqa: D103
|
|
45
|
+
*,
|
|
46
|
+
ctx: typer.Context,
|
|
47
|
+
bits: int = typer.Option(
|
|
48
|
+
3332,
|
|
49
|
+
'-b',
|
|
50
|
+
'--bits',
|
|
51
|
+
min=16,
|
|
52
|
+
help='Modulus size in bits, ≥16; the default (3332) is a safe size',
|
|
53
|
+
),
|
|
54
|
+
) -> None:
|
|
55
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
56
|
+
base_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
57
|
+
rsa_priv: rsa.RSAPrivateKey = rsa.RSAPrivateKey.New(bits)
|
|
58
|
+
rsa_pub: rsa.RSAPublicKey = rsa.RSAPublicKey.Copy(rsa_priv)
|
|
59
|
+
transcrypto.SaveObj(rsa_priv, base_path + '.priv', config.protect)
|
|
60
|
+
transcrypto.SaveObj(rsa_pub, base_path + '.pub', config.protect)
|
|
61
|
+
config.console.print(f'RSA private/public keys saved to {base_path + ".priv/.pub"!r}')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@rsa_app.command(
|
|
65
|
+
'rawencrypt',
|
|
66
|
+
help=(
|
|
67
|
+
'Raw encrypt *integer* `message` with public key (BEWARE: no OAEP/PSS padding or validation).'
|
|
68
|
+
),
|
|
69
|
+
epilog=(
|
|
70
|
+
'Example:\n\n\n\n'
|
|
71
|
+
'$ poetry run transcrypto -p rsa-key.pub rsa rawencrypt 999\n\n'
|
|
72
|
+
'6354905961171348600'
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
@clibase.CLIErrorGuard
|
|
76
|
+
def RSARawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
77
|
+
*,
|
|
78
|
+
ctx: typer.Context,
|
|
79
|
+
message: str = typer.Argument(..., help='Integer message to encrypt, 1≤`message`<*modulus*'),
|
|
80
|
+
) -> None:
|
|
81
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
82
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
83
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
84
|
+
rsa_pub: rsa.RSAPublicKey = rsa.RSAPublicKey.Copy(
|
|
85
|
+
transcrypto.LoadObj(key_path, config.protect, rsa.RSAPublicKey)
|
|
86
|
+
)
|
|
87
|
+
config.console.print(rsa_pub.RawEncrypt(message_i))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@rsa_app.command(
|
|
91
|
+
'rawdecrypt',
|
|
92
|
+
help=(
|
|
93
|
+
'Raw decrypt *integer* `ciphertext` with private key '
|
|
94
|
+
'(BEWARE: no OAEP/PSS padding or validation).'
|
|
95
|
+
),
|
|
96
|
+
epilog=(
|
|
97
|
+
'Example:\n\n\n\n'
|
|
98
|
+
'$ poetry run transcrypto -p rsa-key.priv rsa rawdecrypt 6354905961171348600\n\n'
|
|
99
|
+
'999'
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
@clibase.CLIErrorGuard
|
|
103
|
+
def RSARawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
104
|
+
*,
|
|
105
|
+
ctx: typer.Context,
|
|
106
|
+
ciphertext: str = typer.Argument(
|
|
107
|
+
..., help='Integer ciphertext to decrypt, 1≤`ciphertext`<*modulus*'
|
|
108
|
+
),
|
|
109
|
+
) -> None:
|
|
110
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
111
|
+
ciphertext_i: int = transcrypto.ParseInt(ciphertext, min_value=1)
|
|
112
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
113
|
+
rsa_priv: rsa.RSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPrivateKey)
|
|
114
|
+
config.console.print(rsa_priv.RawDecrypt(ciphertext_i))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@rsa_app.command(
|
|
118
|
+
'rawsign',
|
|
119
|
+
help='Raw sign *integer* `message` with private key (BEWARE: no OAEP/PSS padding or validation).',
|
|
120
|
+
epilog=(
|
|
121
|
+
'Example:\n\n\n\n'
|
|
122
|
+
'$ poetry run transcrypto -p rsa-key.priv rsa rawsign 999\n\n'
|
|
123
|
+
'7632909108672871784'
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
@clibase.CLIErrorGuard
|
|
127
|
+
def RSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
128
|
+
*,
|
|
129
|
+
ctx: typer.Context,
|
|
130
|
+
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<*modulus*'),
|
|
131
|
+
) -> None:
|
|
132
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
133
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
134
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
135
|
+
rsa_priv: rsa.RSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPrivateKey)
|
|
136
|
+
config.console.print(rsa_priv.RawSign(message_i))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@rsa_app.command(
|
|
140
|
+
'rawverify',
|
|
141
|
+
help=(
|
|
142
|
+
'Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
143
|
+
'(BEWARE: no OAEP/PSS padding or validation).'
|
|
144
|
+
),
|
|
145
|
+
epilog=(
|
|
146
|
+
'Example:\n\n\n\n'
|
|
147
|
+
'$ poetry run transcrypto -p rsa-key.pub rsa rawverify 999 7632909108672871784\n\n'
|
|
148
|
+
'RSA signature: OK\n\n'
|
|
149
|
+
'$ poetry run transcrypto -p rsa-key.pub rsa rawverify 999 7632909108672871785\n\n'
|
|
150
|
+
'RSA signature: INVALID'
|
|
151
|
+
),
|
|
152
|
+
)
|
|
153
|
+
@clibase.CLIErrorGuard
|
|
154
|
+
def RSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
155
|
+
*,
|
|
156
|
+
ctx: typer.Context,
|
|
157
|
+
message: str = typer.Argument(
|
|
158
|
+
..., help='Integer message that was signed earlier, 1≤`message`<*modulus*'
|
|
159
|
+
),
|
|
160
|
+
signature: str = typer.Argument(
|
|
161
|
+
..., help='Integer putative signature for `message`, 1≤`signature`<*modulus*'
|
|
162
|
+
),
|
|
163
|
+
) -> None:
|
|
164
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
165
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
166
|
+
signature_i: int = transcrypto.ParseInt(signature, min_value=1)
|
|
167
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
168
|
+
rsa_pub: rsa.RSAPublicKey = rsa.RSAPublicKey.Copy(
|
|
169
|
+
transcrypto.LoadObj(key_path, config.protect, rsa.RSAPublicKey)
|
|
170
|
+
)
|
|
171
|
+
config.console.print(
|
|
172
|
+
'RSA signature: '
|
|
173
|
+
+ ('[green]OK[/]' if rsa_pub.RawVerify(message_i, signature_i) else '[red]INVALID[/]')
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@rsa_app.command(
|
|
178
|
+
'encrypt',
|
|
179
|
+
help='Encrypt `message` with public key.',
|
|
180
|
+
epilog=(
|
|
181
|
+
'Example:\n\n\n\n'
|
|
182
|
+
'$ poetry run transcrypto -i bin -o b64 -p rsa-key.pub rsa encrypt "abcde" -a "xyz"\n\n'
|
|
183
|
+
'AO6knI6xwq6TGR…Qy22jiFhXi1eQ=='
|
|
184
|
+
),
|
|
185
|
+
)
|
|
186
|
+
@clibase.CLIErrorGuard
|
|
187
|
+
def RSAEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
188
|
+
*,
|
|
189
|
+
ctx: typer.Context,
|
|
190
|
+
plaintext: str = typer.Argument(..., help='Message to encrypt'),
|
|
191
|
+
aad: str = typer.Option(
|
|
192
|
+
'',
|
|
193
|
+
'-a',
|
|
194
|
+
'--aad',
|
|
195
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)',
|
|
196
|
+
),
|
|
197
|
+
) -> None:
|
|
198
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
199
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
200
|
+
rsa_pub: rsa.RSAPublicKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPublicKey)
|
|
201
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
202
|
+
pt: bytes = transcrypto.BytesFromText(plaintext, config.input_format)
|
|
203
|
+
ct: bytes = rsa_pub.Encrypt(pt, associated_data=aad_bytes)
|
|
204
|
+
config.console.print(transcrypto.BytesToText(ct, config.output_format))
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@rsa_app.command(
|
|
208
|
+
'decrypt',
|
|
209
|
+
help='Decrypt `ciphertext` with private key.',
|
|
210
|
+
epilog=(
|
|
211
|
+
'Example:\n\n\n\n'
|
|
212
|
+
'$ poetry run transcrypto -i b64 -o bin -p rsa-key.priv rsa decrypt -a eHl6 -- '
|
|
213
|
+
'AO6knI6xwq6TGR…Qy22jiFhXi1eQ==\n\n'
|
|
214
|
+
'abcde'
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
@clibase.CLIErrorGuard
|
|
218
|
+
def RSADecrypt( # documentation is help/epilog/args # noqa: D103
|
|
219
|
+
*,
|
|
220
|
+
ctx: typer.Context,
|
|
221
|
+
ciphertext: str = typer.Argument(..., help='Ciphertext to decrypt'),
|
|
222
|
+
aad: str = typer.Option(
|
|
223
|
+
'',
|
|
224
|
+
'-a',
|
|
225
|
+
'--aad',
|
|
226
|
+
help='Associated data (optional; has to be exactly the same as used during encryption)',
|
|
227
|
+
),
|
|
228
|
+
) -> None:
|
|
229
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
230
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
231
|
+
rsa_priv: rsa.RSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPrivateKey)
|
|
232
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
233
|
+
ct: bytes = transcrypto.BytesFromText(ciphertext, config.input_format)
|
|
234
|
+
pt: bytes = rsa_priv.Decrypt(ct, associated_data=aad_bytes)
|
|
235
|
+
config.console.print(transcrypto.BytesToText(pt, config.output_format))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@rsa_app.command(
|
|
239
|
+
'sign',
|
|
240
|
+
help='Sign `message` with private key.',
|
|
241
|
+
epilog=(
|
|
242
|
+
'Example:\n\n\n\n'
|
|
243
|
+
'$ poetry run transcrypto -i bin -o b64 -p rsa-key.priv rsa sign "xyz"\n\n'
|
|
244
|
+
'91TS7gC6LORiL…6RD23Aejsfxlw==' # cspell:disable-line
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
@clibase.CLIErrorGuard
|
|
248
|
+
def RSASign( # documentation is help/epilog/args # noqa: D103
|
|
249
|
+
*,
|
|
250
|
+
ctx: typer.Context,
|
|
251
|
+
message: str = typer.Argument(..., help='Message to sign'),
|
|
252
|
+
aad: str = typer.Option(
|
|
253
|
+
'',
|
|
254
|
+
'-a',
|
|
255
|
+
'--aad',
|
|
256
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)',
|
|
257
|
+
),
|
|
258
|
+
) -> None:
|
|
259
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
260
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
261
|
+
rsa_priv: rsa.RSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPrivateKey)
|
|
262
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
263
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
264
|
+
sig: bytes = rsa_priv.Sign(pt, associated_data=aad_bytes)
|
|
265
|
+
config.console.print(transcrypto.BytesToText(sig, config.output_format))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@rsa_app.command(
|
|
269
|
+
'verify',
|
|
270
|
+
help='Verify `signature` for `message` with public key.',
|
|
271
|
+
epilog=(
|
|
272
|
+
'Example:\n\n\n\n'
|
|
273
|
+
'$ poetry run transcrypto -i b64 -p rsa-key.pub rsa verify -- eHl6 '
|
|
274
|
+
'91TS7gC6LORiL…6RD23Aejsfxlw==\n\n' # cspell:disable-line
|
|
275
|
+
'RSA signature: OK\n\n'
|
|
276
|
+
'$ poetry run transcrypto -i b64 -p rsa-key.pub rsa verify -- eLl6 '
|
|
277
|
+
'91TS7gC6LORiL…6RD23Aejsfxlw==\n\n' # cspell:disable-line
|
|
278
|
+
'RSA signature: INVALID'
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
@clibase.CLIErrorGuard
|
|
282
|
+
def RSAVerify( # documentation is help/epilog/args # noqa: D103
|
|
283
|
+
*,
|
|
284
|
+
ctx: typer.Context,
|
|
285
|
+
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
286
|
+
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
287
|
+
aad: str = typer.Option(
|
|
288
|
+
'',
|
|
289
|
+
'-a',
|
|
290
|
+
'--aad',
|
|
291
|
+
help='Associated data (optional; has to be exactly the same as used during signing)',
|
|
292
|
+
),
|
|
293
|
+
) -> None:
|
|
294
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
295
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'rsa')
|
|
296
|
+
rsa_pub: rsa.RSAPublicKey = transcrypto.LoadObj(key_path, config.protect, rsa.RSAPublicKey)
|
|
297
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
298
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
299
|
+
sig: bytes = transcrypto.BytesFromText(signature, config.input_format)
|
|
300
|
+
config.console.print(
|
|
301
|
+
'RSA signature: '
|
|
302
|
+
+ ('[green]OK[/]' if rsa_pub.Verify(pt, sig, associated_data=aad_bytes) else '[red]INVALID[/]')
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ================================= "ELGAMAL" COMMAND ==============================================
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
eg_app = typer.Typer(
|
|
310
|
+
no_args_is_help=True,
|
|
311
|
+
help=(
|
|
312
|
+
'El-Gamal asymmetric cryptography. '
|
|
313
|
+
'All methods require file key(s) as `-p`/`--key-path` (see provided examples). '
|
|
314
|
+
'All non-int inputs are raw, or you can use `--input-format <hex|b64|bin>`. '
|
|
315
|
+
'Attention: if you provide `-a`/`--aad` (associated data, AAD), '
|
|
316
|
+
'you will need to provide the same AAD when decrypting/verifying and it is NOT included '
|
|
317
|
+
'in the `ciphertext`/CT or `signature` returned by these methods! '
|
|
318
|
+
'No measures are taken here to prevent timing attacks.'
|
|
319
|
+
),
|
|
320
|
+
)
|
|
321
|
+
transcrypto.app.add_typer(eg_app, name='elgamal')
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@eg_app.command(
|
|
325
|
+
'shared',
|
|
326
|
+
help=(
|
|
327
|
+
'Generate a shared El-Gamal key with `bits` prime modulus size, which is the '
|
|
328
|
+
'first step in key generation. '
|
|
329
|
+
'The shared key can safely be used by any number of users to generate their '
|
|
330
|
+
'private/public key pairs (with the `new` command). The shared keys are "public".'
|
|
331
|
+
),
|
|
332
|
+
epilog=(
|
|
333
|
+
'Example:\n\n\n\n'
|
|
334
|
+
'$ poetry run transcrypto -p eg-key elgamal shared --bits 64 '
|
|
335
|
+
'# NEVER use such a small key: example only!\n\n'
|
|
336
|
+
"El-Gamal shared key saved to 'eg-key.shared'"
|
|
337
|
+
),
|
|
338
|
+
)
|
|
339
|
+
@clibase.CLIErrorGuard
|
|
340
|
+
def ElGamalShared( # documentation is help/epilog/args # noqa: D103
|
|
341
|
+
*,
|
|
342
|
+
ctx: typer.Context,
|
|
343
|
+
bits: int = typer.Option(
|
|
344
|
+
3332,
|
|
345
|
+
'-b',
|
|
346
|
+
'--bits',
|
|
347
|
+
min=16,
|
|
348
|
+
help='Prime modulus (`p`) size in bits, ≥16; the default (3332) is a safe size',
|
|
349
|
+
),
|
|
350
|
+
) -> None:
|
|
351
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
352
|
+
base_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
353
|
+
shared_eg: elgamal.ElGamalSharedPublicKey = elgamal.ElGamalSharedPublicKey.NewShared(bits)
|
|
354
|
+
transcrypto.SaveObj(shared_eg, base_path + '.shared', config.protect)
|
|
355
|
+
config.console.print(f'El-Gamal shared key saved to {base_path + ".shared"!r}')
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@eg_app.command(
|
|
359
|
+
'new',
|
|
360
|
+
help='Generate an individual El-Gamal private/public key pair from a shared key.',
|
|
361
|
+
epilog=(
|
|
362
|
+
'Example:\n\n\n\n'
|
|
363
|
+
'$ poetry run transcrypto -p eg-key elgamal new\n\n'
|
|
364
|
+
"El-Gamal private/public keys saved to 'eg-key.priv/.pub'"
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
@clibase.CLIErrorGuard
|
|
368
|
+
def ElGamalNew(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
369
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
370
|
+
base_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
371
|
+
shared_eg: elgamal.ElGamalSharedPublicKey = transcrypto.LoadObj(
|
|
372
|
+
base_path + '.shared', config.protect, elgamal.ElGamalSharedPublicKey
|
|
373
|
+
)
|
|
374
|
+
eg_priv: elgamal.ElGamalPrivateKey = elgamal.ElGamalPrivateKey.New(shared_eg)
|
|
375
|
+
eg_pub: elgamal.ElGamalPublicKey = elgamal.ElGamalPublicKey.Copy(eg_priv)
|
|
376
|
+
transcrypto.SaveObj(eg_priv, base_path + '.priv', config.protect)
|
|
377
|
+
transcrypto.SaveObj(eg_pub, base_path + '.pub', config.protect)
|
|
378
|
+
config.console.print(f'El-Gamal private/public keys saved to {base_path + ".priv/.pub"!r}')
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@eg_app.command(
|
|
382
|
+
'rawencrypt',
|
|
383
|
+
help=(
|
|
384
|
+
'Raw encrypt *integer* `message` with public key '
|
|
385
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'
|
|
386
|
+
),
|
|
387
|
+
epilog=(
|
|
388
|
+
'Example:\n\n\n\n'
|
|
389
|
+
'$ poetry run transcrypto -p eg-key.pub elgamal rawencrypt 999\n\n'
|
|
390
|
+
'2948854810728206041:15945988196340032688'
|
|
391
|
+
),
|
|
392
|
+
)
|
|
393
|
+
@clibase.CLIErrorGuard
|
|
394
|
+
def ElGamalRawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
395
|
+
*,
|
|
396
|
+
ctx: typer.Context,
|
|
397
|
+
message: str = typer.Argument(..., help='Integer message to encrypt, 1≤`message`<*modulus*'),
|
|
398
|
+
) -> None:
|
|
399
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
400
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
401
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
402
|
+
eg_pub: elgamal.ElGamalPublicKey = elgamal.ElGamalPublicKey.Copy(
|
|
403
|
+
transcrypto.LoadObj(key_path, config.protect, elgamal.ElGamalPublicKey)
|
|
404
|
+
)
|
|
405
|
+
c1: int
|
|
406
|
+
c2: int
|
|
407
|
+
c1, c2 = eg_pub.RawEncrypt(message_i)
|
|
408
|
+
config.console.print(f'{c1}:{c2}')
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@eg_app.command(
|
|
412
|
+
'rawdecrypt',
|
|
413
|
+
help=(
|
|
414
|
+
'Raw decrypt *integer* `ciphertext` with private key '
|
|
415
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'
|
|
416
|
+
),
|
|
417
|
+
epilog=(
|
|
418
|
+
'Example:\n\n\n\n'
|
|
419
|
+
'$ poetry run transcrypto -p eg-key.priv elgamal rawdecrypt '
|
|
420
|
+
'2948854810728206041:15945988196340032688\n\n'
|
|
421
|
+
'999'
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
@clibase.CLIErrorGuard
|
|
425
|
+
def ElGamalRawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
426
|
+
*,
|
|
427
|
+
ctx: typer.Context,
|
|
428
|
+
ciphertext: str = typer.Argument(
|
|
429
|
+
...,
|
|
430
|
+
help=(
|
|
431
|
+
'Integer ciphertext to decrypt; expects `c1:c2` format with 2 integers, `c1`,`c2`<*modulus*'
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
) -> None:
|
|
435
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
436
|
+
ciphertext_i: tuple[int, int] = transcrypto.ParseIntPairCLI(ciphertext)
|
|
437
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
438
|
+
eg_priv: elgamal.ElGamalPrivateKey = transcrypto.LoadObj(
|
|
439
|
+
key_path, config.protect, elgamal.ElGamalPrivateKey
|
|
440
|
+
)
|
|
441
|
+
config.console.print(eg_priv.RawDecrypt(ciphertext_i))
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@eg_app.command(
|
|
445
|
+
'rawsign',
|
|
446
|
+
help=(
|
|
447
|
+
'Raw sign *integer* message with private key '
|
|
448
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation). '
|
|
449
|
+
'Output will 2 *integers* in a `s1:s2` format.'
|
|
450
|
+
),
|
|
451
|
+
epilog=(
|
|
452
|
+
'Example:\n\n\n\n'
|
|
453
|
+
'$ poetry run transcrypto -p eg-key.priv elgamal rawsign 999\n\n'
|
|
454
|
+
'4674885853217269088:14532144906178302633'
|
|
455
|
+
),
|
|
456
|
+
)
|
|
457
|
+
@clibase.CLIErrorGuard
|
|
458
|
+
def ElGamalRawSign( # documentation is help/epilog/args # noqa: D103
|
|
459
|
+
*,
|
|
460
|
+
ctx: typer.Context,
|
|
461
|
+
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<*modulus*'),
|
|
462
|
+
) -> None:
|
|
463
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
464
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
465
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
466
|
+
eg_priv: elgamal.ElGamalPrivateKey = transcrypto.LoadObj(
|
|
467
|
+
key_path, config.protect, elgamal.ElGamalPrivateKey
|
|
468
|
+
)
|
|
469
|
+
s1: int
|
|
470
|
+
s2: int
|
|
471
|
+
s1, s2 = eg_priv.RawSign(message_i)
|
|
472
|
+
config.console.print(f'{s1}:{s2}')
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@eg_app.command(
|
|
476
|
+
'rawverify',
|
|
477
|
+
help=(
|
|
478
|
+
'Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
479
|
+
'(BEWARE: no ECIES-style KEM/DEM padding or validation).'
|
|
480
|
+
),
|
|
481
|
+
epilog=(
|
|
482
|
+
'Example:\n\n\n\n'
|
|
483
|
+
'$ poetry run transcrypto -p eg-key.pub elgamal rawverify 999 '
|
|
484
|
+
'4674885853217269088:14532144906178302633\n\n'
|
|
485
|
+
'El-Gamal signature: OK\n\n'
|
|
486
|
+
'$ poetry run transcrypto -p eg-key.pub elgamal rawverify 999 '
|
|
487
|
+
'4674885853217269088:14532144906178302632\n\n'
|
|
488
|
+
'El-Gamal signature: INVALID'
|
|
489
|
+
),
|
|
490
|
+
)
|
|
491
|
+
@clibase.CLIErrorGuard
|
|
492
|
+
def ElGamalRawVerify( # documentation is help/epilog/args # noqa: D103
|
|
493
|
+
*,
|
|
494
|
+
ctx: typer.Context,
|
|
495
|
+
message: str = typer.Argument(
|
|
496
|
+
..., help='Integer message that was signed earlier, 1≤`message`<*modulus*'
|
|
497
|
+
),
|
|
498
|
+
signature: str = typer.Argument(
|
|
499
|
+
...,
|
|
500
|
+
help=(
|
|
501
|
+
'Integer putative signature for `message`; expects `s1:s2` format with 2 integers, '
|
|
502
|
+
'`s1`,`s2`<*modulus*'
|
|
503
|
+
),
|
|
504
|
+
),
|
|
505
|
+
) -> None:
|
|
506
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
507
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
508
|
+
signature_i: tuple[int, int] = transcrypto.ParseIntPairCLI(signature)
|
|
509
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
510
|
+
eg_pub: elgamal.ElGamalPublicKey = elgamal.ElGamalPublicKey.Copy(
|
|
511
|
+
transcrypto.LoadObj(key_path, config.protect, elgamal.ElGamalPublicKey)
|
|
512
|
+
)
|
|
513
|
+
config.console.print(
|
|
514
|
+
'El-Gamal signature: '
|
|
515
|
+
+ ('[green]OK[/]' if eg_pub.RawVerify(message_i, signature_i) else '[red]INVALID[/]')
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@eg_app.command(
|
|
520
|
+
'encrypt',
|
|
521
|
+
help='Encrypt `message` with public key.',
|
|
522
|
+
epilog=(
|
|
523
|
+
'Example:\n\n\n\n'
|
|
524
|
+
'$ poetry run transcrypto -i bin -o b64 -p eg-key.pub elgamal encrypt "abcde" -a "xyz"\n\n'
|
|
525
|
+
'CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==' # cspell:disable-line
|
|
526
|
+
),
|
|
527
|
+
)
|
|
528
|
+
@clibase.CLIErrorGuard
|
|
529
|
+
def ElGamalEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
530
|
+
*,
|
|
531
|
+
ctx: typer.Context,
|
|
532
|
+
plaintext: str = typer.Argument(..., help='Message to encrypt'),
|
|
533
|
+
aad: str = typer.Option(
|
|
534
|
+
'',
|
|
535
|
+
'-a',
|
|
536
|
+
'--aad',
|
|
537
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)',
|
|
538
|
+
),
|
|
539
|
+
) -> None:
|
|
540
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
541
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
542
|
+
eg_pub: elgamal.ElGamalPublicKey = transcrypto.LoadObj(
|
|
543
|
+
key_path, config.protect, elgamal.ElGamalPublicKey
|
|
544
|
+
)
|
|
545
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
546
|
+
pt: bytes = transcrypto.BytesFromText(plaintext, config.input_format)
|
|
547
|
+
ct: bytes = eg_pub.Encrypt(pt, associated_data=aad_bytes)
|
|
548
|
+
config.console.print(transcrypto.BytesToText(ct, config.output_format))
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@eg_app.command(
|
|
552
|
+
'decrypt',
|
|
553
|
+
help='Decrypt `ciphertext` with private key.',
|
|
554
|
+
epilog=(
|
|
555
|
+
'Example:\n\n\n\n'
|
|
556
|
+
'$ poetry run transcrypto -i b64 -o bin -p eg-key.priv elgamal decrypt -a eHl6 -- '
|
|
557
|
+
'CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==\n\n' # cspell:disable-line
|
|
558
|
+
'abcde'
|
|
559
|
+
),
|
|
560
|
+
)
|
|
561
|
+
@clibase.CLIErrorGuard
|
|
562
|
+
def ElGamalDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
563
|
+
*,
|
|
564
|
+
ctx: typer.Context,
|
|
565
|
+
ciphertext: str = typer.Argument(..., help='Ciphertext to decrypt'),
|
|
566
|
+
aad: str = typer.Option(
|
|
567
|
+
'',
|
|
568
|
+
'-a',
|
|
569
|
+
'--aad',
|
|
570
|
+
help='Associated data (optional; has to be exactly the same as used during encryption)',
|
|
571
|
+
),
|
|
572
|
+
) -> None:
|
|
573
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
574
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
575
|
+
eg_priv: elgamal.ElGamalPrivateKey = transcrypto.LoadObj(
|
|
576
|
+
key_path, config.protect, elgamal.ElGamalPrivateKey
|
|
577
|
+
)
|
|
578
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
579
|
+
ct: bytes = transcrypto.BytesFromText(ciphertext, config.input_format)
|
|
580
|
+
pt: bytes = eg_priv.Decrypt(ct, associated_data=aad_bytes)
|
|
581
|
+
config.console.print(transcrypto.BytesToText(pt, config.output_format))
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
@eg_app.command(
|
|
585
|
+
'sign',
|
|
586
|
+
help='Sign message with private key.',
|
|
587
|
+
epilog=(
|
|
588
|
+
'Example:\n\n\n\n'
|
|
589
|
+
'$ poetry run transcrypto -i bin -o b64 -p eg-key.priv elgamal sign "xyz"\n\n'
|
|
590
|
+
'Xl4hlYK8SHVGw…0fCKJE1XVzA==' # cspell:disable-line
|
|
591
|
+
),
|
|
592
|
+
)
|
|
593
|
+
@clibase.CLIErrorGuard
|
|
594
|
+
def ElGamalSign( # documentation is help/epilog/args # noqa: D103
|
|
595
|
+
*,
|
|
596
|
+
ctx: typer.Context,
|
|
597
|
+
message: str = typer.Argument(..., help='Message to sign'),
|
|
598
|
+
aad: str = typer.Option(
|
|
599
|
+
'',
|
|
600
|
+
'-a',
|
|
601
|
+
'--aad',
|
|
602
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)',
|
|
603
|
+
),
|
|
604
|
+
) -> None:
|
|
605
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
606
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
607
|
+
eg_priv: elgamal.ElGamalPrivateKey = transcrypto.LoadObj(
|
|
608
|
+
key_path, config.protect, elgamal.ElGamalPrivateKey
|
|
609
|
+
)
|
|
610
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
611
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
612
|
+
sig: bytes = eg_priv.Sign(pt, associated_data=aad_bytes)
|
|
613
|
+
config.console.print(transcrypto.BytesToText(sig, config.output_format))
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@eg_app.command(
|
|
617
|
+
'verify',
|
|
618
|
+
help='Verify `signature` for `message` with public key.',
|
|
619
|
+
epilog=(
|
|
620
|
+
'Example:\n\n\n\n'
|
|
621
|
+
'$ poetry run transcrypto -i b64 -p eg-key.pub elgamal verify -- eHl6 '
|
|
622
|
+
'Xl4hlYK8SHVGw…0fCKJE1XVzA==\n\n' # cspell:disable-line
|
|
623
|
+
'El-Gamal signature: OK\n\n'
|
|
624
|
+
'$ poetry run transcrypto -i b64 -p eg-key.pub elgamal verify -- eLl6 '
|
|
625
|
+
'Xl4hlYK8SHVGw…0fCKJE1XVzA==\n\n' # cspell:disable-line
|
|
626
|
+
'El-Gamal signature: INVALID'
|
|
627
|
+
),
|
|
628
|
+
)
|
|
629
|
+
@clibase.CLIErrorGuard
|
|
630
|
+
def ElGamalVerify( # documentation is help/epilog/args # noqa: D103
|
|
631
|
+
*,
|
|
632
|
+
ctx: typer.Context,
|
|
633
|
+
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
634
|
+
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
635
|
+
aad: str = typer.Option(
|
|
636
|
+
'',
|
|
637
|
+
'-a',
|
|
638
|
+
'--aad',
|
|
639
|
+
help='Associated data (optional; has to be exactly the same as used during signing)',
|
|
640
|
+
),
|
|
641
|
+
) -> None:
|
|
642
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
643
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
644
|
+
eg_pub: elgamal.ElGamalPublicKey = transcrypto.LoadObj(
|
|
645
|
+
key_path, config.protect, elgamal.ElGamalPublicKey
|
|
646
|
+
)
|
|
647
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
648
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
649
|
+
sig: bytes = transcrypto.BytesFromText(signature, config.input_format)
|
|
650
|
+
config.console.print(
|
|
651
|
+
'El-Gamal signature: '
|
|
652
|
+
+ ('[green]OK[/]' if eg_pub.Verify(pt, sig, associated_data=aad_bytes) else '[red]INVALID[/]')
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
# ================================== "DSA" COMMAND =================================================
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
dsa_app = typer.Typer(
|
|
660
|
+
no_args_is_help=True,
|
|
661
|
+
help=(
|
|
662
|
+
'DSA (Digital Signature Algorithm) asymmetric signing/verifying. '
|
|
663
|
+
'All methods require file key(s) as `-p`/`--key-path` (see provided examples). '
|
|
664
|
+
'All non-int inputs are raw, or you can use `--input-format <hex|b64|bin>`. '
|
|
665
|
+
'Attention: if you provide `-a`/`--aad` (associated data, AAD), '
|
|
666
|
+
'you will need to provide the same AAD when decrypting/verifying and it is NOT included '
|
|
667
|
+
'in the `signature` returned by these methods! '
|
|
668
|
+
'No measures are taken here to prevent timing attacks.'
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
transcrypto.app.add_typer(dsa_app, name='dsa')
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
@dsa_app.command(
|
|
675
|
+
'shared',
|
|
676
|
+
help=(
|
|
677
|
+
'Generate a shared DSA key with `p-bits`/`q-bits` prime modulus sizes, which is '
|
|
678
|
+
'the first step in key generation. `q-bits` should be larger than the secrets that '
|
|
679
|
+
'will be protected and `p-bits` should be much larger than `q-bits` (e.g. 4096/544). '
|
|
680
|
+
'The shared key can safely be used by any number of users to generate their '
|
|
681
|
+
'private/public key pairs (with the `new` command). The shared keys are "public".'
|
|
682
|
+
),
|
|
683
|
+
epilog=(
|
|
684
|
+
'Example:\n\n\n\n'
|
|
685
|
+
'$ poetry run transcrypto -p dsa-key dsa shared --p-bits 128 --q-bits 32 '
|
|
686
|
+
'# NEVER use such a small key: example only!\n\n'
|
|
687
|
+
"DSA shared key saved to 'dsa-key.shared'"
|
|
688
|
+
),
|
|
689
|
+
)
|
|
690
|
+
@clibase.CLIErrorGuard
|
|
691
|
+
def DSAShared( # documentation is help/epilog/args # noqa: D103
|
|
692
|
+
*,
|
|
693
|
+
ctx: typer.Context,
|
|
694
|
+
p_bits: int = typer.Option(
|
|
695
|
+
4096,
|
|
696
|
+
'-b',
|
|
697
|
+
'--p-bits',
|
|
698
|
+
min=16,
|
|
699
|
+
help='Prime modulus (`p`) size in bits, ≥16; the default (4096) is a safe size',
|
|
700
|
+
),
|
|
701
|
+
q_bits: int = typer.Option(
|
|
702
|
+
544,
|
|
703
|
+
'-q',
|
|
704
|
+
'--q-bits',
|
|
705
|
+
min=8,
|
|
706
|
+
help=(
|
|
707
|
+
'Prime modulus (`q`) size in bits, ≥8; the default (544) is a safe size ***IFF*** you '
|
|
708
|
+
'are protecting symmetric keys or regular hashes'
|
|
709
|
+
),
|
|
710
|
+
),
|
|
711
|
+
) -> None:
|
|
712
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
713
|
+
base_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
714
|
+
dsa_shared: dsa.DSASharedPublicKey = dsa.DSASharedPublicKey.NewShared(p_bits, q_bits)
|
|
715
|
+
transcrypto.SaveObj(dsa_shared, base_path + '.shared', config.protect)
|
|
716
|
+
config.console.print(f'DSA shared key saved to {base_path + ".shared"!r}')
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
@dsa_app.command(
|
|
720
|
+
'new',
|
|
721
|
+
help='Generate an individual DSA private/public key pair from a shared key.',
|
|
722
|
+
epilog=(
|
|
723
|
+
'Example:\n\n\n\n'
|
|
724
|
+
'$ poetry run transcrypto -p dsa-key dsa new\n\n'
|
|
725
|
+
"DSA private/public keys saved to 'dsa-key.priv/.pub'"
|
|
726
|
+
),
|
|
727
|
+
)
|
|
728
|
+
@clibase.CLIErrorGuard
|
|
729
|
+
def DSANew(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
730
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
731
|
+
base_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
732
|
+
dsa_shared: dsa.DSASharedPublicKey = transcrypto.LoadObj(
|
|
733
|
+
base_path + '.shared', config.protect, dsa.DSASharedPublicKey
|
|
734
|
+
)
|
|
735
|
+
dsa_priv: dsa.DSAPrivateKey = dsa.DSAPrivateKey.New(dsa_shared)
|
|
736
|
+
dsa_pub: dsa.DSAPublicKey = dsa.DSAPublicKey.Copy(dsa_priv)
|
|
737
|
+
transcrypto.SaveObj(dsa_priv, base_path + '.priv', config.protect)
|
|
738
|
+
transcrypto.SaveObj(dsa_pub, base_path + '.pub', config.protect)
|
|
739
|
+
config.console.print(f'DSA private/public keys saved to {base_path + ".priv/.pub"!r}')
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
@dsa_app.command(
|
|
743
|
+
'rawsign',
|
|
744
|
+
help=(
|
|
745
|
+
'Raw sign *integer* message with private key (BEWARE: no ECDSA/EdDSA padding or validation). '
|
|
746
|
+
'Output will 2 *integers* in a `s1:s2` format.'
|
|
747
|
+
),
|
|
748
|
+
epilog=(
|
|
749
|
+
'Example:\n\n\n\n'
|
|
750
|
+
'$ poetry run transcrypto -p dsa-key.priv dsa rawsign 999\n\n'
|
|
751
|
+
'2395961484:3435572290'
|
|
752
|
+
),
|
|
753
|
+
)
|
|
754
|
+
@clibase.CLIErrorGuard
|
|
755
|
+
def DSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
756
|
+
*,
|
|
757
|
+
ctx: typer.Context,
|
|
758
|
+
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<`q`'),
|
|
759
|
+
) -> None:
|
|
760
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
761
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
762
|
+
dsa_priv: dsa.DSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, dsa.DSAPrivateKey)
|
|
763
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
764
|
+
m: int = message_i % dsa_priv.prime_seed
|
|
765
|
+
s1: int
|
|
766
|
+
s2: int
|
|
767
|
+
s1, s2 = dsa_priv.RawSign(m)
|
|
768
|
+
config.console.print(f'{s1}:{s2}')
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
@dsa_app.command(
|
|
772
|
+
'rawverify',
|
|
773
|
+
help=(
|
|
774
|
+
'Raw verify *integer* `signature` for *integer* `message` with public key '
|
|
775
|
+
'(BEWARE: no ECDSA/EdDSA padding or validation).'
|
|
776
|
+
),
|
|
777
|
+
epilog=(
|
|
778
|
+
'Example:\n\n\n\n'
|
|
779
|
+
'$ poetry run transcrypto -p dsa-key.pub dsa rawverify 999 2395961484:3435572290\n\n'
|
|
780
|
+
'DSA signature: OK\n\n'
|
|
781
|
+
'$ poetry run transcrypto -p dsa-key.pub dsa rawverify 999 2395961484:3435572291\n\n'
|
|
782
|
+
'DSA signature: INVALID'
|
|
783
|
+
),
|
|
784
|
+
)
|
|
785
|
+
@clibase.CLIErrorGuard
|
|
786
|
+
def DSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
787
|
+
*,
|
|
788
|
+
ctx: typer.Context,
|
|
789
|
+
message: str = typer.Argument(
|
|
790
|
+
..., help='Integer message that was signed earlier, 1≤`message`<`q`'
|
|
791
|
+
),
|
|
792
|
+
signature: str = typer.Argument(
|
|
793
|
+
...,
|
|
794
|
+
help=(
|
|
795
|
+
'Integer putative signature for `message`; expects `s1:s2` format with 2 integers, '
|
|
796
|
+
'`s1`,`s2`<`q`'
|
|
797
|
+
),
|
|
798
|
+
),
|
|
799
|
+
) -> None:
|
|
800
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
801
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
802
|
+
dsa_pub: dsa.DSAPublicKey = dsa.DSAPublicKey.Copy(
|
|
803
|
+
transcrypto.LoadObj(key_path, config.protect, dsa.DSAPublicKey)
|
|
804
|
+
)
|
|
805
|
+
message_i: int = transcrypto.ParseInt(message, min_value=1)
|
|
806
|
+
signature_i: tuple[int, int] = transcrypto.ParseIntPairCLI(signature)
|
|
807
|
+
m: int = message_i % dsa_pub.prime_seed
|
|
808
|
+
config.console.print(
|
|
809
|
+
'DSA signature: ' + ('[green]OK[/]' if dsa_pub.RawVerify(m, signature_i) else '[red]INVALID[/]')
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
@dsa_app.command(
|
|
814
|
+
'sign',
|
|
815
|
+
help='Sign message with private key.',
|
|
816
|
+
epilog=(
|
|
817
|
+
'Example:\n\n\n\n'
|
|
818
|
+
'$ poetry run transcrypto -i bin -o b64 -p dsa-key.priv dsa sign "xyz"\n\n'
|
|
819
|
+
'yq8InJVpViXh9…BD4par2XuA='
|
|
820
|
+
),
|
|
821
|
+
)
|
|
822
|
+
@clibase.CLIErrorGuard
|
|
823
|
+
def DSASign( # documentation is help/epilog/args # noqa: D103
|
|
824
|
+
*,
|
|
825
|
+
ctx: typer.Context,
|
|
826
|
+
message: str = typer.Argument(..., help='Message to sign'),
|
|
827
|
+
aad: str = typer.Option(
|
|
828
|
+
'',
|
|
829
|
+
'-a',
|
|
830
|
+
'--aad',
|
|
831
|
+
help='Associated data (optional; has to be separately sent to receiver/stored)',
|
|
832
|
+
),
|
|
833
|
+
) -> None:
|
|
834
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
835
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
836
|
+
dsa_priv: dsa.DSAPrivateKey = transcrypto.LoadObj(key_path, config.protect, dsa.DSAPrivateKey)
|
|
837
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
838
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
839
|
+
sig: bytes = dsa_priv.Sign(pt, associated_data=aad_bytes)
|
|
840
|
+
config.console.print(transcrypto.BytesToText(sig, config.output_format))
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
@dsa_app.command(
|
|
844
|
+
'verify',
|
|
845
|
+
help='Verify `signature` for `message` with public key.',
|
|
846
|
+
epilog=(
|
|
847
|
+
'Example:\n\n\n\n'
|
|
848
|
+
'$ poetry run transcrypto -i b64 -p dsa-key.pub dsa verify -- '
|
|
849
|
+
'eHl6 yq8InJVpViXh9…BD4par2XuA=\n\n'
|
|
850
|
+
'DSA signature: OK\n\n'
|
|
851
|
+
'$ poetry run transcrypto -i b64 -p dsa-key.pub dsa verify -- '
|
|
852
|
+
'eLl6 yq8InJVpViXh9…BD4par2XuA=\n\n'
|
|
853
|
+
'DSA signature: INVALID'
|
|
854
|
+
),
|
|
855
|
+
)
|
|
856
|
+
@clibase.CLIErrorGuard
|
|
857
|
+
def DSAVerify( # documentation is help/epilog/args # noqa: D103
|
|
858
|
+
*,
|
|
859
|
+
ctx: typer.Context,
|
|
860
|
+
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
861
|
+
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
862
|
+
aad: str = typer.Option(
|
|
863
|
+
'',
|
|
864
|
+
'-a',
|
|
865
|
+
'--aad',
|
|
866
|
+
help='Associated data (optional; has to be exactly the same as used during signing)',
|
|
867
|
+
),
|
|
868
|
+
) -> None:
|
|
869
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
870
|
+
key_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
871
|
+
dsa_pub: dsa.DSAPublicKey = transcrypto.LoadObj(key_path, config.protect, dsa.DSAPublicKey)
|
|
872
|
+
aad_bytes: bytes | None = transcrypto.BytesFromText(aad, config.input_format) if aad else None
|
|
873
|
+
pt: bytes = transcrypto.BytesFromText(message, config.input_format)
|
|
874
|
+
sig: bytes = transcrypto.BytesFromText(signature, config.input_format)
|
|
875
|
+
config.console.print(
|
|
876
|
+
'DSA signature: '
|
|
877
|
+
+ ('[green]OK[/]' if dsa_pub.Verify(pt, sig, associated_data=aad_bytes) else '[red]INVALID[/]')
|
|
878
|
+
)
|