neva256 0.1.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.
neva256-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: neva256
3
+ Version: 0.1.0
4
+ Summary: NEVA-256: experimental self-synchronizing cipher (NOT for production secrets)
5
+ Author: EWX-AI
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ewxai/ewxai
8
+ Project-URL: Repository, https://github.com/ewxai/ewxai
9
+ Keywords: cipher,experimental,self-synchronizing,educational,ctf
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Classifier: Topic :: Education
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Provides-Extra: fast
18
+ Requires-Dist: neva256-native<0.2,>=0.1.0; extra == "fast"
19
+
20
+ # neva256 — NEVA-256 for Python
21
+
22
+ Port bit-exact dari implementasi TypeScript di `src/neva/neva.ts`.
23
+ Ciphertext yang dihasilkan **kompatibel lintas bahasa** (diverifikasi lewat
24
+ known-answer test + uji silang Python ↔ Node).
25
+
26
+ > ⚠️ **EKSPERIMENTAL.** NEVA belum pernah dianalisis kriptografer. Jangan pakai
27
+ > untuk melindungi data rahasia sungguhan — gunakan AES-256-GCM, libsodium,
28
+ > atau `cryptography` untuk produksi. Cocok untuk: belajar, CTF, obfuscation,
29
+ > watermark, dan eksperimen.
30
+
31
+ ## Instalasi
32
+
33
+ ```bash
34
+ pip install neva256 # pure Python (jalan di mana saja)
35
+ pip install "neva256[fast]" # + akselerator Rust/PyO3 (~360x lebih cepat)
36
+ ```
37
+
38
+ Dari repo ini:
39
+
40
+ ```bash
41
+ cd python && pip install -e . # pure Python
42
+ cd python/rust && pip install maturin && maturin develop --release # + native
43
+ ```
44
+
45
+ Backend native dipakai otomatis bila tersedia, dan jatuh kembali ke pure Python
46
+ kalau tidak. Eksplisit: `NevaCipher(key, nonce, use_native=True/False)`; cek
47
+ `cipher.backend` (`"native"` atau `"python"`) dan konstanta `NATIVE_AVAILABLE`.
48
+
49
+ ## Pemakaian
50
+
51
+ ```python
52
+ from neva256 import NevaCipher, generate_key, generate_nonce
53
+
54
+ key = generate_key() # simpan ini (32 byte)
55
+ nonce = generate_nonce() # acak per pesan (16 byte)
56
+
57
+ cipher = NevaCipher(key, nonce)
58
+
59
+ blob = cipher.encrypt_text("halo dunia 🌪️") # string base64 (ct || tag)
60
+ print(cipher.decrypt_text(blob)) # 'halo dunia 🌪️'
61
+
62
+ # bytes
63
+ data = b"\x00\x01\x02"
64
+ sealed = cipher.encrypt_bytes(data)
65
+ assert cipher.decrypt_bytes(sealed) == data
66
+
67
+ # mode toleran kerusakan: 1 byte rusak hanya merusak ~5 byte sekitarnya
68
+ partial = cipher.decrypt_bytes(corrupted, verify=False)
69
+ ```
70
+
71
+ Salah kunci atau ciphertext yang diubah → `NevaError`
72
+ (`integrity check failed`).
73
+
74
+ ## CLI
75
+
76
+ ```bash
77
+ neva256 keygen
78
+ neva256 enc "rahasia" --key <64-hex>
79
+ neva256 dec "<nonceHex>:<base64>" --key <64-hex>
80
+ echo "teks dari pipe" | neva256 enc - --key <64-hex>
81
+ ```
82
+
83
+ ## Test
84
+
85
+ ```bash
86
+ cd python
87
+ python -m unittest discover -s tests -v
88
+ ```
89
+
90
+ Test mencakup known-answer vectors dari implementasi TypeScript, round-trip
91
+ (a–4 KB), deteksi tamper, validasi ukuran key/nonce, dan sifat
92
+ self-synchronizing.
93
+
94
+ ## Catatan keamanan
95
+
96
+ Lihat `src/neva/README.md` untuk matriks serangan lengkap: differential per
97
+ round, kurva sensitivitas rotasi, hill-climbing, SMT/Z3, dan temuan
98
+ equivalent-key pada versi 1-swirl. Ringkasnya: implementasi ini memakai
99
+ **4 swirl** dengan margin satu round di atas ambang kebersihan statistik
100
+ terukur — tapi statusnya tetap eksperimental.
101
+
102
+ ## Performa (terukur di aarch64)
103
+
104
+ | Backend | Encrypt 1 MB | Throughput |
105
+ | --- | --- | --- |
106
+ | Pure Python | 31.3 s | 0.03 MB/s |
107
+ | **Native (Rust/PyO3)** | **87 ms** | **11.5 MB/s** |
108
+
109
+ Native ~360× lebih cepat dan melepas GIL: 4 thread paralel = 2.5× speedup pada
110
+ mesin uji. Implementasi TypeScript ~3 MB/s (native Rust ~4× lebih cepat dari itu).
111
+ Untuk teks/payload kecil, pure Python sudah cukup.
@@ -0,0 +1,92 @@
1
+ # neva256 — NEVA-256 for Python
2
+
3
+ Port bit-exact dari implementasi TypeScript di `src/neva/neva.ts`.
4
+ Ciphertext yang dihasilkan **kompatibel lintas bahasa** (diverifikasi lewat
5
+ known-answer test + uji silang Python ↔ Node).
6
+
7
+ > ⚠️ **EKSPERIMENTAL.** NEVA belum pernah dianalisis kriptografer. Jangan pakai
8
+ > untuk melindungi data rahasia sungguhan — gunakan AES-256-GCM, libsodium,
9
+ > atau `cryptography` untuk produksi. Cocok untuk: belajar, CTF, obfuscation,
10
+ > watermark, dan eksperimen.
11
+
12
+ ## Instalasi
13
+
14
+ ```bash
15
+ pip install neva256 # pure Python (jalan di mana saja)
16
+ pip install "neva256[fast]" # + akselerator Rust/PyO3 (~360x lebih cepat)
17
+ ```
18
+
19
+ Dari repo ini:
20
+
21
+ ```bash
22
+ cd python && pip install -e . # pure Python
23
+ cd python/rust && pip install maturin && maturin develop --release # + native
24
+ ```
25
+
26
+ Backend native dipakai otomatis bila tersedia, dan jatuh kembali ke pure Python
27
+ kalau tidak. Eksplisit: `NevaCipher(key, nonce, use_native=True/False)`; cek
28
+ `cipher.backend` (`"native"` atau `"python"`) dan konstanta `NATIVE_AVAILABLE`.
29
+
30
+ ## Pemakaian
31
+
32
+ ```python
33
+ from neva256 import NevaCipher, generate_key, generate_nonce
34
+
35
+ key = generate_key() # simpan ini (32 byte)
36
+ nonce = generate_nonce() # acak per pesan (16 byte)
37
+
38
+ cipher = NevaCipher(key, nonce)
39
+
40
+ blob = cipher.encrypt_text("halo dunia 🌪️") # string base64 (ct || tag)
41
+ print(cipher.decrypt_text(blob)) # 'halo dunia 🌪️'
42
+
43
+ # bytes
44
+ data = b"\x00\x01\x02"
45
+ sealed = cipher.encrypt_bytes(data)
46
+ assert cipher.decrypt_bytes(sealed) == data
47
+
48
+ # mode toleran kerusakan: 1 byte rusak hanya merusak ~5 byte sekitarnya
49
+ partial = cipher.decrypt_bytes(corrupted, verify=False)
50
+ ```
51
+
52
+ Salah kunci atau ciphertext yang diubah → `NevaError`
53
+ (`integrity check failed`).
54
+
55
+ ## CLI
56
+
57
+ ```bash
58
+ neva256 keygen
59
+ neva256 enc "rahasia" --key <64-hex>
60
+ neva256 dec "<nonceHex>:<base64>" --key <64-hex>
61
+ echo "teks dari pipe" | neva256 enc - --key <64-hex>
62
+ ```
63
+
64
+ ## Test
65
+
66
+ ```bash
67
+ cd python
68
+ python -m unittest discover -s tests -v
69
+ ```
70
+
71
+ Test mencakup known-answer vectors dari implementasi TypeScript, round-trip
72
+ (a–4 KB), deteksi tamper, validasi ukuran key/nonce, dan sifat
73
+ self-synchronizing.
74
+
75
+ ## Catatan keamanan
76
+
77
+ Lihat `src/neva/README.md` untuk matriks serangan lengkap: differential per
78
+ round, kurva sensitivitas rotasi, hill-climbing, SMT/Z3, dan temuan
79
+ equivalent-key pada versi 1-swirl. Ringkasnya: implementasi ini memakai
80
+ **4 swirl** dengan margin satu round di atas ambang kebersihan statistik
81
+ terukur — tapi statusnya tetap eksperimental.
82
+
83
+ ## Performa (terukur di aarch64)
84
+
85
+ | Backend | Encrypt 1 MB | Throughput |
86
+ | --- | --- | --- |
87
+ | Pure Python | 31.3 s | 0.03 MB/s |
88
+ | **Native (Rust/PyO3)** | **87 ms** | **11.5 MB/s** |
89
+
90
+ Native ~360× lebih cepat dan melepas GIL: 4 thread paralel = 2.5× speedup pada
91
+ mesin uji. Implementasi TypeScript ~3 MB/s (native Rust ~4× lebih cepat dari itu).
92
+ Untuk teks/payload kecil, pure Python sudah cukup.
@@ -0,0 +1,315 @@
1
+ """NEVA-256 — Non-Equilibrium Vortex Algorithm (Python port).
2
+
3
+ A bit-exact Python port of the TypeScript implementation in
4
+ ``src/neva/neva.ts``. Ciphertexts are interoperable between both
5
+ implementations (verified by known-answer tests and round-trip tests).
6
+
7
+ EXPERIMENTAL / EDUCATIONAL — NEVA has not been analyzed by cryptographers.
8
+ Do not use it to protect real secrets; use AES-256-GCM / libsodium instead.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ from typing import Optional, Union
15
+
16
+ try: # optional Rust accelerator (neva256-native)
17
+ import neva256_native as _native
18
+ except ImportError: # pragma: no cover - pure Python fallback
19
+ _native = None
20
+
21
+ NATIVE_AVAILABLE = _native is not None
22
+
23
+ __all__ = [
24
+ "KEY_BYTES",
25
+ "NONCE_BYTES",
26
+ "TAG_BYTES",
27
+ "VERSION",
28
+ "NATIVE_AVAILABLE",
29
+ "NevaError",
30
+ "NevaCipher",
31
+ "generate_key",
32
+ "generate_nonce",
33
+ ]
34
+
35
+ __version__ = "0.1.0"
36
+ VERSION = "NEVA-256/0.1.0"
37
+
38
+ KEY_BYTES = 32
39
+ NONCE_BYTES = 16
40
+ TAG_BYTES = 16
41
+
42
+ _GOLDEN = 0x9E3779B9
43
+ _SEED_SECOND = 0x85EBCA6B
44
+
45
+ _SALT_VORTEX = 0x1
46
+ _SALT_COMET = 0x7E3
47
+ _SALT_TAG = 0xC0FFEE
48
+
49
+ _M32 = 0xFFFFFFFF
50
+
51
+
52
+ class NevaError(Exception):
53
+ """Raised for invalid keys, tampered ciphertext, or malformed input."""
54
+
55
+
56
+ def _m32(x: int) -> int:
57
+ return x & _M32
58
+
59
+
60
+ def _rotl(x: int, r: int) -> int:
61
+ r &= 31
62
+ return _m32((x << r) | (x >> (32 - r)))
63
+
64
+
65
+ def _swirl(w: list, rnd: int) -> None:
66
+ """One ARX swirl over an 8-lane state (in place)."""
67
+ for i in range(8):
68
+ a = w[i]
69
+ b = w[(i + 1) & 7]
70
+ c = w[(i + 3) & 7]
71
+ r = 1 + ((a >> ((rnd + i) & 7)) % 31)
72
+ w[i] = _m32(_rotl(_m32(a ^ b), r) + c + _m32((rnd + i) * _GOLDEN))
73
+
74
+
75
+ def _seed_words(salt: int) -> list:
76
+ return [
77
+ _m32(0x6E657661 ^ salt),
78
+ _m32(0x2D766F72 ^ _m32(salt * _GOLDEN)),
79
+ 0x74657820,
80
+ 0x636F7265,
81
+ _m32(0x0E3D25C1 ^ salt),
82
+ 0x656C6961,
83
+ _m32(0x24BD08E5 ^ _m32(salt * 0x27D4EB2F)),
84
+ 0x1A2B3C4D,
85
+ ]
86
+
87
+
88
+ def _absorb_bytes(v: list, data: bytes) -> None:
89
+ acc = 0
90
+ count = 0
91
+ for byte in data:
92
+ acc = _m32(acc | (byte << (8 * (count & 3))))
93
+ count += 1
94
+ if (count & 3) == 0:
95
+ lane = (count >> 2) & 7
96
+ v[lane] = _m32(v[lane] ^ acc ^ _m32(count * _GOLDEN))
97
+ _swirl(v, lane & 7)
98
+ acc = 0
99
+ if count & 3:
100
+ lane = count & 7
101
+ v[lane] = _m32(v[lane] ^ acc ^ _m32((count + 1) * _GOLDEN))
102
+ _swirl(v, lane & 7)
103
+
104
+
105
+ def _schedule(key: bytes, nonce: bytes, salt: int) -> list:
106
+ v = _seed_words(salt)
107
+ _swirl(v, salt & 7)
108
+ v[0] = _m32(v[0] ^ _m32((salt + 1) * _GOLDEN))
109
+ _absorb_bytes(v, key)
110
+ _swirl(v, 1)
111
+ _swirl(v, 2)
112
+ _absorb_bytes(v, nonce)
113
+ for i in range(8):
114
+ _swirl(v, 3 + (i & 7))
115
+ return v
116
+
117
+
118
+ def _stream_byte(core: list, reg: int, pos: int) -> int:
119
+ w = core.copy()
120
+ w[0] = _m32(w[0] ^ reg ^ _m32((pos + 1) * _GOLDEN))
121
+ w[1] = _m32(w[1] ^ (reg >> 16) ^ _m32((pos + 1) * _SEED_SECOND))
122
+ w[2] = _m32(w[2] + pos)
123
+ w[7] = _m32(w[7] ^ reg ^ _m32(reg << 13))
124
+ _swirl(w, 0)
125
+ _swirl(w, 3)
126
+ _swirl(w, 6)
127
+ _swirl(w, 9)
128
+ return (w[(pos * 5) & 7] >> ((pos & 3) * 8)) & 0xFF
129
+
130
+
131
+ def _vortex_layer(data: bytes, core: list, decoding: bool = False) -> bytes:
132
+ out = bytearray(len(data))
133
+ reg = 0
134
+ for i in range(len(data)):
135
+ sb = _stream_byte(core, reg, i)
136
+ c = (data[i] ^ sb) & 0xFF
137
+ out[i] = c
138
+ feed = data[i] if decoding else c
139
+ reg = _m32((reg >> 8) | (feed << 24))
140
+ return bytes(out)
141
+
142
+
143
+ def _make_permutation(n: int, seeds: list) -> list:
144
+ rng = list(seeds)
145
+ if (rng[0] | rng[1] | rng[2] | rng[3]) == 0:
146
+ rng[0] = _GOLDEN
147
+
148
+ def nxt() -> int:
149
+ t = rng[3]
150
+ w = rng[0]
151
+ rng[3] = rng[2]
152
+ rng[2] = rng[1]
153
+ rng[1] = w
154
+ t = _m32(t ^ _m32(t << 11))
155
+ t = _m32(t ^ (t >> 8))
156
+ rng[0] = _m32(t ^ w ^ (w >> 19))
157
+ return rng[0]
158
+
159
+ p = list(range(n))
160
+ for i in range(n - 1, 0, -1):
161
+ j = nxt() % (i + 1)
162
+ p[i], p[j] = p[j], p[i]
163
+ return p
164
+
165
+
166
+ def _compute_tag(tag_state: list, data: bytes, length: int) -> bytes:
167
+ v = list(tag_state)
168
+ _absorb_bytes(v, data)
169
+ len_digest = bytes(
170
+ [length & 0xFF, (length >> 8) & 0xFF, (length >> 16) & 0xFF, (length >> 24) & 0xFF, 0x5A]
171
+ )
172
+ _absorb_bytes(v, len_digest)
173
+ for i in range(8):
174
+ _swirl(v, (i + 1) & 7)
175
+ out = bytearray(16)
176
+ for i in range(4):
177
+ out[i * 4] = v[i] & 0xFF
178
+ out[i * 4 + 1] = (v[i] >> 8) & 0xFF
179
+ out[i * 4 + 2] = (v[i] >> 16) & 0xFF
180
+ out[i * 4 + 3] = (v[i] >> 24) & 0xFF
181
+ return bytes(out)
182
+
183
+
184
+ def _as_bytes(value: Union[bytes, bytearray, str], what: str) -> bytes:
185
+ if isinstance(value, str):
186
+ value = value.encode()
187
+ if not isinstance(value, (bytes, bytearray)):
188
+ raise NevaError(f"{what} must be bytes or str")
189
+ return bytes(value)
190
+
191
+
192
+ class NevaCipher:
193
+ """NEVA-256 text/binary cipher.
194
+
195
+ >>> import os
196
+ >>> c = NevaCipher(os.urandom(32), os.urandom(16))
197
+ >>> blob = c.encrypt_text("halo dunia")
198
+ >>> c.decrypt_text(blob)
199
+ 'halo dunia'
200
+ """
201
+
202
+ def __init__(
203
+ self,
204
+ key: Union[bytes, bytearray, str],
205
+ nonce: Optional[Union[bytes, bytearray, str]] = None,
206
+ *,
207
+ use_native: Optional[bool] = None,
208
+ ) -> None:
209
+ """Create a cipher.
210
+
211
+ ``use_native``: ``None`` (default) uses the native Rust backend when
212
+ installed and falls back to pure Python; ``True`` requires native;
213
+ ``False`` forces the pure Python implementation.
214
+ """
215
+ key = _as_bytes(key, "key")
216
+ nonce = bytes(NONCE_BYTES) if nonce is None else _as_bytes(nonce, "nonce")
217
+ if len(key) != KEY_BYTES:
218
+ raise NevaError(f"key must be exactly {KEY_BYTES} bytes (got {len(key)})")
219
+ if len(nonce) != NONCE_BYTES:
220
+ raise NevaError(f"nonce must be exactly {NONCE_BYTES} bytes (got {len(nonce)})")
221
+ self.nonce = nonce
222
+ self._vortex_core = _schedule(key, nonce, _SALT_VORTEX)
223
+ self._comet_seeds = list(_schedule(key, nonce, _SALT_COMET)[:4])
224
+ self._tag_state = _schedule(key, nonce, _SALT_TAG)
225
+
226
+ self._native = None
227
+ self.backend = "python"
228
+ if use_native is True and not NATIVE_AVAILABLE:
229
+ raise NevaError("native backend requested but neva256-native is not installed")
230
+ if use_native is not False and NATIVE_AVAILABLE:
231
+ try:
232
+ self._native = _native.NativeCipher(key, nonce)
233
+ self.backend = "native"
234
+ except Exception: # pragma: no cover - fall back defensively
235
+ if use_native is True:
236
+ raise
237
+
238
+ def encrypt_bytes(self, data: Union[bytes, bytearray, str]) -> bytes:
239
+ """Encrypt bytes -> ``ciphertext || 16-byte tag``."""
240
+ data = _as_bytes(data, "data")
241
+ if self._native is not None:
242
+ return bytes(self._native.encrypt_bytes(data))
243
+ n = len(data)
244
+ masked = _vortex_layer(data, self._vortex_core)
245
+ if n > 1:
246
+ p = _make_permutation(n, self._comet_seeds)
247
+ payload = bytearray(n)
248
+ for i in range(n):
249
+ payload[i] = masked[p[i]]
250
+ payload = bytes(payload)
251
+ else:
252
+ payload = masked
253
+ return payload + _compute_tag(self._tag_state, payload, n)
254
+
255
+ def decrypt_bytes(self, blob: Union[bytes, bytearray, str], verify: bool = True) -> bytes:
256
+ """Decrypt bytes produced by :meth:`encrypt_bytes`.
257
+
258
+ With ``verify=False`` the divergence tag is ignored and corruption stays
259
+ local (~5 bytes per damaged byte) thanks to self-synchronization.
260
+ """
261
+ blob = _as_bytes(blob, "blob")
262
+ if len(blob) < TAG_BYTES:
263
+ raise NevaError("ciphertext too short: missing divergence tag")
264
+ if self._native is not None:
265
+ try:
266
+ return bytes(self._native.decrypt_bytes(blob, verify))
267
+ except NevaError:
268
+ raise
269
+ except Exception as exc:
270
+ raise NevaError(str(exc)) from None
271
+ n = len(blob) - TAG_BYTES
272
+ payload = blob[:n]
273
+ tag = blob[n:]
274
+ if verify and not _constant_eq(_compute_tag(self._tag_state, payload, n), tag):
275
+ raise NevaError("integrity check failed: ciphertext was modified")
276
+ if n > 1:
277
+ p = _make_permutation(n, self._comet_seeds)
278
+ masked = bytearray(n)
279
+ for i in range(n):
280
+ masked[p[i]] = payload[i]
281
+ masked = bytes(masked)
282
+ else:
283
+ masked = payload
284
+ return _vortex_layer(masked, self._vortex_core, decoding=True)
285
+
286
+ def encrypt_text(self, text: str) -> str:
287
+ """Encrypt UTF-8 text to a standard base64 string."""
288
+ import base64
289
+
290
+ return base64.b64encode(self.encrypt_bytes(text.encode())).decode()
291
+
292
+ def decrypt_text(self, blob: str, verify: bool = True) -> str:
293
+ """Decrypt a base64 string produced by :meth:`encrypt_text`."""
294
+ import base64
295
+
296
+ return self.decrypt_bytes(base64.b64decode(blob), verify=verify).decode()
297
+
298
+
299
+ def _constant_eq(a: bytes, b: bytes) -> bool:
300
+ if len(a) != len(b):
301
+ return False
302
+ diff = 0
303
+ for x, y in zip(a, b):
304
+ diff |= x ^ y
305
+ return diff == 0
306
+
307
+
308
+ def generate_key() -> bytes:
309
+ """Return a random 256-bit key."""
310
+ return os.urandom(KEY_BYTES)
311
+
312
+
313
+ def generate_nonce() -> bytes:
314
+ """Return a random 128-bit nonce."""
315
+ return os.urandom(NONCE_BYTES)
@@ -0,0 +1,83 @@
1
+ """NEVA-256 command line interface (mirrors the TypeScript CLI)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import sys
7
+
8
+ from . import (
9
+ KEY_BYTES,
10
+ NONCE_BYTES,
11
+ NevaCipher,
12
+ NevaError,
13
+ generate_key,
14
+ generate_nonce,
15
+ )
16
+
17
+ USAGE = """NEVA-256 — text encryption (experimental, not for real secrets)
18
+
19
+ Usage:
20
+ neva256 keygen generate a random 256-bit key (hex)
21
+ neva256 enc <text|-> --key <hex> encrypt text ("-" = stdin)
22
+ neva256 dec <blob|-> --key <hex> decrypt a <nonceHex>:<base64> blob
23
+ """
24
+
25
+
26
+ def _hex_to_bytes(value: str, expected: int, what: str) -> bytes:
27
+ try:
28
+ raw = bytes.fromhex(value)
29
+ except ValueError as exc:
30
+ raise NevaError(f"{what} must be hex") from exc
31
+ if len(raw) != expected:
32
+ raise NevaError(f"{what} must be exactly {expected * 2} hex characters")
33
+ return raw
34
+
35
+
36
+ def _read_input(value: str) -> str:
37
+ return sys.stdin.read() if value == "-" else value
38
+
39
+
40
+ def main(argv: list | None = None) -> int:
41
+ args = list(sys.argv[1:] if argv is None else argv)
42
+ if not args or args[0] in ("help", "-h", "--help"):
43
+ print(USAGE, end="")
44
+ return 0
45
+
46
+ cmd = args[0]
47
+ try:
48
+ if cmd == "keygen":
49
+ print(generate_key().hex())
50
+ return 0
51
+
52
+ if cmd in ("enc", "dec"):
53
+ if len(args) < 2:
54
+ raise NevaError(f"missing argument for {cmd} (use '-' for stdin)")
55
+ if "--key" not in args:
56
+ raise NevaError("missing --key <64-hex-chars>")
57
+ key = _hex_to_bytes(args[args.index("--key") + 1], KEY_BYTES, "key")
58
+ value = _read_input(args[1])
59
+
60
+ if cmd == "enc":
61
+ nonce = generate_nonce()
62
+ cipher = NevaCipher(key, nonce)
63
+ blob = cipher.encrypt_bytes(value.encode())
64
+ print(f"{nonce.hex()}:{base64.b64encode(blob).decode()}")
65
+ return 0
66
+
67
+ if ":" not in value:
68
+ raise NevaError("bad blob: expected <nonceHex>:<base64>")
69
+ nonce_hex, b64 = value.strip().split(":", 1)
70
+ cipher = NevaCipher(key, _hex_to_bytes(nonce_hex, NONCE_BYTES, "nonce"))
71
+ sys.stdout.write(cipher.decrypt_bytes(base64.b64decode(b64)).decode())
72
+ if sys.stdout.isatty():
73
+ sys.stdout.write("\n")
74
+ return 0
75
+
76
+ raise NevaError(f"unknown command: {cmd} (try: help)")
77
+ except NevaError as exc:
78
+ print(f"error: NEVA: {exc}", file=sys.stderr)
79
+ return 1
80
+
81
+
82
+ if __name__ == "__main__": # pragma: no cover
83
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: neva256
3
+ Version: 0.1.0
4
+ Summary: NEVA-256: experimental self-synchronizing cipher (NOT for production secrets)
5
+ Author: EWX-AI
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ewxai/ewxai
8
+ Project-URL: Repository, https://github.com/ewxai/ewxai
9
+ Keywords: cipher,experimental,self-synchronizing,educational,ctf
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Classifier: Topic :: Education
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Provides-Extra: fast
18
+ Requires-Dist: neva256-native<0.2,>=0.1.0; extra == "fast"
19
+
20
+ # neva256 — NEVA-256 for Python
21
+
22
+ Port bit-exact dari implementasi TypeScript di `src/neva/neva.ts`.
23
+ Ciphertext yang dihasilkan **kompatibel lintas bahasa** (diverifikasi lewat
24
+ known-answer test + uji silang Python ↔ Node).
25
+
26
+ > ⚠️ **EKSPERIMENTAL.** NEVA belum pernah dianalisis kriptografer. Jangan pakai
27
+ > untuk melindungi data rahasia sungguhan — gunakan AES-256-GCM, libsodium,
28
+ > atau `cryptography` untuk produksi. Cocok untuk: belajar, CTF, obfuscation,
29
+ > watermark, dan eksperimen.
30
+
31
+ ## Instalasi
32
+
33
+ ```bash
34
+ pip install neva256 # pure Python (jalan di mana saja)
35
+ pip install "neva256[fast]" # + akselerator Rust/PyO3 (~360x lebih cepat)
36
+ ```
37
+
38
+ Dari repo ini:
39
+
40
+ ```bash
41
+ cd python && pip install -e . # pure Python
42
+ cd python/rust && pip install maturin && maturin develop --release # + native
43
+ ```
44
+
45
+ Backend native dipakai otomatis bila tersedia, dan jatuh kembali ke pure Python
46
+ kalau tidak. Eksplisit: `NevaCipher(key, nonce, use_native=True/False)`; cek
47
+ `cipher.backend` (`"native"` atau `"python"`) dan konstanta `NATIVE_AVAILABLE`.
48
+
49
+ ## Pemakaian
50
+
51
+ ```python
52
+ from neva256 import NevaCipher, generate_key, generate_nonce
53
+
54
+ key = generate_key() # simpan ini (32 byte)
55
+ nonce = generate_nonce() # acak per pesan (16 byte)
56
+
57
+ cipher = NevaCipher(key, nonce)
58
+
59
+ blob = cipher.encrypt_text("halo dunia 🌪️") # string base64 (ct || tag)
60
+ print(cipher.decrypt_text(blob)) # 'halo dunia 🌪️'
61
+
62
+ # bytes
63
+ data = b"\x00\x01\x02"
64
+ sealed = cipher.encrypt_bytes(data)
65
+ assert cipher.decrypt_bytes(sealed) == data
66
+
67
+ # mode toleran kerusakan: 1 byte rusak hanya merusak ~5 byte sekitarnya
68
+ partial = cipher.decrypt_bytes(corrupted, verify=False)
69
+ ```
70
+
71
+ Salah kunci atau ciphertext yang diubah → `NevaError`
72
+ (`integrity check failed`).
73
+
74
+ ## CLI
75
+
76
+ ```bash
77
+ neva256 keygen
78
+ neva256 enc "rahasia" --key <64-hex>
79
+ neva256 dec "<nonceHex>:<base64>" --key <64-hex>
80
+ echo "teks dari pipe" | neva256 enc - --key <64-hex>
81
+ ```
82
+
83
+ ## Test
84
+
85
+ ```bash
86
+ cd python
87
+ python -m unittest discover -s tests -v
88
+ ```
89
+
90
+ Test mencakup known-answer vectors dari implementasi TypeScript, round-trip
91
+ (a–4 KB), deteksi tamper, validasi ukuran key/nonce, dan sifat
92
+ self-synchronizing.
93
+
94
+ ## Catatan keamanan
95
+
96
+ Lihat `src/neva/README.md` untuk matriks serangan lengkap: differential per
97
+ round, kurva sensitivitas rotasi, hill-climbing, SMT/Z3, dan temuan
98
+ equivalent-key pada versi 1-swirl. Ringkasnya: implementasi ini memakai
99
+ **4 swirl** dengan margin satu round di atas ambang kebersihan statistik
100
+ terukur — tapi statusnya tetap eksperimental.
101
+
102
+ ## Performa (terukur di aarch64)
103
+
104
+ | Backend | Encrypt 1 MB | Throughput |
105
+ | --- | --- | --- |
106
+ | Pure Python | 31.3 s | 0.03 MB/s |
107
+ | **Native (Rust/PyO3)** | **87 ms** | **11.5 MB/s** |
108
+
109
+ Native ~360× lebih cepat dan melepas GIL: 4 thread paralel = 2.5× speedup pada
110
+ mesin uji. Implementasi TypeScript ~3 MB/s (native Rust ~4× lebih cepat dari itu).
111
+ Untuk teks/payload kecil, pure Python sudah cukup.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ neva256/__init__.py
4
+ neva256/cli.py
5
+ neva256/py.typed
6
+ neva256.egg-info/PKG-INFO
7
+ neva256.egg-info/SOURCES.txt
8
+ neva256.egg-info/dependency_links.txt
9
+ neva256.egg-info/entry_points.txt
10
+ neva256.egg-info/requires.txt
11
+ neva256.egg-info/top_level.txt
12
+ tests/test_neva.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ neva256 = neva256.cli:main
@@ -0,0 +1,3 @@
1
+
2
+ [fast]
3
+ neva256-native<0.2,>=0.1.0
@@ -0,0 +1 @@
1
+ neva256
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "neva256"
7
+ version = "0.1.0"
8
+ description = "NEVA-256: experimental self-synchronizing cipher (NOT for production secrets)"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ authors = [{ name = "EWX-AI" }]
13
+ keywords = ["cipher", "experimental", "self-synchronizing", "educational", "ctf"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Security :: Cryptography",
19
+ "Topic :: Education",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ fast = ["neva256-native>=0.1.0,<0.2"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/ewxai/ewxai"
28
+ Repository = "https://github.com/ewxai/ewxai"
29
+
30
+ [project.scripts]
31
+ neva256 = "neva256.cli:main"
32
+
33
+ [tool.setuptools]
34
+ packages = ["neva256"]
35
+
36
+ [tool.setuptools.package-data]
37
+ neva256 = ["py.typed"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,160 @@
1
+ """Tests for the NEVA-256 Python port.
2
+
3
+ Includes known-answer vectors produced by the TypeScript implementation
4
+ (``src/neva/neva.ts``) to guarantee bit-exact cross-language compatibility.
5
+ """
6
+
7
+ import os
8
+ import unittest
9
+
10
+ from neva256 import (
11
+ KEY_BYTES,
12
+ NATIVE_AVAILABLE,
13
+ NONCE_BYTES,
14
+ TAG_BYTES,
15
+ NevaCipher,
16
+ NevaError,
17
+ generate_key,
18
+ generate_nonce,
19
+ )
20
+
21
+ # Known-answer vectors generated with the TypeScript implementation.
22
+ KAT_KEY = bytes.fromhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
23
+ KAT_NONCE = bytes.fromhex("f0e1d2c3b4a5968778695a4b3c2d1e0f")
24
+ KAT = [
25
+ ("", "N4YwZ1bf1M/ydRjWfgXtMA=="),
26
+ ("a", "FnerAJQGbirLgGg+pxB2JO0="),
27
+ ("halo dunia", "yLbIIB8GBvVN+R0JO/vueBpPXGpT9Bnigj0="),
28
+ ("NEVA-256 🌪️ unicode test", "x1Fz4ZaaeaEA/H7hTxePjeO3ObyTGArR9QpBswSUJBnVmmsOcRAy1A4lvyrC"),
29
+ (
30
+ "x" * 64,
31
+ "tkR0wQ9QyBKIEtmBrq3ESLIQs5wvO9vhIILU2RODOg98v2ZvGsXs3/1UuLnEIhRz9dLQAcfhjeF01EOQG8oYfQvxx7cDgzUMKfXrpSTmiAs=",
32
+ ),
33
+ ("0123456789abcdef", "3M5USVJYDuNHgNJpm6xQJekFSrvWnSgHbVWP4/jKutc="),
34
+ ]
35
+
36
+
37
+ class TestKnownAnswers(unittest.TestCase):
38
+ def test_matches_typescript_vectors(self):
39
+ for text, expected in KAT:
40
+ with self.subTest(text=text[:20]):
41
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE)
42
+ self.assertEqual(cipher.encrypt_text(text), expected)
43
+ self.assertEqual(NevaCipher(KAT_KEY, KAT_NONCE).decrypt_text(expected), text)
44
+
45
+
46
+ class TestRoundTrip(unittest.TestCase):
47
+ def test_many_sizes(self):
48
+ for size in (0, 1, 2, 5, 16, 31, 32, 255, 1000, 4096):
49
+ with self.subTest(size=size):
50
+ data = os.urandom(size)
51
+ cipher = NevaCipher(generate_key(), generate_nonce())
52
+ self.assertEqual(cipher.decrypt_bytes(cipher.encrypt_bytes(data)), data)
53
+
54
+ def test_unicode_text(self):
55
+ cipher = NevaCipher(generate_key(), generate_nonce())
56
+ text = "Halo dunia! 🌪️ NEVA-256 — vortex 👾 émojis + ascii"
57
+ self.assertEqual(cipher.decrypt_text(cipher.encrypt_text(text)), text)
58
+
59
+ def test_deterministic(self):
60
+ key, nonce, data = generate_key(), generate_nonce(), os.urandom(64)
61
+ a = NevaCipher(key, nonce).encrypt_bytes(data)
62
+ b = NevaCipher(key, nonce).encrypt_bytes(data)
63
+ self.assertEqual(a, b)
64
+
65
+ def test_nonce_changes_output(self):
66
+ key, data = generate_key(), os.urandom(64)
67
+ a = NevaCipher(key, generate_nonce()).encrypt_bytes(data)
68
+ b = NevaCipher(key, generate_nonce()).encrypt_bytes(data)
69
+ self.assertNotEqual(a, b)
70
+
71
+
72
+ class TestSecurityProperties(unittest.TestCase):
73
+ def test_wrong_key_rejected(self):
74
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE)
75
+ blob = cipher.encrypt_bytes(b"secret")
76
+ other = NevaCipher(generate_key(), KAT_NONCE)
77
+ with self.assertRaises(NevaError):
78
+ other.decrypt_bytes(blob)
79
+
80
+ def test_tampering_detected(self):
81
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE)
82
+ blob = bytearray(cipher.encrypt_bytes(os.urandom(512)))
83
+ blob[100] ^= 0xFF
84
+ with self.assertRaises(NevaError):
85
+ cipher.decrypt_bytes(bytes(blob))
86
+
87
+ def test_resilient_mode_localizes_damage(self):
88
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE)
89
+ data = os.urandom(10000)
90
+ blob = bytearray(cipher.encrypt_bytes(data))
91
+ blob[5000] ^= 0x40
92
+ out = cipher.decrypt_bytes(bytes(blob), verify=False)
93
+ damaged = sum(1 for i in range(len(data)) if out[i] != data[i])
94
+ self.assertLessEqual(damaged, 6)
95
+
96
+ def test_size_validation(self):
97
+ with self.assertRaises(NevaError):
98
+ NevaCipher(bytes(KEY_BYTES - 1))
99
+ with self.assertRaises(NevaError):
100
+ NevaCipher(generate_key(), bytes(NONCE_BYTES + 1))
101
+
102
+ def test_short_ciphertext_rejected(self):
103
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE)
104
+ with self.assertRaises(NevaError):
105
+ cipher.decrypt_bytes(b"too short")
106
+
107
+
108
+ class TestBackends(unittest.TestCase):
109
+ def test_pure_backend_can_be_forced(self):
110
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE, use_native=False)
111
+ self.assertEqual(cipher.backend, "python")
112
+ self.assertEqual(cipher.encrypt_text("abc"), NevaCipher(KAT_KEY, KAT_NONCE, use_native=False).encrypt_text("abc"))
113
+
114
+ @unittest.skipUnless(NATIVE_AVAILABLE, "neva256-native not installed")
115
+ def test_native_backend_selected(self):
116
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE, use_native=True)
117
+ self.assertEqual(cipher.backend, "native")
118
+ for text, expected in KAT:
119
+ self.assertEqual(cipher.encrypt_text(text), expected)
120
+
121
+ @unittest.skipUnless(NATIVE_AVAILABLE, "neva256-native not installed")
122
+ def test_native_matches_pure_python_exactly(self):
123
+ key, nonce = generate_key(), generate_nonce()
124
+ native = NevaCipher(key, nonce, use_native=True)
125
+ pure = NevaCipher(key, nonce, use_native=False)
126
+ for size in (0, 1, 2, 31, 255, 1000, 4096):
127
+ with self.subTest(size=size):
128
+ data = os.urandom(size)
129
+ self.assertEqual(native.encrypt_bytes(data), pure.encrypt_bytes(data))
130
+
131
+ @unittest.skipUnless(NATIVE_AVAILABLE, "neva256-native not installed")
132
+ def test_cross_backend_round_trip(self):
133
+ key, nonce = generate_key(), generate_nonce()
134
+ native = NevaCipher(key, nonce, use_native=True)
135
+ pure = NevaCipher(key, nonce, use_native=False)
136
+ data = os.urandom(500)
137
+ self.assertEqual(pure.decrypt_bytes(native.encrypt_bytes(data)), data)
138
+ self.assertEqual(native.decrypt_bytes(pure.encrypt_bytes(data)), data)
139
+
140
+ @unittest.skipUnless(NATIVE_AVAILABLE, "neva256-native not installed")
141
+ def test_native_reports_tampering_as_neva_error(self):
142
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE, use_native=True)
143
+ blob = bytearray(cipher.encrypt_bytes(b"secret data"))
144
+ blob[20] ^= 0x01
145
+ with self.assertRaises(NevaError):
146
+ cipher.decrypt_bytes(bytes(blob))
147
+
148
+ @unittest.skipUnless(NATIVE_AVAILABLE, "neva256-native not installed")
149
+ def test_native_resilient_mode(self):
150
+ cipher = NevaCipher(KAT_KEY, KAT_NONCE, use_native=True)
151
+ data = os.urandom(4096)
152
+ blob = bytearray(cipher.encrypt_bytes(data))
153
+ blob[2048] ^= 0x40
154
+ out = cipher.decrypt_bytes(bytes(blob), verify=False)
155
+ damaged = sum(1 for i in range(len(data)) if out[i] != data[i])
156
+ self.assertLessEqual(damaged, 6)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ unittest.main()