yace-codec 0.1.0__tar.gz → 0.2.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.
- {yace_codec-0.1.0 → yace_codec-0.2.0}/CHANGELOG.md +28 -0
- {yace_codec-0.1.0/src/yace_codec.egg-info → yace_codec-0.2.0}/PKG-INFO +176 -30
- {yace_codec-0.1.0 → yace_codec-0.2.0}/README.md +172 -27
- {yace_codec-0.1.0 → yace_codec-0.2.0}/pyproject.toml +4 -1
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/__init__.py +13 -2
- yace_codec-0.2.0/src/yace_codec/_text.py +39 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/cli.py +141 -4
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/codec.py +98 -24
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/errors.py +10 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/keystore.py +144 -16
- yace_codec-0.2.0/src/yace_codec/sealed.py +228 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0/src/yace_codec.egg-info}/PKG-INFO +176 -30
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec.egg-info/SOURCES.txt +2 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/LICENSE +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/MANIFEST.in +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/examples/bytes_and_unicode.py +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/examples/quickstart.py +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/examples/share_tables.py +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/setup.cfg +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/__main__.py +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/py.typed +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec/tables.py +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec.egg-info/dependency_links.txt +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec.egg-info/entry_points.txt +0 -0
- {yace_codec-0.1.0 → yace_codec-0.2.0}/src/yace_codec.egg-info/top_level.txt +0 -0
|
@@ -4,6 +4,34 @@ All notable changes to this project are documented in this file. The format foll
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.2.0] - 2026-10-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Encryption: `encrypt()` and `decrypt()` (also `Codec.encrypt()` /
|
|
12
|
+
`Codec.decrypt()`) turn text or bytes into `yace2_` tokens and back. Tokens are
|
|
13
|
+
authenticated (HMAC-SHA256, encrypt-then-MAC), use per-token keys derived with
|
|
14
|
+
HKDF-SHA256 from a random salt, and are padded to a Padmé length. They reveal neither
|
|
15
|
+
the key name, the creation time, the exact length, nor the data type. Everything
|
|
16
|
+
comes from Python's standard library.
|
|
17
|
+
- Secret keys in the key store (`key:<name>` entries): `ensure_key()`, `get_key()`,
|
|
18
|
+
`find_key()`, `key_names()`, `export_key()`, and `import_keys(..., name=...)`.
|
|
19
|
+
`decrypt()` tries every stored key.
|
|
20
|
+
- Password mode: `encrypt(data, password=...)` stretches the password with scrypt and
|
|
21
|
+
does not touch the key store.
|
|
22
|
+
- `decrypt(..., max_age=SECONDS)` rejects old tokens with `TokenExpiredError`.
|
|
23
|
+
- `DecryptionError` and `TokenExpiredError` (both subclasses of `DecodingError`), and
|
|
24
|
+
the `DEFAULT_KEY` constant.
|
|
25
|
+
- `yace-codec encrypt`, `yace-codec decrypt` (with `--password`, `--key`, and
|
|
26
|
+
`--max-age`), and `yace-codec keys list|export|import`. `keys import` needs
|
|
27
|
+
`--name NAME` or `--all-keys`. `YACE_CODEC_PASSWORD` can supply the password.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- `decode()` on a `yace2_` token points to `decrypt()`, and `decrypt()` on an
|
|
32
|
+
encoded token points to `decode()`.
|
|
33
|
+
- `import_tables()` refuses secret key entries; import them with `import_keys()`.
|
|
34
|
+
|
|
7
35
|
## [0.1.0] - 2026-10-14
|
|
8
36
|
|
|
9
37
|
First release on PyPI, as `yace-codec` (import name `yace_codec`).
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yace-codec
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: YACE (Yet Another Custom Encoder):
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: YACE (Yet Another Custom Encoder): reversible text encoding with random substitution tables, plus authenticated encryption into yace2 tokens, with no dependencies.
|
|
5
5
|
Author: Mudith Daga
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/thisismudith/YACE
|
|
8
8
|
Project-URL: Source, https://github.com/thisismudith/YACE
|
|
9
9
|
Project-URL: Issues, https://github.com/thisismudith/YACE/issues
|
|
10
10
|
Project-URL: Changelog, https://github.com/thisismudith/YACE/blob/main/CHANGELOG.md
|
|
11
|
-
Keywords: yace,encoder,decoder,encoding,codec,substitution,obfuscation,text,cli
|
|
11
|
+
Keywords: yace,encoder,decoder,encoding,codec,substitution,obfuscation,encryption,decryption,text,cli
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.14
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Topic :: Security :: Cryptography
|
|
25
26
|
Classifier: Topic :: Text Processing
|
|
26
27
|
Classifier: Topic :: Utilities
|
|
27
28
|
Classifier: Typing :: Typed
|
|
@@ -32,16 +33,21 @@ Dynamic: license-file
|
|
|
32
33
|
|
|
33
34
|
# YACE
|
|
34
35
|
|
|
35
|
-
**Reversible text encoding with random substitution tables,
|
|
36
|
+
**Reversible text encoding with random substitution tables, and authenticated encryption into `yace2` tokens.**
|
|
36
37
|
|
|
37
38
|
[](https://pypi.org/project/yace-codec/)
|
|
38
39
|
[](https://pypi.org/project/yace-codec/)
|
|
39
40
|
[](https://github.com/thisismudith/YACE/blob/main/LICENSE)
|
|
40
41
|
|
|
41
|
-
YACE (Yet Another Custom Encoder)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
YACE (Yet Another Custom Encoder) has two modes. Both come as a Python API and as
|
|
43
|
+
the `yace-codec` command, with no third-party dependencies:
|
|
44
|
+
|
|
45
|
+
- **Encoding** (`encode`/`decode`) turns text into tokens of random-looking letters
|
|
46
|
+
and digits with YACE's substitution tables. It hides text from casual reading,
|
|
47
|
+
but it is not encryption.
|
|
48
|
+
- **Encryption** (`encrypt`/`decrypt`) turns any text or file into a `yace2_` token
|
|
49
|
+
that reveals nothing but its approximate size, and that only the matching key or
|
|
50
|
+
password can open.
|
|
45
51
|
|
|
46
52
|
| | |
|
|
47
53
|
|---|---|
|
|
@@ -53,11 +59,12 @@ This project is not affiliated with the `yace` package on PyPI ("Yet Another Cod
|
|
|
53
59
|
Emitter") or its `yace-*` companions, nor with YACE, the Prometheus CloudWatch
|
|
54
60
|
exporter.
|
|
55
61
|
|
|
56
|
-
> **
|
|
57
|
-
>
|
|
58
|
-
>
|
|
59
|
-
>
|
|
60
|
-
>
|
|
62
|
+
> **Use `encrypt()` for secrets, never `encode()`.** Encoding is a substitution code
|
|
63
|
+
> with extra noise: frequency analysis, or a couple of tokens whose text an attacker
|
|
64
|
+
> knows, can break it. Encryption is built from standard primitives (scrypt,
|
|
65
|
+
> HKDF-SHA256, and HMAC-SHA256 from Python's standard library), but this project
|
|
66
|
+
> assembled them and nobody has audited the result. For high-stakes secrets, use an
|
|
67
|
+
> audited library such as [`cryptography`](https://cryptography.io/).
|
|
61
68
|
|
|
62
69
|
## Contents
|
|
63
70
|
|
|
@@ -65,6 +72,7 @@ exporter.
|
|
|
65
72
|
- [How the encoder works](#how-the-encoder-works)
|
|
66
73
|
- [Installation](#installation)
|
|
67
74
|
- [Quick start](#quick-start)
|
|
75
|
+
- [Encryption (yace2 tokens)](#encryption-yace2-tokens)
|
|
68
76
|
- [Command line](#command-line)
|
|
69
77
|
- [API reference](#api-reference)
|
|
70
78
|
- [The key store](#the-key-store)
|
|
@@ -79,7 +87,9 @@ exporter.
|
|
|
79
87
|
|
|
80
88
|
Common encodings such as base64 or hex are public mappings, so anyone can reverse
|
|
81
89
|
them. Decoding a YACE token needs the tables in your key store, and encoding the same
|
|
82
|
-
text twice usually gives two different tokens.
|
|
90
|
+
text twice usually gives two different tokens. Encrypted `yace2` tokens go further:
|
|
91
|
+
without the key or password they cannot be read, linked to their key, or changed
|
|
92
|
+
without detection.
|
|
83
93
|
|
|
84
94
|
You can use YACE to:
|
|
85
95
|
|
|
@@ -87,16 +97,18 @@ You can use YACE to:
|
|
|
87
97
|
game content, test fixtures, or messages for people you share tables with);
|
|
88
98
|
- produce plain ASCII output: a short header line followed by a body of letters and
|
|
89
99
|
digits only;
|
|
100
|
+
- encrypt messages and files for people who share a key or a password with you;
|
|
90
101
|
- learn how substitution, homophones, nulls, and transposition combine in classical
|
|
91
102
|
ciphers.
|
|
92
103
|
|
|
93
|
-
| | base64 / hex | YACE |
|
|
94
|
-
|
|
95
|
-
| Who can
|
|
96
|
-
| Same input, same output | yes | usually not | no |
|
|
97
|
-
| Resists cryptanalysis | no | no | yes |
|
|
98
|
-
| Detects tampering | no | no (only some corruption) | yes |
|
|
99
|
-
|
|
|
104
|
+
| | base64 / hex | YACE `encode` | YACE `encrypt` | Fernet (`cryptography`) |
|
|
105
|
+
|---|---|---|---|---|
|
|
106
|
+
| Who can read it | anyone | holders of the tables, or an attacker with enough samples | holders of the key or password | holders of the key |
|
|
107
|
+
| Same input, same output | yes | usually not | no | no |
|
|
108
|
+
| Resists cryptanalysis | no | no | designed to (standard primitives, not audited) | yes (audited) |
|
|
109
|
+
| Detects tampering | no | no (only some corruption) | yes | yes |
|
|
110
|
+
| Hides the length | no | no | roughly (Padmé padding) | no |
|
|
111
|
+
| Output size | 1.33x / 2x | about 1.25 x length<sup>iterations</sup> | about 1.4x plus about 95 characters | about 1.33x plus a fixed overhead |
|
|
100
112
|
|
|
101
113
|
## How the encoder works
|
|
102
114
|
|
|
@@ -366,6 +378,74 @@ assert make_token() == make_token()
|
|
|
366
378
|
More runnable scripts are in
|
|
367
379
|
[`examples/`](https://github.com/thisismudith/YACE/tree/main/examples).
|
|
368
380
|
|
|
381
|
+
## Encryption (yace2 tokens)
|
|
382
|
+
|
|
383
|
+
`encrypt()` turns any text or bytes into a `yace2_` token, and `decrypt()` turns it
|
|
384
|
+
back:
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
token = yace.encrypt("Meet at the north gate at 06:00.")
|
|
388
|
+
print(token)
|
|
389
|
+
print(yace.decrypt(token))
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
```text
|
|
393
|
+
yace2_5a5Hfi0rCH1GDypoUqaZ0C2doMT8ZgvPx3TdjOC4G7Zi4afClySrDuTJDBs38qfnIMITLJWJXXtwE3kzFZl77cPY1vmbl740V6h2Dk7H8DdQAP8CUd2AgCPsrIFJKI8j02wB2jawa0
|
|
394
|
+
Meet at the north gate at 06:00.
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
By default the secret key named `default` in the [key store](#the-key-store) is used,
|
|
398
|
+
and it is generated on first use. `decrypt()` tries every key in the key store, so a
|
|
399
|
+
token never names its key. Choose another key with `yace.encrypt(data, "team")`.
|
|
400
|
+
|
|
401
|
+
With a password instead of a stored key, no key store is involved:
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
token = yace.encrypt("for your eyes only", password="correct horse battery staple")
|
|
405
|
+
print(yace.decrypt(token, password="correct horse battery staple"))
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
```text
|
|
409
|
+
for your eyes only
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
`decrypt()` returns `str` for text and `bytes` for bytes. It also offers:
|
|
413
|
+
|
|
414
|
+
- `max_age`: `yace.decrypt(token, max_age=3600)` rejects tokens made more than an hour
|
|
415
|
+
ago with `TokenExpiredError`.
|
|
416
|
+
- Tamper detection: a wrong key or password, or any change to the token, raises
|
|
417
|
+
`DecryptionError`.
|
|
418
|
+
- Key sharing: export a key with `KeyStore.export_key("team")` (CLI:
|
|
419
|
+
`yace-codec keys export team`) and import it with
|
|
420
|
+
`import_keys(data, name="team")` (CLI: `yace-codec keys import FILE --name team`).
|
|
421
|
+
Anyone with the key can read and create tokens for it.
|
|
422
|
+
|
|
423
|
+
### What a yace2 token reveals
|
|
424
|
+
|
|
425
|
+
Only that it is a yace2 token, and roughly how long the data is: the size is padded
|
|
426
|
+
to a Padmé length, which leaks at most a few bits. The key name, the exact length,
|
|
427
|
+
the creation time, and whether the data was text or bytes are all encrypted. The same
|
|
428
|
+
data gives a different token every time.
|
|
429
|
+
|
|
430
|
+
### How encryption works
|
|
431
|
+
|
|
432
|
+
1. A random 16-byte salt is drawn for every token.
|
|
433
|
+
2. The secret is a 32-byte key from the key store, or the password stretched with
|
|
434
|
+
scrypt (N = 2<sup>17</sup>, r = 8, p = 1) and the salt.
|
|
435
|
+
3. HKDF-SHA256 (RFC 5869) derives two keys from the secret and the salt: one to
|
|
436
|
+
encrypt and one to authenticate. Every token gets its own pair.
|
|
437
|
+
4. A frame holds the data type, the creation time, the length, the data, and zero
|
|
438
|
+
padding up to a Padmé length.
|
|
439
|
+
5. The frame is XORed with a keystream of HMAC-SHA256 blocks in counter mode.
|
|
440
|
+
6. An HMAC-SHA256 tag covers the prefix, the salt, and the ciphertext. Decryption
|
|
441
|
+
checks it first, with a constant-time comparison, so wrong keys and changed tokens
|
|
442
|
+
are rejected before anything is decrypted.
|
|
443
|
+
7. The salt, ciphertext, and tag are written as letters and digits: every 8 bytes
|
|
444
|
+
become 11 characters ("YACE62").
|
|
445
|
+
|
|
446
|
+
Every building block comes from Python's standard library (`hashlib`, `hmac`, and
|
|
447
|
+
`secrets`).
|
|
448
|
+
|
|
369
449
|
## Command line
|
|
370
450
|
|
|
371
451
|
Installing the package adds the `yace-codec` command (`python -m yace_codec` works
|
|
@@ -398,6 +478,34 @@ $ yace-codec encode --binary photo.jpg -o photo.yace
|
|
|
398
478
|
$ yace-codec decode --binary photo.yace -o photo-copy.jpg
|
|
399
479
|
```
|
|
400
480
|
|
|
481
|
+
Encrypt and decrypt (any file works, byte for byte):
|
|
482
|
+
|
|
483
|
+
```console
|
|
484
|
+
$ yace-codec encrypt --text "Meet at the north gate at 06:00."
|
|
485
|
+
yace2_JHVUvAKzCjE56fROwZRSVk3L7Y4zpVFHXJlRuiF4Mb2lK0IjhXOy7fv6tTg9sDdyRxIYUG5RC0WS7CNhi073vj6AAcx3VS651sTFbvYWVvSEi669m6KEXB52AKvdzmQ7RM9L3sAiO5
|
|
486
|
+
|
|
487
|
+
$ yace-codec encrypt secret.pdf -o secret.yace --key team
|
|
488
|
+
$ yace-codec decrypt secret.yace -o secret-copy.pdf
|
|
489
|
+
|
|
490
|
+
$ yace-codec encrypt --password notes.txt -o notes.yace
|
|
491
|
+
Password:
|
|
492
|
+
Repeat password:
|
|
493
|
+
$ yace-codec decrypt --password notes.yace
|
|
494
|
+
Password:
|
|
495
|
+
Shopping list:
|
|
496
|
+
- milk
|
|
497
|
+
- 12 eggs
|
|
498
|
+
|
|
499
|
+
$ yace-codec keys list
|
|
500
|
+
default
|
|
501
|
+
team
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
`--password` asks for the password (twice when encrypting). Scripts can set
|
|
505
|
+
`YACE_CODEC_PASSWORD` instead, but other programs running as you may be able to
|
|
506
|
+
read environment variables. `keys import` needs `--name NAME` (or `--all-keys`), for
|
|
507
|
+
the same reason as `tables import` below.
|
|
508
|
+
|
|
401
509
|
Inspect and share tables:
|
|
402
510
|
|
|
403
511
|
```console
|
|
@@ -435,6 +543,11 @@ yace-codec: error: groceries.json holds tables for: groceries. Name the family y
|
|
|
435
543
|
| `tables list` | `-k/--keystore PATH` |
|
|
436
544
|
| `tables export NAME` | `-o/--output PATH`, `-k/--keystore PATH` |
|
|
437
545
|
| `tables import FILE` | `--family NAME` or `--all-families` (one is required), `--overwrite`, `-k/--keystore PATH` (use `-` as `FILE` for standard input) |
|
|
546
|
+
| `encrypt [INPUT]` | `-t/--text TEXT`, `-o/--output PATH`, `--key NAME` (default `default`) or `-p/--password`, `-k/--keystore PATH` |
|
|
547
|
+
| `decrypt [INPUT]` | `-t/--text TOKEN`, `-o/--output PATH`, `-p/--password`, `--max-age SECONDS`, `-k/--keystore PATH` |
|
|
548
|
+
| `keys list` | `-k/--keystore PATH` |
|
|
549
|
+
| `keys export NAME` | `-o/--output PATH`, `-k/--keystore PATH` |
|
|
550
|
+
| `keys import FILE` | `--name NAME` or `--all-keys` (one is required), `--overwrite`, `-k/--keystore PATH` |
|
|
438
551
|
|
|
439
552
|
Text mode reads and writes UTF-8 and keeps line endings unchanged, so `decode`
|
|
440
553
|
returns exactly the bytes that were encoded. Token and JSON files saved by Windows
|
|
@@ -453,6 +566,8 @@ Everything below is importable from `yace_codec`.
|
|
|
453
566
|
| `decode(token, *, keystore=None) -> str` | Decode a token: a `str`, or `bytes` in UTF-8 (UTF-16 and UTF-32 need a byte order mark). |
|
|
454
567
|
| `encode_bytes(data, name="default", *, length=2, iterations=3, keystore=None) -> str` | Encode any bytes (base64 first). |
|
|
455
568
|
| `decode_bytes(token, *, keystore=None) -> bytes` | Decode a token made by `encode_bytes`. |
|
|
569
|
+
| `encrypt(data, key=None, *, password=None, keystore=None) -> str` | Encrypt text or bytes into a `yace2_` token, with a stored key (default `"default"`) or a password. |
|
|
570
|
+
| `decrypt(token, *, password=None, max_age=None, keystore=None) -> str \| bytes` | Decrypt a `yace2_` token; returns `str` or `bytes`, as encrypted. |
|
|
456
571
|
| `parse_header(token) -> Header` | Read a token's header without decoding it. |
|
|
457
572
|
| `default_keystore_path() -> pathlib.Path` | Where the default key store lives (an absolute path). |
|
|
458
573
|
|
|
@@ -461,9 +576,10 @@ default key store.
|
|
|
461
576
|
|
|
462
577
|
### `Codec(keystore=None, *, rng=None)`
|
|
463
578
|
|
|
464
|
-
Encodes and
|
|
465
|
-
`
|
|
466
|
-
`keystore` argument), and a
|
|
579
|
+
Encodes, decodes, encrypts, and decrypts with one key store. It has the same
|
|
580
|
+
`encode`, `decode`, `encode_bytes`, `decode_bytes`, `encrypt`, and `decrypt`
|
|
581
|
+
methods as the functions above (without the `keystore` argument), and a
|
|
582
|
+
`keystore` attribute. `rng` replaces the system random
|
|
467
583
|
source with a `random.Random`, for reproducible tests only.
|
|
468
584
|
|
|
469
585
|
### `KeyStore(path)`
|
|
@@ -483,6 +599,11 @@ the `KeyStore` is created. The file is created on the first write.
|
|
|
483
599
|
| `families() -> list[Family]` | Tables grouped as `Family(name, length, layers)`. |
|
|
484
600
|
| `export_family(family) -> dict` | All tables of a family, as JSON-ready data. |
|
|
485
601
|
| `import_tables(tables, *, overwrite=False, family=None) -> ImportResult` | Add tables; with `family`, only that family's. Returns `ImportResult(added, unchanged, replaced)`. |
|
|
602
|
+
| `ensure_key(name, *, rng=None) -> bytes` | Return a secret key for `encrypt`, generating it if missing. |
|
|
603
|
+
| `get_key(name) -> bytes`, `find_key(name) -> bytes \| None` | Return a secret key. |
|
|
604
|
+
| `key_names() -> list[str]` | Names of the stored secret keys. |
|
|
605
|
+
| `export_key(name) -> dict` | A secret key as JSON-ready data. |
|
|
606
|
+
| `import_keys(keys, *, overwrite=False, name=None) -> ImportResult` | Add secret keys; with `name`, only that key. |
|
|
486
607
|
| `reload()` | Drop cached data and re-read the file on next use. |
|
|
487
608
|
| `name in store`, `len(store)` | Membership test and table count. |
|
|
488
609
|
|
|
@@ -507,13 +628,15 @@ YaceError
|
|
|
507
628
|
├── EncodingError invalid settings (also a ValueError)
|
|
508
629
|
│ └── UnsupportedCharacterError .characters, .table_name
|
|
509
630
|
├── DecodingError malformed or corrupted token, or wrong tables (also a ValueError)
|
|
631
|
+
│ └── DecryptionError a yace2 token cannot be opened: wrong key or password, or changed
|
|
632
|
+
│ └── TokenExpiredError the token is older than max_age
|
|
510
633
|
├── TableNotFoundError a needed table is missing; .table_name (also a LookupError)
|
|
511
634
|
└── KeyStoreError unreadable, invalid, or unwritable key store
|
|
512
635
|
```
|
|
513
636
|
|
|
514
637
|
### Constants
|
|
515
638
|
|
|
516
|
-
`DEFAULT_FAMILY` (`"default"`), `DEFAULT_LENGTH` (2), `DEFAULT_ITERATIONS` (3),
|
|
639
|
+
`DEFAULT_FAMILY` (`"default"`), `DEFAULT_KEY` (`"default"`), `DEFAULT_LENGTH` (2), `DEFAULT_ITERATIONS` (3),
|
|
517
640
|
`MIN_LENGTH` (2), `MAX_LENGTH` (62), `MAX_ITERATIONS` (32), `MAX_ENCODED_CHARS`
|
|
518
641
|
(2<sup>25</sup>), `SUPPORTED_CHARACTERS`, `ENV_VAR` (`"YACE_CODEC_KEYSTORE"`), and
|
|
519
642
|
`__version__`.
|
|
@@ -538,6 +661,9 @@ original scripts' `encodings.json`:
|
|
|
538
661
|
{"team-3:1": {"!": "B1x", "a": "yI4", "...": "...", " ": ["6nM", "..."], "bin": ["EbC", "..."]}}
|
|
539
662
|
```
|
|
540
663
|
|
|
664
|
+
Secret keys for `encrypt` live in the same file, as entries named `key:<name>` that
|
|
665
|
+
hold a base64 `secret` of 32 random bytes.
|
|
666
|
+
|
|
541
667
|
How the key store behaves:
|
|
542
668
|
|
|
543
669
|
- **Existing tables are never changed.** New tables are only added, except through
|
|
@@ -557,8 +683,8 @@ How the key store behaves:
|
|
|
557
683
|
`KeyStoreError` and the file is not overwritten. Entries the package does not
|
|
558
684
|
understand are kept.
|
|
559
685
|
|
|
560
|
-
**Back up your key store.** If you lose it, every token encoded with its tables
|
|
561
|
-
becomes
|
|
686
|
+
**Back up your key store.** If you lose it, every token encoded with its tables or
|
|
687
|
+
encrypted with its keys becomes unreadable. Anyone who has it can read those tokens.
|
|
562
688
|
|
|
563
689
|
## Limits and edge cases
|
|
564
690
|
|
|
@@ -577,6 +703,8 @@ becomes undecodable. Anyone who has it can decode your tokens.
|
|
|
577
703
|
token character, so tokens near the size limit need 1–2 GB. YACE is meant for
|
|
578
704
|
strings and small to medium files, not streams.
|
|
579
705
|
- **Empty input.** Encoding `""` gives a header-only token, which decodes to `""`.
|
|
706
|
+
- **Encryption size.** `encrypt()` holds the data and the token in memory. Tokens
|
|
707
|
+
are about 1.4 times the size of the data, plus about 95 characters.
|
|
580
708
|
- **Whitespace.** Decoding ignores whitespace in a token, so wrapped lines, CRLF line
|
|
581
709
|
endings, and leading blank lines are fine.
|
|
582
710
|
- **Corruption.** Decoding raises `DecodingError` when a chunk is not a valid code,
|
|
@@ -595,7 +723,9 @@ becomes undecodable. Anyone who has it can decode your tokens.
|
|
|
595
723
|
|
|
596
724
|
## Security
|
|
597
725
|
|
|
598
|
-
|
|
726
|
+
### Encoding (`encode`)
|
|
727
|
+
|
|
728
|
+
- YACE encoding is a randomised substitution code with a keyless, public chunk shuffle.
|
|
599
729
|
Within a family, every character except the space always has the same code. So
|
|
600
730
|
frequency analysis over enough tokens recovers the text, and a couple of tokens
|
|
601
731
|
whose text an attacker knows can be enough to decode other tokens.
|
|
@@ -606,8 +736,24 @@ becomes undecodable. Anyone who has it can decode your tokens.
|
|
|
606
736
|
- The key store is the key. Store it like a credential, share single families with
|
|
607
737
|
`tables export` instead of sharing the whole file, and import only the family you
|
|
608
738
|
expect from files other people send you.
|
|
609
|
-
- Use
|
|
610
|
-
|
|
739
|
+
- Use `encrypt()` whenever confidentiality matters.
|
|
740
|
+
|
|
741
|
+
### Encryption (`encrypt`)
|
|
742
|
+
|
|
743
|
+
- Confidentiality and integrity rest on HMAC-SHA256 as a pseudorandom function (for
|
|
744
|
+
the keystream and the tag), HKDF-SHA256 for per-token keys, and scrypt for
|
|
745
|
+
passwords. These are standard, well-studied constructions, but this project
|
|
746
|
+
assembled them and nobody has audited the result. For high-stakes secrets, use an
|
|
747
|
+
audited library such as [`cryptography`](https://cryptography.io/).
|
|
748
|
+
- A password token is only as strong as its password. scrypt makes each guess slow,
|
|
749
|
+
but a short or common password can still be found. Stored keys are 32 random bytes.
|
|
750
|
+
- The key store holds the keys: anyone who copies it can open every token made with
|
|
751
|
+
its keys. Password tokens do not depend on the key store.
|
|
752
|
+
- Tokens hide the key name, the creation time, and the exact length, but not roughly
|
|
753
|
+
how long the data is, or that YACE made them.
|
|
754
|
+
- There is no forward secrecy (a leaked key opens all earlier tokens made with it),
|
|
755
|
+
and pure Python gives no protection against attackers who can measure timing or
|
|
756
|
+
memory on your machine.
|
|
611
757
|
|
|
612
758
|
To report a bug, including a security issue in the code, please
|
|
613
759
|
[open an issue](https://github.com/thisismudith/YACE/issues).
|