jucrypt 0.2.3__tar.gz → 0.3.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.
jucrypt-0.3.0/PKG-INFO ADDED
@@ -0,0 +1,389 @@
1
+ Metadata-Version: 2.4
2
+ Name: jucrypt
3
+ Version: 0.3.0
4
+ Summary: A Fully Parameterised, Story-Key Driven Experimental SPN Cipher
5
+ Author-email: "I. Nabil" <w3nabil@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/w3nabil/jucrypt
8
+ Project-URL: Repository, https://github.com/w3nabil/jucrypt
9
+ Project-URL: Issues, https://github.com/w3nabil/jucrypt/issues
10
+ Keywords: cryptography,symmetric encryption,educational crypto,experimental cipher,story-based key derivation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Topic :: Security :: Cryptography
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Operating System :: OS Independent
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Provides-Extra: experiment
24
+ Requires-Dist: numpy>=1.23; extra == "experiment"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # Ju's Story (STORY)
30
+
31
+ > **Your story is your key.**
32
+ > A story-key driven Substitution-Permutation Network cipher.
33
+
34
+ > **Pre-review research cipher.** No external cryptanalysis or formal peer review has been conducted yet. We are actively seeking feedback from the research community. See [Current Limitations and Open Issues](#current-limitations-and-open-issues) before use.
35
+
36
+ [![Python](https://img.shields.io/badge/Python-3.9%2B-blue?logo=python)](https://www.python.org/)
37
+ [![License](https://img.shields.io/badge/Apace-2.0-green)](LICENSE)
38
+ [![PyPI](https://img.shields.io/badge/PyPI-jucrypt-orange?logo=pypi)](https://pypi.org/project/jucrypt/)
39
+
40
+ STORY is an experimental symmetric block cipher in which the secret key is a natural-language narrative — a sentence, a paragraph, or any Unicode text — rather than a raw byte string. It operates on 128-bit blocks using a Substitution-Permutation Network (SPN), and provides authenticated encryption through CTR mode combined with HMAC-SHA-256 in an Encrypt-then-MAC construction.
41
+
42
+ The project is published as `jucrypt` on PyPI. The cipher, key derivation pipeline, and test suite are entirely open. We are conducting empirical security testing and would genuinely appreciate researchers using, testing, and critiquing the design.
43
+
44
+ ---
45
+
46
+ ## Contents
47
+
48
+ - [Design Overview](#design-overview)
49
+ - [Key Derivation Pipeline](#key-derivation-pipeline)
50
+ - [Installation](#installation)
51
+ - [Python API](#python-api)
52
+ - [Command-Line Interface](#command-line-interface)
53
+ - [Empirical Test Results](#empirical-test-results)
54
+ - [Current Limitations and Open Issues](#current-limitations-and-open-issues)
55
+ - [Custom S-box Pool](#custom-s-box-pool)
56
+ - [For Researchers](#for-researchers)
57
+ - [Citation](#citation)
58
+
59
+ ---
60
+
61
+ ## Design Overview
62
+
63
+ | Property | Value |
64
+ |---|---|
65
+ | Block size | 128 bits (16 bytes) |
66
+ | Key material | Any Unicode narrative string |
67
+ | Derived key width | 256 bits (enc\_key) + 256 bits (mac\_key) |
68
+ | Round structure | SPN: ARK → SubBytes → Permute → MDS Mix |
69
+ | Round count | 8–15 per message (base 8–12 key-derived, offset 0–3 from round salt) |
70
+ | S-box | 8-bit bijection, key-selected from a pre-validated pool |
71
+ | Diffusion layer | 16×16 Cauchy MDS matrix over GF(2⁸), branch number = 17 |
72
+ | Permutation | 16-byte key-derived permutation (Fisher-Yates, rejection sampling) |
73
+ | Mode | CTR (counter mode) |
74
+ | Authentication | HMAC-SHA-256 over nonce ‖ round\_salt ‖ ciphertext |
75
+ | Construction | Encrypt-then-MAC (EtM) → IND-CCA2 |
76
+ | Implementation | Pure Python (`STORY`) + optional C extension (`STORYC`, ~12 MB/s) |
77
+
78
+ The cipher is defined entirely in `story.py`. The C-accelerated variant in `storyc.py` and `story_core.c` is a drop-in replacement with an identical API.
79
+
80
+ ---
81
+
82
+ ## Key Derivation Pipeline
83
+
84
+ This is the part of STORY that distinguishes it from conventional designs. The story string is the only secret. All cipher parameters — S-box selection, byte permutation, round count, round keys, whitening key, and MAC key — are derived deterministically from it.
85
+
86
+ ```
87
+ Story string (Unicode)
88
+
89
+ ├─ NFC normalise → UTF-16-LE encode
90
+
91
+ └─ SHAKE-256 → 32-byte IKM
92
+
93
+ ├─ HMAC-SHA256(IKM, "enc||story_v1_master") → enc_key (32 B)
94
+ │ │
95
+ │ ├─ SHAKE-256("story_v1_sbox||" + enc_key) → S-box index (rejection sampling)
96
+ │ ├─ SHAKE-256("story_v1_perm||" + enc_key) → 16-byte permutation
97
+ │ ├─ SHAKE-256("story_v1_rounds||" + enc_key) → base round count [8–12]
98
+ │ ├─ HMAC-SHA256(enc_key, "story_v1_round||r") → rk[r] (one per round)
99
+ │ └─ HMAC-SHA256(enc_key, "story_v1_whitening||") → whitening key
100
+
101
+ └─ HMAC-SHA256(IKM, "mac||story_v1_master") → mac_key (32 B)
102
+ ```
103
+
104
+ The NFC normalisation step ensures that the same story entered on different platforms produces the same key material regardless of the Unicode form used by the operating system. UTF-16-LE encoding maps the normalised text to a canonical byte sequence.
105
+
106
+ The enc\_key and mac\_key are domain-separated HMAC outputs from the same IKM and are therefore computationally independent. Round keys are further domain-separated by round index, so recovering enc\_key from any set of round keys requires inverting HMAC-SHA-256 — a 2²⁵⁶-work preimage problem under standard assumptions.
107
+
108
+ Each encryption also draws a 1-byte random `round_salt`. The salt modifies the actual round count as `actual_rounds = base_rounds + (round_salt[0] % 4)`, so the round count varies from message to message even under the same story key. The salt is transmitted openly alongside the ciphertext and is covered by the authentication tag.
109
+
110
+ ---
111
+
112
+ ## Installation
113
+
114
+ ```bash
115
+ pip install jucrypt
116
+ ```
117
+
118
+ If the C extension is not present, `STORYC` falls back to pure Python automatically with no change to the API or output.
119
+
120
+ ---
121
+
122
+ ## Python API
123
+
124
+ Both `STORY` (pure Python) and `STORYC` (C-accelerated) expose an identical static-method API.
125
+
126
+ ```python
127
+ from jucrypt import STORYC as STORY # C-accelerated, falls back to pure Python
128
+ # from jucrypt import STORY # pure Python only
129
+ ```
130
+
131
+ ### Encrypt
132
+
133
+ ```python
134
+ ciphertext, nonce, tag, round_salt = STORY.encrypt(
135
+ "The quick brown fox jumps over the lazy dog",
136
+ "Once upon a time in a kingdom by the sea, there lived a cryptographer"
137
+ )
138
+ ```
139
+
140
+ `encrypt` accepts `str`, `bytes`, or `bytearray` as plaintext and returns four `bytes` objects. All four must be stored and transmitted together to allow decryption.
141
+
142
+ ### Decrypt to bytes
143
+
144
+ ```python
145
+ plaintext_bytes = STORY.decrypt(
146
+ ciphertext,
147
+ "Once upon a time in a kingdom by the sea, there lived a cryptographer",
148
+ nonce,
149
+ tag,
150
+ round_salt,
151
+ )
152
+ ```
153
+
154
+ If the authentication tag does not verify, `decrypt` raises `ValueError` immediately. No partial plaintext is ever returned on authentication failure.
155
+
156
+ ### Decrypt to string
157
+
158
+ ```python
159
+ plaintext_str = STORY.decrypt_str(
160
+ ciphertext,
161
+ "Once upon a time in a kingdom by the sea, there lived a cryptographer",
162
+ nonce,
163
+ tag,
164
+ round_salt,
165
+ encoding="utf-16-le", # default; change if you encrypted raw bytes
166
+ )
167
+ ```
168
+
169
+ ### Hex string parameters
170
+
171
+ All four ciphertext parameters accept either `bytes` or a hex-encoded `str`:
172
+
173
+ ```python
174
+ pt = STORY.decrypt(
175
+ "a3f1...", # ciphertext as hex string
176
+ story,
177
+ "8c2d4f1a...", # nonce as hex string
178
+ "e9b0...", # tag as hex string
179
+ "03", # round_salt as hex string (1 byte = 2 hex chars)
180
+ )
181
+ ```
182
+
183
+ ### Checking C extension availability
184
+
185
+ ```python
186
+ from jucrypt import STORYC
187
+ print(STORYC.C_AVAILABLE) # True if story_core.so / .pyd was compiled
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Command-Line Interface
193
+
194
+ The CLI is available after installation via `python storyc.py` or `python -m jucrypt.storyc`.
195
+
196
+ ### Encrypt
197
+
198
+ ```bash
199
+ # Inline text
200
+ python storyc.py --enc "Hello world" --story "My secret story"
201
+
202
+ # From .txt files
203
+ python storyc.py --enc message.txt --story story.txt
204
+
205
+ # Save output to file
206
+ python storyc.py --enc message.txt --story story.txt --out cipher.txt
207
+ ```
208
+
209
+ Output format:
210
+
211
+ ```
212
+ CHUNK : $$<ciphertext_hex>$$<nonce_hex>$$<tag_hex>
213
+ SALT : <round_salt_hex>
214
+ ```
215
+
216
+ Both `CHUNK` and `SALT` are required for decryption. They can be stored in the same file.
217
+
218
+ ### Decrypt
219
+
220
+ ```bash
221
+ # Inline chunk and salt
222
+ python storyc.py --dec "$$aa..$$bb..$$cc.." --story "My secret story" --r 03
223
+
224
+ # From files — chunk and salt in the same output file
225
+ python storyc.py --dec cipher.txt --story story.txt --r cipher.txt
226
+
227
+ # Save decrypted output
228
+ python storyc.py --dec cipher.txt --story story.txt --r cipher.txt --out plain.txt
229
+ ```
230
+
231
+ ### Backend selection
232
+
233
+ ```bash
234
+ python storyc.py --enc "text" --story "key" --impl auto # default: C if available
235
+ python storyc.py --enc "text" --story "key" --impl c # force C extension
236
+ python storyc.py --enc "text" --story "key" --impl python # force pure Python
237
+ ```
238
+
239
+ ---
240
+
241
+ ## Empirical Test Results
242
+
243
+ The following results are from our internal test suite run across 2,000 randomly sampled story keys using the C-accelerated implementation. They are **not peer-reviewed**. All numbers are reported as-is, including the ones that fall short of ideal.
244
+
245
+ ### Diffusion and confusion
246
+
247
+ | Metric | Mean | Std | Min | Max | Ideal |
248
+ |---|---|---|---|---|---|
249
+ | Avalanche (%) | 49.992 | 0.395 | 48.68 | 51.30 | 50.0 |
250
+ | Key sensitivity (%) | 49.997 | 0.275 | 48.99 | 50.90 | 50.0 |
251
+ | Shannon entropy (bits/byte) | 7.992 | 0.001 | 7.989 | 7.994 | 8.0 |
252
+
253
+ ### S-box and diffusion layer properties
254
+
255
+ These are constant across all story keys — S-boxes are validated before inclusion in the pool, and the MDS matrix is fixed.
256
+
257
+ | Metric | STORY | AES reference |
258
+ |---|---|---|
259
+ | Differential uniformity (DDT max) | 4 | 4 |
260
+ | Nonlinearity (NL) | 120 | 112 |
261
+ | Max LAT bias | 8 | 16 |
262
+ | Linear correlation ε | 0.0625 | 0.125 |
263
+ | Algebraic degree | 7 | 7 |
264
+ | MDS branch number | 17 | — (AES uses 4×4, BN=5) |
265
+
266
+ ### Statistical uniformity (n = 2,000)
267
+
268
+ | Test | Pass rate | Note |
269
+ |---|---|---|
270
+ | Roundtrip correctness | 100.0% | |
271
+ | Chi-squared byte uniformity | 98.95% | Expected ~99% at α = 0.01 |
272
+ | NIST SP 800-22 monobit | 98.85% | Expected ~99% at α = 0.01 |
273
+
274
+ The ~1% failure rates on chi-squared and NIST monobit are consistent with the expected false-positive rate of a correctly uniform distribution tested at α = 0.01. They are not evidence of cipher weakness.
275
+
276
+ ### IND-CPA / IND-CCA2 (n = 2,000)
277
+
278
+ | Sub-test | Pass rate |
279
+ |---|---|
280
+ | Ciphertext distribution | 98.8% |
281
+ | Semantic security | 100.0% |
282
+ | Key-change indistinguishability | 99.0% |
283
+ | Length leakage | 100.0% |
284
+ | Prefix indistinguishability | 100.0% |
285
+ | **IND-CPA composite** | **97.7%** |
286
+ | MAC tamper rejection | 100.0% (144,000 / 144,000 attempts) |
287
+ | Bit-flip rejection | 100.0% (256,000 / 256,000 attempts) |
288
+ | Truncation rejection | 100.0% (26,000 / 26,000 attempts) |
289
+ | Replay rejection | 100.0% |
290
+ | **IND-CCA2 composite** | **100.0%** |
291
+ | **Overall pass** | **97.7%** |
292
+
293
+ The 97.7% overall rate is driven entirely by the IND-CPA composite. See issue #3 below for the current status of the investigation.
294
+
295
+ ---
296
+
297
+ ## Current Limitations and Open Issues
298
+
299
+ We are disclosing all known issues in full. This is a pre-review cipher and transparency is more useful to the community than silence.
300
+
301
+ **Issue 1 — No formal peer review**
302
+
303
+ The cipher design, key derivation pipeline, and all empirical results above have not undergone external cryptanalysis or formal peer review. STORY should be treated as a research prototype. We are actively seeking differential, linear, algebraic, and structural cryptanalysis. If you attempt an attack, successful or not, we would like to hear about it.
304
+
305
+ **Issue 2 — IND-CPA composite pass rate of 97.7%**
306
+
307
+ The individual IND-CPA sub-tests (semantic security, length leakage, prefix indistinguishability) all pass at 100%. The composite failure originates in the statistical distribution test (98.8%) and the key-change test (99.0%). Approximately 1% false failures are expected under α = 0.01, but the remaining ~1.3% excess is currently under investigation. No confirmed root cause has been identified yet.
308
+
309
+ **Issue 3 — SAC measurement error in test suite prior to v4.2.0**
310
+
311
+ In `story_basic.py` versions prior to v4.2.0, the `sac_avg` column was numerically identical to `avalanche_pct / 100` — a redundant column carrying no independent information. The SAC figures in CSV files up to `story_basic_7.csv` should be read as a restatement of the avalanche figure, not an independent measurement. Fixed in v4.2.0, which now reports genuine per-output-bit SAC variance (`sac_std`, `sac_min_bit`, `sac_max_bit`).
312
+
313
+ **Issue 4 — BIC implementation error in test suite prior to v4.2.0**
314
+
315
+ The `_bic()` function in `story_deep.py` prior to v4.2.0 measured per-input-bit avalanche rate rather than the pairwise output-bit independence criterion defined by Webster and Tavares (1986). The BIC columns in `story_deep` CSV files prior to v4.2.0 are mislabelled and should be disregarded. Fixed in v4.2.0, which now computes Pearson correlation across all C(128, 2) = 8,128 output-bit pairs.
316
+
317
+ **Issue 5 — Variable round count increases timing variance**
318
+
319
+ Actual rounds per message vary from 8 to 15. This is intentional — it adds per-message variability — but it causes higher coefficient of variation in timing measurements than a fixed-round cipher would produce. The API does not currently expose a fixed-round mode, so timing benchmarks reflect an average over the round-count distribution.
320
+
321
+ **Issue 6 — C extension requires manual compilation**
322
+
323
+ The `story_core.c` extension provides roughly 10× throughput improvement but must be compiled manually. A pip-installable binary wheel is not yet available. The pure-Python fallback is automatic.
324
+
325
+ **Issue 7 — No custom S-box generation tooling yet**
326
+
327
+ A tool to generate validated custom S-box pools with verified DDT and LAT properties is planned but not yet released.
328
+
329
+ ---
330
+
331
+ ## Custom S-box Pool
332
+
333
+ STORY supports user-supplied S-boxes. If a file exists at `customju/sboxes.json` relative to `story.py`, it takes precedence over the default pool. Format:
334
+
335
+ ```json
336
+ {
337
+ "0": "1,200,87,...",
338
+ "1": "43,11,..."
339
+ }
340
+ ```
341
+
342
+ Each value is a comma-separated list of 256 integers forming a bijection of 0–255. Values are stored 1-indexed in the JSON (add 1 to each actual S-box value when writing). The loader validates each entry as a permutation of 0–255 and raises `ValueError` if the check fails.
343
+
344
+ Any custom pool should be validated for DDT max ≤ 4 and NL ≥ 112 before deployment. Pools with weaker properties will reduce the security margins reported in the test results above.
345
+
346
+ ---
347
+
348
+ ## For Researchers
349
+
350
+ **Source files**
351
+
352
+ | File | Contents |
353
+ |---|---|
354
+ | `story.py` | Pure-Python reference implementation, fully commented |
355
+ | `storyc.py` | C-accelerated variant and CLI |
356
+ | `story_core.c` | C extension: GF(2⁸) multiply table, MDS Mix, CTR-mode kernel |
357
+ | `default_sboxes.py` | S-BOX Pool file |
358
+
359
+ **Reproducing the test results**
360
+
361
+ ```bash
362
+ git clone https://github.com/w3nabil/jucrypt
363
+ cd jucrypt/analyse
364
+ pip install numpy scipy pulp
365
+ python story_basic.py --workers 4
366
+ python story_ind.py --workers 4
367
+ python formal.py --quick
368
+ ```
369
+
370
+ **Attack surfaces we have not fully explored**
371
+
372
+ - Algebraic attacks exploiting the HMAC-based key schedule structure
373
+ - Related-story attacks (stories differing by a single character or punctuation mark)
374
+ - Timing side-channels in the pure-Python execution path
375
+ - The confirmed root cause of the 2.3% IND-CPA composite failure
376
+ - Invariant subspace attacks using the full round function under key-derived parameters
377
+
378
+ If you find a weakness — or confirm the absence of one — please open an issue or contact us directly. We would rather know.
379
+
380
+ ---
381
+
382
+ ## Citation
383
+
384
+ If you reference STORY in research, please cite:
385
+
386
+ ```
387
+ Islam, N. (2026). STORY: A Fully Parameterised, Story-Key Driven SPN Cipher.
388
+ DOI: <coming_soon>
389
+ ```