transcrypto 1.7.0__tar.gz → 2.0.0__tar.gz
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-1.7.0 → transcrypto-2.0.0}/PKG-INFO +111 -109
- {transcrypto-1.7.0 → transcrypto-2.0.0}/README.md +107 -106
- {transcrypto-1.7.0 → transcrypto-2.0.0}/pyproject.toml +16 -15
- {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/__init__.py +1 -1
- transcrypto-2.0.0/src/transcrypto/cli/__init__.py +3 -0
- transcrypto-2.0.0/src/transcrypto/cli/aeshash.py +370 -0
- transcrypto-2.0.0/src/transcrypto/cli/bidsecret.py +336 -0
- transcrypto-2.0.0/src/transcrypto/cli/clibase.py +183 -0
- transcrypto-2.0.0/src/transcrypto/cli/intmath.py +429 -0
- transcrypto-2.0.0/src/transcrypto/cli/publicalgos.py +878 -0
- transcrypto-2.0.0/src/transcrypto/core/__init__.py +3 -0
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/aes.py +17 -29
- transcrypto-2.0.0/src/transcrypto/core/bid.py +161 -0
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/dsa.py +28 -27
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/elgamal.py +33 -32
- transcrypto-2.0.0/src/transcrypto/core/hashes.py +96 -0
- transcrypto-2.0.0/src/transcrypto/core/key.py +735 -0
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/modmath.py +91 -17
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/rsa.py +51 -50
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/sss.py +27 -26
- {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/profiler.py +29 -13
- transcrypto-2.0.0/src/transcrypto/transcrypto.py +471 -0
- transcrypto-2.0.0/src/transcrypto/utils/__init__.py +3 -0
- transcrypto-2.0.0/src/transcrypto/utils/base.py +72 -0
- transcrypto-2.0.0/src/transcrypto/utils/human.py +278 -0
- transcrypto-2.0.0/src/transcrypto/utils/logging.py +139 -0
- transcrypto-2.0.0/src/transcrypto/utils/saferandom.py +102 -0
- transcrypto-2.0.0/src/transcrypto/utils/stats.py +360 -0
- transcrypto-2.0.0/src/transcrypto/utils/timer.py +175 -0
- transcrypto-1.7.0/src/transcrypto/base.py +0 -1918
- transcrypto-1.7.0/src/transcrypto/transcrypto.py +0 -2407
- {transcrypto-1.7.0 → transcrypto-2.0.0}/LICENSE +0 -0
- {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/constants.py +0 -0
- {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/py.typed +0 -0
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: transcrypto
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Basic crypto primitives, not intended for actual use, but as a companion to --Criptografia, Métodos e Algoritmos--
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Keywords: cryptography,validation,encryption,signing,random-generation,prime-numbers,aes-encryption,decryption,rsa-cryptography,elgamal-encryption,dsa-algorithm,modular-mathematics,rsa,dsa,elgamal,aes,python,poetry,typer,rich,cli
|
|
8
8
|
Author: Daniel Balparda
|
|
9
9
|
Author-email: balparda@github.com
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
15
|
Classifier: Operating System :: OS Independent
|
|
14
16
|
Classifier: Topic :: Utilities
|
|
15
17
|
Classifier: Topic :: Security :: Cryptography
|
|
@@ -17,7 +19,6 @@ Requires-Dist: cryptography (>=46.0)
|
|
|
17
19
|
Requires-Dist: gmpy2 (>=2.2)
|
|
18
20
|
Requires-Dist: platformdirs (>=4.5)
|
|
19
21
|
Requires-Dist: rich (>=14.3)
|
|
20
|
-
Requires-Dist: scipy (>=1.17)
|
|
21
22
|
Requires-Dist: typer (>=0.21)
|
|
22
23
|
Requires-Dist: zstandard (>=0.25)
|
|
23
24
|
Project-URL: Changelog, https://github.com/balparda/transcrypto/blob/main/CHANGELOG.md
|
|
@@ -118,25 +119,24 @@ To use in your project just do:
|
|
|
118
119
|
pip3 install transcrypto
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
and then `from transcrypto import rsa` (or other parts of the library) for using it.
|
|
122
|
+
and then `from transcrypto.core import rsa` (or other parts of the library) for using it.
|
|
122
123
|
|
|
123
124
|
Known dependencies:
|
|
124
125
|
|
|
125
126
|
- [zstandard](https://pypi.org/project/zstandard/) ([docs](https://python-zstandard.readthedocs.org/))
|
|
126
127
|
- [cryptography](https://pypi.org/project/cryptography/) ([docs](https://cryptography.io/en/latest/))
|
|
127
128
|
- [gmpy2](https://pypi.org/project/gmpy2/) ([docs](https://gmpy2.readthedocs.io/en/latest/))
|
|
128
|
-
- [scipy](https://pypi.org/project/scipy/) ([docs](https://docs.scipy.org/doc/scipy/))
|
|
129
129
|
|
|
130
130
|
### Base Library
|
|
131
131
|
|
|
132
132
|
#### Humanized Sizes (IEC binary)
|
|
133
133
|
|
|
134
134
|
```py
|
|
135
|
-
from transcrypto import
|
|
135
|
+
from transcrypto.utils import human
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
human.HumanizedBytes(512) # '512 B'
|
|
138
|
+
human.HumanizedBytes(2048) # '2.000 KiB'
|
|
139
|
+
human.HumanizedBytes(5 * 1024**3) # '5.000 GiB'
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`).
|
|
@@ -146,22 +146,24 @@ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `
|
|
|
146
146
|
- For values `≥1024`, returns 3 decimals.
|
|
147
147
|
|
|
148
148
|
- standard: 1 KiB = 1024 B, 1 MiB = 1024 KiB, …
|
|
149
|
-
- errors: negative inputs raise `InputError`
|
|
149
|
+
- errors: negative inputs raise `base.InputError`
|
|
150
150
|
|
|
151
151
|
#### Humanized Decimal Quantities (SI)
|
|
152
152
|
|
|
153
153
|
```py
|
|
154
|
+
from transcrypto.utils import human
|
|
155
|
+
|
|
154
156
|
# Base (unitless)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
human.HumanizedDecimal(950) # '950'
|
|
158
|
+
human.HumanizedDecimal(1500) # '1.500 k'
|
|
157
159
|
|
|
158
160
|
# With a unit (trimmed and attached)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
human.HumanizedDecimal(1500, unit=' Hz ') # '1.500 kHz'
|
|
162
|
+
human.HumanizedDecimal(0.123456, unit='V') # '123.456 mV'
|
|
161
163
|
|
|
162
164
|
# Large magnitudes
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
human.HumanizedDecimal(3_200_000) # '3.200 M'
|
|
166
|
+
human.HumanizedDecimal(7.2e12, unit='B/s') # '7.200 TB/s'
|
|
165
167
|
```
|
|
166
168
|
|
|
167
169
|
Scales by powers of 1000 using SI prefixes to keep the displayed value in roughly `[1, 1000)` when possible.
|
|
@@ -171,17 +173,19 @@ Scales by powers of 1000 using SI prefixes to keep the displayed value in roughl
|
|
|
171
173
|
- Formatting uses 3 decimals for non-integer/unscaled values and for scaled values.
|
|
172
174
|
|
|
173
175
|
- unit handling: `unit` is stripped; `<1000` values include a space before the unit (`'950 Hz'`)
|
|
174
|
-
- errors: non-finite inputs raise `InputError` (negative values are supported and keep a leading `-`)
|
|
176
|
+
- errors: non-finite inputs raise `base.InputError` (negative values are supported and keep a leading `-`)
|
|
175
177
|
|
|
176
178
|
#### Humanized Durations
|
|
177
179
|
|
|
178
180
|
```py
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
from transcrypto.utils import human
|
|
182
|
+
|
|
183
|
+
human.HumanizedSeconds(0) # '0.000 s'
|
|
184
|
+
human.HumanizedSeconds(0.000004) # '4.000 µs'
|
|
185
|
+
human.HumanizedSeconds(0.25) # '250.000 ms'
|
|
186
|
+
human.HumanizedSeconds(42) # '42.000 s'
|
|
187
|
+
human.HumanizedSeconds(3661) # '1.017 h'
|
|
188
|
+
human.HumanizedSeconds(172800) # '2.000 d'
|
|
185
189
|
```
|
|
186
190
|
|
|
187
191
|
Chooses an appropriate time unit based on magnitude and formats with fixed precision:
|
|
@@ -193,21 +197,21 @@ Chooses an appropriate time unit based on magnitude and formats with fixed preci
|
|
|
193
197
|
- `< 24 h`: hours with three decimals (`h`)
|
|
194
198
|
- `≥ 24 h`: days with three decimals (`d`)
|
|
195
199
|
- special case: `0 → '0.000 s'`
|
|
196
|
-
- errors: negative or non-finite inputs raise `InputError`
|
|
200
|
+
- errors: negative or non-finite inputs raise `base.InputError`
|
|
197
201
|
|
|
198
202
|
#### Execution Timing
|
|
199
203
|
|
|
200
204
|
A flexible timing utility that works as a **context manager**, **decorator**, or **manual timer object**.
|
|
201
205
|
|
|
202
206
|
```py
|
|
203
|
-
from transcrypto import
|
|
207
|
+
from transcrypto.utils import timer
|
|
204
208
|
import time
|
|
205
209
|
```
|
|
206
210
|
|
|
207
211
|
##### Context manager
|
|
208
212
|
|
|
209
213
|
```py
|
|
210
|
-
with
|
|
214
|
+
with timer.Timer('Block timing'):
|
|
211
215
|
time.sleep(1.2)
|
|
212
216
|
# → logs: "Block timing: 1.200 s" (default via logging.info)
|
|
213
217
|
```
|
|
@@ -217,7 +221,7 @@ Starts timing on entry, stops on exit, and reports elapsed time automatically.
|
|
|
217
221
|
##### Decorator
|
|
218
222
|
|
|
219
223
|
```py
|
|
220
|
-
@
|
|
224
|
+
@timer.Timer('Function timing')
|
|
221
225
|
def slow_function():
|
|
222
226
|
time.sleep(0.8)
|
|
223
227
|
|
|
@@ -230,7 +234,7 @@ Wraps a function so that each call is automatically timed.
|
|
|
230
234
|
##### Manual use
|
|
231
235
|
|
|
232
236
|
```py
|
|
233
|
-
tm =
|
|
237
|
+
tm = timer.Timer('Inline timing', emit_print=True)
|
|
234
238
|
tm.Start()
|
|
235
239
|
time.sleep(0.1)
|
|
236
240
|
tm.Stop() # prints: "Inline timing: 0.100 s"
|
|
@@ -256,7 +260,7 @@ Manual control over `Start()` and `Stop()` for precise measurement of custom int
|
|
|
256
260
|
These helpers turn arbitrary Python objects into compressed and/or encrypted binary blobs, and back again — with detailed timing and size logging.
|
|
257
261
|
|
|
258
262
|
```py
|
|
259
|
-
from transcrypto import
|
|
263
|
+
from transcrypto.core import key
|
|
260
264
|
```
|
|
261
265
|
|
|
262
266
|
##### Serialize
|
|
@@ -265,17 +269,17 @@ from transcrypto import base
|
|
|
265
269
|
data = {'x': 42, 'y': 'hello'}
|
|
266
270
|
|
|
267
271
|
# Basic serialization
|
|
268
|
-
blob =
|
|
272
|
+
blob = key.Serialize(data)
|
|
269
273
|
|
|
270
274
|
# With compression and encryption
|
|
271
|
-
blob =
|
|
275
|
+
blob = key.Serialize(
|
|
272
276
|
data,
|
|
273
277
|
compress=9, # compression level (-22..22, default=3)
|
|
274
|
-
|
|
278
|
+
encryption_key=my_encryptor # must implement `key.Encryptor` (e.g., `aes.AESKey`)
|
|
275
279
|
)
|
|
276
280
|
|
|
277
281
|
# Save directly to file
|
|
278
|
-
|
|
282
|
+
key.Serialize(data, file_path='/tmp/data.blob')
|
|
279
283
|
```
|
|
280
284
|
|
|
281
285
|
Serialization path:
|
|
@@ -303,19 +307,19 @@ Compression levels:
|
|
|
303
307
|
| 11…15 | Much slower | Slight gains | Large archives, not for runtime use |
|
|
304
308
|
| 16…22 | Very slow | Tiny gains | Archival-only, multi-GB datasets |
|
|
305
309
|
|
|
306
|
-
Errors: invalid compression level is clamped to range; other input errors raise `InputError`.
|
|
310
|
+
Errors: invalid compression level is clamped to range; other input errors raise `base.InputError`.
|
|
307
311
|
|
|
308
312
|
##### DeSerialize
|
|
309
313
|
|
|
310
314
|
```py
|
|
311
315
|
# From in-memory blob
|
|
312
|
-
obj =
|
|
316
|
+
obj = key.DeSerialize(data=blob)
|
|
313
317
|
|
|
314
318
|
# From file
|
|
315
|
-
obj =
|
|
319
|
+
obj = key.DeSerialize(file_path='/tmp/data.blob')
|
|
316
320
|
|
|
317
321
|
# With decryption
|
|
318
|
-
obj =
|
|
322
|
+
obj = key.DeSerialize(data=blob, decryption_key=my_decryptor)
|
|
319
323
|
```
|
|
320
324
|
|
|
321
325
|
Deserialization path:
|
|
@@ -331,82 +335,82 @@ data/file → (decrypt) → (decompress if Zstd) → unpickle
|
|
|
331
335
|
|
|
332
336
|
- Exactly one of `data` or `file_path` must be provided.
|
|
333
337
|
- `file_path` must exist; `data` must be at least 4 bytes.
|
|
334
|
-
- Wrong key / authentication failure can raise `CryptoError`.
|
|
338
|
+
- Wrong key / authentication failure can raise `key.CryptoError`.
|
|
335
339
|
- Corrupted compressed blobs typically raise `zstandard.ZstdError` during decompression.
|
|
336
340
|
|
|
337
341
|
#### Cryptographically Secure Randomness
|
|
338
342
|
|
|
339
|
-
These helpers live in `
|
|
343
|
+
These helpers live in `saferandom` and wrap Python’s `secrets` with additional checks and guarantees for crypto use-cases.
|
|
340
344
|
|
|
341
345
|
```py
|
|
342
|
-
from transcrypto import
|
|
346
|
+
from transcrypto.core import bid
|
|
343
347
|
```
|
|
344
348
|
|
|
345
349
|
##### Fixed-size random integers
|
|
346
350
|
|
|
347
351
|
```py
|
|
348
352
|
# Generate a 256-bit integer (first bit always set)
|
|
349
|
-
r =
|
|
353
|
+
r = saferandom.RandBits(256)
|
|
350
354
|
assert r.bit_length() == 256
|
|
351
355
|
```
|
|
352
356
|
|
|
353
357
|
Produces a crypto-secure random integer with exactly `n_bits` bits (`≥ 8`). The most significant bit is guaranteed to be `1`, so entropy is \~`n_bits−1` — negligible for large crypto sizes.
|
|
354
358
|
|
|
355
|
-
- errors: `n_bits < 8` → `InputError`
|
|
359
|
+
- errors: `n_bits < 8` → `base.InputError`
|
|
356
360
|
|
|
357
361
|
##### Uniform random integers in a range
|
|
358
362
|
|
|
359
363
|
```py
|
|
360
364
|
# Uniform between [10, 20] inclusive
|
|
361
|
-
n =
|
|
365
|
+
n = saferandom.RandInt(10, 20)
|
|
362
366
|
assert 10 <= n <= 20
|
|
363
367
|
```
|
|
364
368
|
|
|
365
369
|
Returns a crypto-secure integer uniformly distributed over the closed interval `[min_int, max_int]`.
|
|
366
370
|
|
|
367
371
|
- constraints: `min_int ≥ 0` and `< max_int`
|
|
368
|
-
- errors: invalid bounds → `InputError`
|
|
372
|
+
- errors: invalid bounds → `base.InputError`
|
|
369
373
|
|
|
370
374
|
##### In-place secure shuffle
|
|
371
375
|
|
|
372
376
|
```py
|
|
373
377
|
deck = list(range(10))
|
|
374
|
-
|
|
378
|
+
saferandom.RandShuffle(deck)
|
|
375
379
|
print(deck) # securely shuffled order
|
|
376
380
|
```
|
|
377
381
|
|
|
378
382
|
Performs an in-place Fisher–Yates shuffle using `secrets.randbelow`. Suitable for sensitive data ordering.
|
|
379
383
|
|
|
380
384
|
- constraints: sequence length ≥ 2
|
|
381
|
-
- errors: shorter sequences → `InputError`
|
|
385
|
+
- errors: shorter sequences → `base.InputError`
|
|
382
386
|
|
|
383
387
|
##### Random byte strings
|
|
384
388
|
|
|
385
389
|
```py
|
|
386
390
|
# 32 random bytes
|
|
387
|
-
b =
|
|
391
|
+
b = saferandom.RandBytes(32)
|
|
388
392
|
assert len(b) == 32
|
|
389
393
|
```
|
|
390
394
|
|
|
391
395
|
Generates `n_bytes` of high-quality crypto-secure random data.
|
|
392
396
|
|
|
393
397
|
- constraints: `n_bytes ≥ 1`
|
|
394
|
-
- errors: smaller values → `InputError`
|
|
398
|
+
- errors: smaller values → `base.InputError`
|
|
395
399
|
|
|
396
400
|
#### Computing the Greatest Common Divisor
|
|
397
401
|
|
|
398
402
|
```py
|
|
399
|
-
>>> from transcrypto import
|
|
400
|
-
>>>
|
|
403
|
+
>>> from transcrypto.core import modmath
|
|
404
|
+
>>> modmath.GCD(462, 1071)
|
|
401
405
|
21
|
|
402
|
-
>>>
|
|
406
|
+
>>> modmath.GCD(0, 17)
|
|
403
407
|
17
|
|
404
408
|
```
|
|
405
409
|
|
|
406
410
|
The function is `O(log(min(a, b)))` and handles arbitrarily large integers. To find Bézout coefficients `(x, y)` such that `ax + by = gcd(a, b)` do:
|
|
407
411
|
|
|
408
412
|
```py
|
|
409
|
-
>>>
|
|
413
|
+
>>> modmath.ExtendedGCD(462, 1071)
|
|
410
414
|
(21, 7, -3)
|
|
411
415
|
>>> 462 * 7 + 1071 * (-3)
|
|
412
416
|
21
|
|
@@ -421,7 +425,7 @@ Use-cases:
|
|
|
421
425
|
#### Fast Modular Arithmetic
|
|
422
426
|
|
|
423
427
|
```py
|
|
424
|
-
from transcrypto import modmath
|
|
428
|
+
from transcrypto.core import modmath
|
|
425
429
|
|
|
426
430
|
m = 2**256 - 189 # a large prime modulus
|
|
427
431
|
|
|
@@ -442,7 +446,7 @@ exp = modmath.ModExp(3, 10**20, m) # ≈ log₂(y) time, handles huge exponent
|
|
|
442
446
|
##### Chinese Remainder Theorem (CRT) – Pair
|
|
443
447
|
|
|
444
448
|
```py
|
|
445
|
-
from transcrypto import modmath
|
|
449
|
+
from transcrypto.core import modmath
|
|
446
450
|
|
|
447
451
|
# Solve:
|
|
448
452
|
# x ≡ 2 (mod 3)
|
|
@@ -465,7 +469,7 @@ x ≡ a2 (mod m2)
|
|
|
465
469
|
- `m1 ≥ 2`, `m2 ≥ 2`, `m1 != m2`
|
|
466
470
|
- `gcd(m1, m2) == 1` (co-prime)
|
|
467
471
|
- **Errors**:
|
|
468
|
-
- invalid modulus values → `InputError`
|
|
472
|
+
- invalid modulus values → `base.InputError`
|
|
469
473
|
- non co-prime moduli → `ModularDivideError`
|
|
470
474
|
|
|
471
475
|
This function is a 2-modulus variant; for multiple moduli, apply it iteratively or use a general CRT solver.
|
|
@@ -511,13 +515,13 @@ for k, m_p, perfect in modmath.MersennePrimesGenerator(0):
|
|
|
511
515
|
Simple, fixed-output-size wrappers over Python’s `hashlib` for common digest operations, plus file hashing.
|
|
512
516
|
|
|
513
517
|
```py
|
|
514
|
-
from transcrypto import
|
|
518
|
+
from transcrypto.core import hashes
|
|
515
519
|
```
|
|
516
520
|
|
|
517
521
|
##### SHA-256 hashing
|
|
518
522
|
|
|
519
523
|
```py
|
|
520
|
-
h =
|
|
524
|
+
h = hashes.Hash256(b'hello world')
|
|
521
525
|
assert len(h) == 32 # bytes
|
|
522
526
|
print(h.hex()) # 64 hex chars
|
|
523
527
|
```
|
|
@@ -527,7 +531,7 @@ Computes the SHA-256 digest of a byte string, returning exactly 32 bytes (256 bi
|
|
|
527
531
|
##### SHA-512 hashing
|
|
528
532
|
|
|
529
533
|
```py
|
|
530
|
-
h =
|
|
534
|
+
h = hashes.Hash512(b'hello world')
|
|
531
535
|
assert len(h) == 64 # bytes
|
|
532
536
|
print(h.hex()) # 128 hex chars
|
|
533
537
|
```
|
|
@@ -538,11 +542,11 @@ Computes the SHA-512 digest of a byte string, returning exactly 64 bytes (512 bi
|
|
|
538
542
|
|
|
539
543
|
```py
|
|
540
544
|
# Default SHA-256
|
|
541
|
-
fh =
|
|
545
|
+
fh = hashes.FileHash('/path/to/file')
|
|
542
546
|
print(fh.hex())
|
|
543
547
|
|
|
544
548
|
# SHA-512
|
|
545
|
-
fh2 =
|
|
549
|
+
fh2 = hashes.FileHash('/path/to/file', digest='sha512')
|
|
546
550
|
```
|
|
547
551
|
|
|
548
552
|
Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha512'` switches to SHA-512.
|
|
@@ -550,17 +554,19 @@ Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha
|
|
|
550
554
|
- constraints:
|
|
551
555
|
- `digest` must be `'sha256'` or `'sha512'`
|
|
552
556
|
- `full_path` must exist
|
|
553
|
-
- errors: invalid digest or missing file → `InputError`
|
|
557
|
+
- errors: invalid digest or missing file → `base.InputError`
|
|
554
558
|
|
|
555
559
|
#### Symmetric Encryption Interface
|
|
556
560
|
|
|
557
|
-
`
|
|
561
|
+
`key.Encryptor` and `key.Decryptor` are runtime-checkable protocols that define the **byte-in / byte-out** contract for symmetric ciphers.
|
|
558
562
|
|
|
559
563
|
- **Metadata handling** — if the algorithm uses a `nonce` or `tag`, the implementation must handle it internally (e.g., append it to ciphertext).
|
|
560
|
-
- **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `InputError`.
|
|
564
|
+
- **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `base.InputError`.
|
|
561
565
|
|
|
562
566
|
```py
|
|
563
|
-
|
|
567
|
+
from transcrypto.core import key
|
|
568
|
+
|
|
569
|
+
class MyAES(key.Encryptor, key.Decryptor):
|
|
564
570
|
def Encrypt(self, plaintext: bytes, *, associated_data=None) -> bytes:
|
|
565
571
|
...
|
|
566
572
|
def Decrypt(self, ciphertext: bytes, *, associated_data=None) -> bytes:
|
|
@@ -574,14 +580,14 @@ Cryptographic objects all derive from the `CryptoKey` class and will all have so
|
|
|
574
580
|
- Will be safe to log and print, i.e., implement safe `__str__()` and `__repr__()` methods (in actuality `repr` will be exactly the same as `str`). The `__str__()` should always fully print the public parts of the object and obfuscate the private ones. This obfuscation allows for some debugging, if needed, but if the secrets are "too short" then it can be defeated by brute force. For usual crypto defaults the obfuscation is fine. The obfuscation is the fist 4 bytes of the SHA-512 for the value followed by an ellipsis (e.g. `c9626f16…`).
|
|
575
581
|
- It will have a `_DebugDump()` method that **does print secrets** and can be used for **debugging only**.
|
|
576
582
|
- Can be easily serialized to `bytes` by the `blob` property and to base-64 encoded `str` by the `encoded` property.
|
|
577
|
-
- Can be serialized encrypted to `bytes` by the `Blob(
|
|
578
|
-
- Can be instantiated back as an object from `str` or `bytes` using the `Load(data,
|
|
583
|
+
- Can be serialized encrypted to `bytes` by the `Blob(encryption_key=[key.Encryptor])` method and to encrypted base-64 encoded `str` by the `Encoded(encryption_key=[key.Encryptor])` method.
|
|
584
|
+
- Can be instantiated back as an object from `str` or `bytes` using the `Load(data, decryption_key=[key.Decryptor] | None)` method. The `Load()` will decide how to build the object and will work universally with all the serialization options discussed above.
|
|
579
585
|
|
|
580
586
|
Example:
|
|
581
587
|
|
|
582
588
|
<!-- cspell:disable -->
|
|
583
589
|
```py
|
|
584
|
-
from transcrypto import
|
|
590
|
+
from transcrypto.core import aes, rsa
|
|
585
591
|
|
|
586
592
|
priv = rsa.RSAPrivateKey.New(512) # small key, but good for this example
|
|
587
593
|
print(str(priv)) # safe, no secrets
|
|
@@ -596,12 +602,12 @@ print(priv.blob)
|
|
|
596
602
|
print(priv.encoded)
|
|
597
603
|
# ▶ KLUv_WBwAIELAIAElWUBAAAAAAAAjA90cmFuc2NyeXB0by5yc2GUjA1SU0FQcml2YXRlS2V5lJOUKYGUXZQoikHf1EvsmZedAZve7TrLmobLAwuRIr_77TLG6G_0fsLGThERVJu075be8PLjUQYnLXcacZFQ5Fb1Iy1WtiE985euAEoBAAEAiiFR9ngiXMzkf41o5CRBY3h0D4DJVisDDhLmAWsiaHggzQCKIS_cmQ6MKXCtROtC7c_Mrsi9A-9NM8DksaHaRwvy6uTZAIpB4TVbsLxc41TEc19wIzpxbi9y5dW5gdfTkRQSSiz0ijmb8Xk3pyBfKAv8JbHp8Yv48gNZUfX67qq0J7yhJqeUoACKIbFb2kTNRzSqm3JRtjc2BPS-FnLFdadlFcV4-6IW7eqLAIogFZfzDN39gZLR9uTz4KHSTaqxWrJgP8-YYssjss6FlFKKIIItgCDv7ompNpY8gBs5bibN8XTsr-JOYSntDVT5Fe5vZWIu
|
|
598
604
|
|
|
599
|
-
|
|
600
|
-
print(
|
|
605
|
+
aes_key = aes.AESKey(key256=b'x' * 32)
|
|
606
|
+
print(aes_key)
|
|
601
607
|
# ▶ AESKey(key256=86a86df7…)
|
|
602
608
|
|
|
603
|
-
encrypted = priv.Blob(
|
|
604
|
-
print(priv == rsa.RSAPrivateKey.Load(encrypted,
|
|
609
|
+
encrypted = priv.Blob(encryption_key=aes_key)
|
|
610
|
+
print(priv == rsa.RSAPrivateKey.Load(encrypted, decryption_key=aes_key))
|
|
605
611
|
# ▶ True
|
|
606
612
|
```
|
|
607
613
|
<!-- cspell:enable -->
|
|
@@ -614,14 +620,14 @@ Also includes a high-iteration PBKDF2-based key derivation from static passwords
|
|
|
614
620
|
##### Key creation
|
|
615
621
|
|
|
616
622
|
```py
|
|
617
|
-
from transcrypto import aes
|
|
623
|
+
from transcrypto.core import aes
|
|
618
624
|
|
|
619
625
|
# From raw bytes (must be exactly 32 bytes)
|
|
620
|
-
|
|
626
|
+
aes_key = aes.AESKey(key256=b'\x00' * 32)
|
|
621
627
|
|
|
622
628
|
# From a static password (slow, high-iteration PBKDF2-SHA256)
|
|
623
|
-
|
|
624
|
-
print(
|
|
629
|
+
aes_key = aes.AESKey.FromStaticPassword('correct horse battery staple')
|
|
630
|
+
print(aes_key.encoded) # URL-safe Base64
|
|
625
631
|
```
|
|
626
632
|
|
|
627
633
|
- **Length**: `key256` must be exactly 32 bytes
|
|
@@ -636,10 +642,10 @@ data = b'secret message'
|
|
|
636
642
|
aad = b'metadata'
|
|
637
643
|
|
|
638
644
|
# Encrypt (returns IV + ciphertext + tag)
|
|
639
|
-
ct =
|
|
645
|
+
ct = aes_key.Encrypt(data, associated_data=aad)
|
|
640
646
|
|
|
641
647
|
# Decrypt
|
|
642
|
-
pt =
|
|
648
|
+
pt = aes_key.Decrypt(ct, associated_data=aad)
|
|
643
649
|
assert pt == data
|
|
644
650
|
```
|
|
645
651
|
|
|
@@ -648,13 +654,13 @@ assert pt == data
|
|
|
648
654
|
- Authenticated tag (128-bit) ensures integrity
|
|
649
655
|
- Optional `associated_data` is authenticated but not encrypted
|
|
650
656
|
- **Errors**:
|
|
651
|
-
- Tag mismatch or wrong key → `CryptoError`
|
|
657
|
+
- Tag mismatch or wrong key → `key.CryptoError`
|
|
652
658
|
|
|
653
659
|
##### AES-256 + ECB (unsafe, fixed block only)
|
|
654
660
|
|
|
655
661
|
```py
|
|
656
662
|
# ECB mode is for 16-byte block encoding ONLY
|
|
657
|
-
ecb =
|
|
663
|
+
ecb = aes_key.ECBEncoder()
|
|
658
664
|
|
|
659
665
|
block = b'16-byte string!!'
|
|
660
666
|
ct_block = ecb.Encrypt(block)
|
|
@@ -679,7 +685,7 @@ Key points:
|
|
|
679
685
|
|
|
680
686
|
- **GCM mode** is secure for general use; ECB mode is for special low-level operations
|
|
681
687
|
- **Static password derivation** is intentionally slow to resist brute force
|
|
682
|
-
- All sizes and parameters are validated with `InputError` on misuse
|
|
688
|
+
- All sizes and parameters are validated with `base.InputError` on misuse
|
|
683
689
|
|
|
684
690
|
#### RSA (Rivest-Shamir-Adleman) Public Cryptography
|
|
685
691
|
|
|
@@ -690,7 +696,7 @@ This implementation is raw RSA, no OAEP or PSS! It works on the actual integers.
|
|
|
690
696
|
By default and deliberate choice the *encryption exponent* will be either 7 or 65537, depending on the size of `phi=(p-1)*(q-1)`. If `phi` allows it the larger one will be chosen to avoid Coppersmith attacks.
|
|
691
697
|
|
|
692
698
|
```py
|
|
693
|
-
from transcrypto import rsa
|
|
699
|
+
from transcrypto.core import rsa
|
|
694
700
|
|
|
695
701
|
# Generate a key pair
|
|
696
702
|
priv = rsa.RSAPrivateKey.New(2048) # 2048-bit modulus
|
|
@@ -735,7 +741,7 @@ This is **raw El-Gamal** over a prime field — no padding, no hashing — and i
|
|
|
735
741
|
For real-world deployments, use a high-level library with authenticated encryption and proper encoding.
|
|
736
742
|
|
|
737
743
|
```py
|
|
738
|
-
from transcrypto import elgamal
|
|
744
|
+
from transcrypto.core import elgamal
|
|
739
745
|
|
|
740
746
|
# Shared parameters (prime modulus, group base) for a group
|
|
741
747
|
shared = elgamal.ElGamalSharedPublicKey.NewShared(256)
|
|
@@ -771,13 +777,13 @@ Key points:
|
|
|
771
777
|
|
|
772
778
|
- **Security parameters**:
|
|
773
779
|
- Recommended `prime_modulus` bit length ≥ 2048 for real security
|
|
774
|
-
- Random values from `
|
|
780
|
+
- Random values from `saferandom.RandBits`
|
|
775
781
|
- **Ephemeral keys**:
|
|
776
782
|
- Fresh per encryption/signature
|
|
777
783
|
- Must satisfy `gcd(k, p-1) == 1`
|
|
778
784
|
- **Errors**:
|
|
779
|
-
- Bad ranges → `InputError`
|
|
780
|
-
- Invalid math relationships → `CryptoError`
|
|
785
|
+
- Bad ranges → `base.InputError`
|
|
786
|
+
- Invalid math relationships → `key.CryptoError`
|
|
781
787
|
- **Group sharing**:
|
|
782
788
|
- Multiple parties can share `(p, g)` but have different `(individual_base, decrypt_exp)`
|
|
783
789
|
|
|
@@ -788,7 +794,7 @@ Key points:
|
|
|
788
794
|
This is **raw DSA** over a prime field — **no hashing or padding**. You sign/verify **integers** modulo `q` (`prime_seed`). For real use, hash the message first (e.g., SHA-256) and then map to an integer `< q`.
|
|
789
795
|
|
|
790
796
|
```py
|
|
791
|
-
from transcrypto import dsa
|
|
797
|
+
from transcrypto.core import dsa
|
|
792
798
|
|
|
793
799
|
# Shared parameters (p, q, g) - Safe Sign/Verify requires q > 512 bits
|
|
794
800
|
shared = dsa.DSASharedPublicKey.NewShared(2048, 520)
|
|
@@ -815,8 +821,8 @@ assert pub.RawVerify(msg, sig)
|
|
|
815
821
|
- `1 ≤ message < q`
|
|
816
822
|
- signatures: `(s1, s2)` with `2 ≤ s1, s2 < q`
|
|
817
823
|
- errors:
|
|
818
|
-
- invalid ranges → `InputError`
|
|
819
|
-
- inconsistent parameters → `CryptoError`
|
|
824
|
+
- invalid ranges → `base.InputError`
|
|
825
|
+
- inconsistent parameters → `key.CryptoError`
|
|
820
826
|
|
|
821
827
|
##### Security notes
|
|
822
828
|
|
|
@@ -832,23 +838,23 @@ assert (p - 1) % q == 0
|
|
|
832
838
|
```
|
|
833
839
|
|
|
834
840
|
Used internally by `DSASharedPublicKey.NewShared()`.
|
|
835
|
-
Search breadth and retry caps are bounded; repeated failures raise `CryptoError`.
|
|
841
|
+
Search breadth and retry caps are bounded; repeated failures raise `key.CryptoError`.
|
|
836
842
|
|
|
837
843
|
#### Public Bidding
|
|
838
844
|
|
|
839
845
|
This is a way of bidding on some commitment (the `secret`) that can be cryptographically proved later to not have been changed. To do that the secret is combined with 2 nonces (random values, `n1` & `n2`) and a hash of it is taken (`H=SHA-512(n1||n2||secret)`). The hash `H` and one nonce `n1` are public and divulged. The other nonce `n2` and the `secret` are kept private and will be used to show `secret` was not changed since the beginning of the process. The nonces guarantee the `secret` cannot be brute-forced or changed after-the-fact. The whole process is as strong as SHA-512 collisions.
|
|
840
846
|
|
|
841
847
|
```py
|
|
842
|
-
from transcrypto import
|
|
848
|
+
from transcrypto.core import bid
|
|
843
849
|
|
|
844
850
|
# Generate the private and public bids
|
|
845
|
-
bid_priv =
|
|
846
|
-
bid_pub =
|
|
851
|
+
bid_priv = bid.PrivateBid512.New(secret) # this one you keep private
|
|
852
|
+
bid_pub = bid.PublicBid512.Copy(bid_priv) # this one you publish
|
|
847
853
|
|
|
848
854
|
# Checking that a bid is genuine requires the public bid and knowing the nonce and the secret:
|
|
849
855
|
print(bid_pub.VerifyBid(private_key, secret_bid)) # these come from a divulged private bid
|
|
850
856
|
# of course, you want to also make sure the provided private data matches your version of it, e.g.:
|
|
851
|
-
bid_pub_expected =
|
|
857
|
+
bid_pub_expected = bid.PublicBid512.Copy(bid_priv)
|
|
852
858
|
print(bid_pub == bid_pub_expected)
|
|
853
859
|
```
|
|
854
860
|
|
|
@@ -859,7 +865,7 @@ print(bid_pub == bid_pub_expected)
|
|
|
859
865
|
This is the information-theoretic SSS but with no authentication or binding between share and secret. Malicious share injection is possible! Add MAC or digital signature in hostile settings. Use at least 128-bit modulus for non-toy deployments; `MakeDataShares()` requires > 256 bits.
|
|
860
866
|
|
|
861
867
|
```py
|
|
862
|
-
from transcrypto import sss
|
|
868
|
+
from transcrypto.core import sss
|
|
863
869
|
|
|
864
870
|
# Generate parameters: at least 3 of 5 shares needed,
|
|
865
871
|
# coefficients & modulus are 264-bit primes (> 256 bits required for MakeDataShares)
|
|
@@ -905,7 +911,7 @@ recovered = pub.RawRecoverSecret(subset)
|
|
|
905
911
|
assert recovered == secret
|
|
906
912
|
```
|
|
907
913
|
|
|
908
|
-
If you supply fewer than minimum shares you get a `CryptoError`, unless you explicitly override:
|
|
914
|
+
If you supply fewer than minimum shares you get a `key.CryptoError`, unless you explicitly override:
|
|
909
915
|
|
|
910
916
|
```py
|
|
911
917
|
try:
|
|
@@ -932,7 +938,7 @@ print(priv.RawVerifyShare(secret, tampered)) # ▶ False
|
|
|
932
938
|
|
|
933
939
|
### Setup
|
|
934
940
|
|
|
935
|
-
If you want to develop for this project, first install python 3.
|
|
941
|
+
If you want to develop for this project, first install python 3.12 and [Poetry](https://python-poetry.org/docs/cli/), but to get the versions you will need, we suggest you do it like this (*Linux*):
|
|
936
942
|
|
|
937
943
|
```sh
|
|
938
944
|
sudo apt-get update
|
|
@@ -941,10 +947,10 @@ sudo apt-get install git python3 python3-pip pipx python3-dev python3-venv build
|
|
|
941
947
|
|
|
942
948
|
sudo add-apt-repository ppa:deadsnakes/ppa # install arbitrary python version
|
|
943
949
|
sudo apt-get update
|
|
944
|
-
sudo apt-get install python3.
|
|
950
|
+
sudo apt-get install python3.12
|
|
945
951
|
|
|
946
952
|
sudo apt-get remove python3-poetry
|
|
947
|
-
python3.
|
|
953
|
+
python3.12 -m pipx ensurepath
|
|
948
954
|
# re-open terminal
|
|
949
955
|
pipx install poetry
|
|
950
956
|
poetry --version # should be >=2.1
|
|
@@ -960,11 +966,11 @@ brew update
|
|
|
960
966
|
brew upgrade
|
|
961
967
|
brew cleanup -s
|
|
962
968
|
|
|
963
|
-
brew install git python@3.
|
|
969
|
+
brew install git python@3.12 # install arbitrary python version
|
|
964
970
|
|
|
965
971
|
brew uninstall poetry
|
|
966
|
-
python3.
|
|
967
|
-
python3.
|
|
972
|
+
python3.12 -m pip install --user pipx
|
|
973
|
+
python3.12 -m pipx ensurepath
|
|
968
974
|
# re-open terminal
|
|
969
975
|
pipx install poetry
|
|
970
976
|
poetry --version # should be >=2.1
|
|
@@ -979,7 +985,7 @@ Now install the project:
|
|
|
979
985
|
git clone https://github.com/balparda/transcrypto.git transcrypto
|
|
980
986
|
cd transcrypto
|
|
981
987
|
|
|
982
|
-
poetry env use python3.
|
|
988
|
+
poetry env use python3.12 # creates the venv
|
|
983
989
|
poetry sync # sync env to project's poetry.lock file
|
|
984
990
|
poetry env info # no-op: just to check
|
|
985
991
|
|
|
@@ -1017,7 +1023,7 @@ If you manually added a dependency to `pyproject.toml` you should ***very carefu
|
|
|
1017
1023
|
|
|
1018
1024
|
```sh
|
|
1019
1025
|
rm -rf .venv .poetry poetry.lock
|
|
1020
|
-
poetry env use python3.
|
|
1026
|
+
poetry env use python3.12
|
|
1021
1027
|
poetry install
|
|
1022
1028
|
```
|
|
1023
1029
|
|
|
@@ -1026,7 +1032,7 @@ Remember to check your diffs before submitting (especially `poetry.lock`) to avo
|
|
|
1026
1032
|
When dependencies change, always regenerate `requirements.txt` by running:
|
|
1027
1033
|
|
|
1028
1034
|
```sh
|
|
1029
|
-
poetry export --format requirements.txt --without-hashes --output requirements.txt
|
|
1035
|
+
make req # or: poetry export --format requirements.txt --without-hashes --output requirements.txt
|
|
1030
1036
|
```
|
|
1031
1037
|
|
|
1032
1038
|
### Creating a New Version
|
|
@@ -1051,11 +1057,7 @@ poetry build
|
|
|
1051
1057
|
poetry publish
|
|
1052
1058
|
```
|
|
1053
1059
|
|
|
1054
|
-
If you changed the CLI interface at all, in any tool, run
|
|
1055
|
-
|
|
1056
|
-
```sh
|
|
1057
|
-
make docs
|
|
1058
|
-
```
|
|
1060
|
+
If you changed the CLI interface at all, in any tool, run `make docs` or even better `make ci`.
|
|
1059
1061
|
|
|
1060
1062
|
You can find the 10 top slowest tests by running:
|
|
1061
1063
|
|