vicrypt 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.
@@ -0,0 +1 @@
1
+ .wakatime-project
vicrypt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vincent Chen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
vicrypt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,77 @@
1
+ Metadata-Version: 2.5
2
+ Name: vicrypt
3
+ Version: 0.1.0
4
+ Summary: A Python cryptography toolset for CTF work
5
+ Project-URL: Homepage, https://github.com/vincentchen18/vicrypt
6
+ Project-URL: Repository, https://github.com/vincentchen18/vicrypt
7
+ Project-URL: Issues, https://github.com/vincentchen18/vicrypt/issues
8
+ Author: Vincent Chen
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: cipher,cryptography,ctf,encoding,security
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Security :: Cryptography
17
+ Requires-Python: >=3.9
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=7.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.4; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # ViCrypt
24
+
25
+ ## Overview
26
+
27
+ ViCrypt is a command line cryptography tool (currently a WIP) with basic encoding/decoding functionality! Featuring hex encode/decode, base64 encode/decode, base32 encode/decode, base85 encode/decode, URL encode/decode, binary encode/decode, atbash, ROT13, ROT47, ROT brute-force (all rotations), custom-offset rotation, affine encode/decode, affine brute-force (function exists but not wired to CLI), repeating-key XOR, single-byte XOR brute-force (in magic only), text and decimal codepoints, and the magic-wand auto-analyser (multi-layer beam search that auto-detects and decodes, scoring by printable ratio + common words + flag regex)
28
+
29
+ This project was inspired by [gchq](https://github.com/gchq/)'s [Cyberchef](https://github.com/gchq/CyberChef), although no code was taken from it.
30
+
31
+ ## Installation
32
+
33
+ ### Requirements:
34
+ Python >= 3.9
35
+
36
+ In your terminal, run
37
+
38
+ `pip install vicrypt`
39
+
40
+ and you're good to go!
41
+
42
+
43
+ ## Usage
44
+
45
+ All commands take the form `vicrypt <command> <text> [options]`.
46
+
47
+ | Command | Description | Example |
48
+ |---------|-------------|---------|
49
+ | `hex` | Hex encode / decode | `vicrypt hex <text>` |
50
+ | `b64` | Base64 encode / decode | `vicrypt b64 <text>` |
51
+ | `b32` | Base32 encode / decode | `vicrypt b32 <text>` |
52
+ | `b85` | Base85 encode / decode | `vicrypt b85 <text>` |
53
+ | `url` | URL encode / decode | `vicrypt url <text>` |
54
+ | `binary` | Binary encode / decode | `vicrypt binary <text>` |
55
+ | `atbash` | Atbash cipher (self-inverse) | `vicrypt atbash <text>` |
56
+ | `rot` | ROT13 / ROT47, with brute-force | `vicrypt rot <text>` |
57
+ | `affine` | Affine cipher | `vicrypt affine <text> -a 5 -b 8` |
58
+ | `xor` | Repeating-key XOR | `vicrypt xor <text> --key <key>` |
59
+ | `decimal` | Text to/from decimal codepoints | `vicrypt decimal <text>` |
60
+ | `analyse` | Auto-detect and decode (magic wand) | `vicrypt analyse <text>` |
61
+
62
+ ### Flags
63
+
64
+ | Flag | Applies to | Meaning |
65
+ |------|-------------|---------|
66
+ | `-d`, `--decode` | codecs, `affine`, `decimal` | decode instead of encode |
67
+ | `--variant` | `rot` | rot13 (default) or rot47 |
68
+ | `-n` | `rot` | rotation offset |
69
+ | `--brute` | `rot` | print all rotations |
70
+ | `-a`, `-b` | `affine` | cipher coefficients (required) |
71
+ | `--key` | `xor` | XOR key (required) |
72
+ | `-d`, `--depth` | `analyse` | max decode layers (default 3) |
73
+ | `-b`, `--beam` | `analyse` | branches kept per layer (default 6) |
74
+
75
+ ## AI Use
76
+
77
+ AI was used for help on the pyproject.toml as well as the argparse module, however all encodings and encryptions were researched and coded and implemented by me.
@@ -0,0 +1,55 @@
1
+ # ViCrypt
2
+
3
+ ## Overview
4
+
5
+ ViCrypt is a command line cryptography tool (currently a WIP) with basic encoding/decoding functionality! Featuring hex encode/decode, base64 encode/decode, base32 encode/decode, base85 encode/decode, URL encode/decode, binary encode/decode, atbash, ROT13, ROT47, ROT brute-force (all rotations), custom-offset rotation, affine encode/decode, affine brute-force (function exists but not wired to CLI), repeating-key XOR, single-byte XOR brute-force (in magic only), text and decimal codepoints, and the magic-wand auto-analyser (multi-layer beam search that auto-detects and decodes, scoring by printable ratio + common words + flag regex)
6
+
7
+ This project was inspired by [gchq](https://github.com/gchq/)'s [Cyberchef](https://github.com/gchq/CyberChef), although no code was taken from it.
8
+
9
+ ## Installation
10
+
11
+ ### Requirements:
12
+ Python >= 3.9
13
+
14
+ In your terminal, run
15
+
16
+ `pip install vicrypt`
17
+
18
+ and you're good to go!
19
+
20
+
21
+ ## Usage
22
+
23
+ All commands take the form `vicrypt <command> <text> [options]`.
24
+
25
+ | Command | Description | Example |
26
+ |---------|-------------|---------|
27
+ | `hex` | Hex encode / decode | `vicrypt hex <text>` |
28
+ | `b64` | Base64 encode / decode | `vicrypt b64 <text>` |
29
+ | `b32` | Base32 encode / decode | `vicrypt b32 <text>` |
30
+ | `b85` | Base85 encode / decode | `vicrypt b85 <text>` |
31
+ | `url` | URL encode / decode | `vicrypt url <text>` |
32
+ | `binary` | Binary encode / decode | `vicrypt binary <text>` |
33
+ | `atbash` | Atbash cipher (self-inverse) | `vicrypt atbash <text>` |
34
+ | `rot` | ROT13 / ROT47, with brute-force | `vicrypt rot <text>` |
35
+ | `affine` | Affine cipher | `vicrypt affine <text> -a 5 -b 8` |
36
+ | `xor` | Repeating-key XOR | `vicrypt xor <text> --key <key>` |
37
+ | `decimal` | Text to/from decimal codepoints | `vicrypt decimal <text>` |
38
+ | `analyse` | Auto-detect and decode (magic wand) | `vicrypt analyse <text>` |
39
+
40
+ ### Flags
41
+
42
+ | Flag | Applies to | Meaning |
43
+ |------|-------------|---------|
44
+ | `-d`, `--decode` | codecs, `affine`, `decimal` | decode instead of encode |
45
+ | `--variant` | `rot` | rot13 (default) or rot47 |
46
+ | `-n` | `rot` | rotation offset |
47
+ | `--brute` | `rot` | print all rotations |
48
+ | `-a`, `-b` | `affine` | cipher coefficients (required) |
49
+ | `--key` | `xor` | XOR key (required) |
50
+ | `-d`, `--depth` | `analyse` | max decode layers (default 3) |
51
+ | `-b`, `--beam` | `analyse` | branches kept per layer (default 6) |
52
+
53
+ ## AI Use
54
+
55
+ AI was used for help on the pyproject.toml as well as the argparse module, however all encodings and encryptions were researched and coded and implemented by me.
vicrypt-0.1.0/main.py ADDED
@@ -0,0 +1,388 @@
1
+ import re
2
+ def hex_to_text(text, delimiter=""):
3
+ try:
4
+ if delimiter:
5
+ text = text.replace(delimiter, "")
6
+ text = re.sub(r"(?i)0x|\\x|[^0-9a-f]", "", text)
7
+ if "0x" == text[:2]:
8
+ return bytes.fromhex(text[2:]).decode("utf-8")
9
+ else:
10
+ return bytes.fromhex(text).decode("utf-8")
11
+ except Exception:
12
+ return False
13
+
14
+ def text_to_hex(text):
15
+ try:
16
+ return text.encode("utf-8").hex()
17
+ except Exception:
18
+ return False
19
+
20
+ import base64
21
+ def text_to_base64(text):
22
+ try:
23
+ return base64.b64encode(text.encode("utf-8")).decode("utf-8")
24
+ except Exception:
25
+ return False
26
+
27
+ def base64_to_text(text):
28
+ try:
29
+ return base64.b64decode(text.encode("utf-8")).decode("utf-8")
30
+ except Exception:
31
+ return False
32
+
33
+ def text_to_base32(text):
34
+ try:
35
+ return base64.b32encode(text.encode("utf-8")).decode("utf-8")
36
+ except Exception:
37
+ return False
38
+
39
+ def base32_to_text(text):
40
+ try:
41
+ return base64.b32decode(text.encode("utf-8")).decode("utf-8")
42
+ except Exception:
43
+ return False
44
+
45
+ def text_to_base85(text):
46
+ try:
47
+ return base64.b85encode(text.encode("utf-8")).decode("utf-8")
48
+ except Exception:
49
+ return False
50
+
51
+ def base85_to_text(text):
52
+ try:
53
+ return base64.b85decode(text.encode("utf-8")).decode("utf-8")
54
+ except Exception:
55
+ return False
56
+
57
+ from urllib.parse import quote, unquote
58
+ def text_to_url(text):
59
+ try:
60
+ return quote(text.encode("utf-8"))
61
+ except Exception:
62
+ return False
63
+
64
+ def url_to_text(url):
65
+ try:
66
+ return unquote(url)
67
+ except Exception:
68
+ return False
69
+
70
+ letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
71
+ def rot13(text, rot=13, bruteforce=False, rotate_numbers=False):
72
+ if bruteforce:
73
+ result = []
74
+ for i in range(26):
75
+ try:
76
+ result.append(rot13(text, rot=i+1, rotate_numbers=rotate_numbers))
77
+ except Exception:
78
+ return False
79
+ return result
80
+ else:
81
+ try:
82
+ global letters
83
+ ans = ""
84
+ for char in text:
85
+ if char in letters:
86
+ ind = letters[(ord(char.upper())+13+rot)%26]
87
+ if char.isupper():
88
+ ans += ind.upper()
89
+ else:
90
+ ans += ind.lower()
91
+ elif rotate_numbers and char.isdigit():
92
+ ans += str((int(char) + rot)%10)
93
+ else:
94
+ ans += char
95
+ return ans
96
+ except Exception:
97
+ return False
98
+
99
+ def xor(text, key):
100
+ ct = ""
101
+ for i in range(len(text)):
102
+ ct += chr(ord(text[i]) ^ ord(key[i % len(key)]))
103
+ return ct
104
+
105
+ def rot47(text, rot=47, bruteforce=False):
106
+ if bruteforce:
107
+ result = []
108
+ for i in range(94):
109
+ try:
110
+ result.append(rot47(text, rot=i))
111
+ except Exception:
112
+ return False
113
+ return result
114
+ try:
115
+ ans = ""
116
+ for char in text:
117
+ if 33 <= ord(char) <= 126:
118
+ ans += chr((ord(char) - 33 + rot) % 94 + 33)
119
+ else:
120
+ ans += char
121
+ return ans
122
+ except Exception:
123
+ return False
124
+
125
+ def affine_encode(text, a, b):
126
+ global letters
127
+ ret = ""
128
+ for char in text:
129
+ if char.lower() in letters:
130
+ if char.isupper():
131
+ ret += letters[((ord(char.upper()) - 65) * a + b)%26].upper()
132
+ else:
133
+ ret += letters[((ord(char.upper()) - 65) * a + b)%26].lower()
134
+ else:
135
+ ret += char
136
+ return ret
137
+
138
+ def atbash(text):
139
+ return affine_encode(text, a=25, b=25)
140
+
141
+ def affine_decode(text, a, b):
142
+ global letters
143
+ if a not in [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]:
144
+ return False
145
+ invmod = pow(a, -1, 26)
146
+ ret = ""
147
+ for char in text:
148
+ if char.lower() in letters:
149
+ if char.isupper():
150
+ ret += letters[((ord(char.upper())-65-b)%26 * invmod)%26].upper()
151
+ else:
152
+ ret += letters[((ord(char.upper())-65-b)%26 * invmod)%26].lower()
153
+ else:
154
+ ret += char
155
+ return ret
156
+
157
+ def affine_bruteforce(text, ciphertext):
158
+ for a in [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]:
159
+ for b in range(26):
160
+ if affine_encode(text, a, b) == ciphertext:
161
+ return a, b
162
+ return False
163
+
164
+ def decimal_to_text(decimallist):
165
+ ret = "".join([chr(decimallist[i]) for i in range(len(decimallist))])
166
+ return ret
167
+
168
+ def text_to_decimal(text):
169
+ ret = [ord(char) for char in text]
170
+ return ret
171
+
172
+ def binary_to_text(binarylist):
173
+ try:
174
+ ret = ""
175
+ for binary in list(binarylist.split()):
176
+ decimal = int(binary, 2)
177
+ ret += chr(decimal)
178
+ return ret
179
+ except Exception:
180
+ return False
181
+
182
+ def text_to_binary(text):
183
+ try:
184
+ ret = []
185
+ for char in text:
186
+ decimal = ord(char)
187
+ if decimal > 255:
188
+ return False
189
+ binary = format(ord(char), "08b")
190
+ ret.append(binary)
191
+ return " ".join(ret)
192
+ except Exception:
193
+ return False
194
+
195
+ import argparse
196
+ import sys
197
+
198
+ codecs = {
199
+ "hex": (text_to_hex, hex_to_text),
200
+ "b64": (text_to_base64, base64_to_text),
201
+ "b32": (text_to_base32, base32_to_text),
202
+ "b85": (text_to_base85, base85_to_text),
203
+ "url": (text_to_url, url_to_text),
204
+ "binary": (text_to_binary, binary_to_text),
205
+ "atbash": (atbash, atbash),
206
+ }
207
+
208
+ common_words = {"the", "and", "flag", "for", "you", "that", "this",
209
+ "with", "ctf", "key", "are", "was", "have", "not"}
210
+
211
+ flag_regex = re.compile(r"[a-zA-Z0-9_]{2,}\{[^}]{2,}\}")
212
+
213
+ def score(s):
214
+ global flag_regex, common_words
215
+ if not s or not isinstance(s, str):
216
+ return 0.0
217
+ printable = sum(1 for c in s if 32 <= ord(c) < 127 or c in "\n\t\r")
218
+ scoar = printable / len(s)
219
+ low = s.lower()
220
+ scoar += 0.2 * sum(w in low for w in common_words)
221
+ if flag_regex.search(s):
222
+ scoar += 3.0
223
+ return scoar
224
+
225
+ def one(fn, label):
226
+ def run(t):
227
+ out = fn(t)
228
+ return [(label, out)] if out and isinstance(out, str) else []
229
+ return run
230
+
231
+ def rot13_all(t):
232
+ res = rot13(t, bruteforce=True)
233
+ return [(f"rot13#{i+1}", v) for i, v in enumerate(res)] if res else []
234
+
235
+ def rot47_all(t):
236
+ res = rot47(t, bruteforce=True)
237
+ return [(f"rot47#{i}", v) for i, v in enumerate(res)] if res else []
238
+
239
+ def affine_all(t):
240
+ out = []
241
+ for a in [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]:
242
+ for b in range(26):
243
+ v = affine_decode(t, a, b)
244
+ if v:
245
+ out.append((f"affine:a{a}b{b}", v))
246
+ return out
247
+
248
+ def xor1(t):
249
+ out = []
250
+ for k in range(1, 256):
251
+ try:
252
+ out.append((f"xor:0x{k:02x}", xor(t, chr(k))))
253
+ except Exception:
254
+ pass
255
+ return out
256
+
257
+ cheap_search = [
258
+ one(hex_to_text, "hex"),
259
+ one(base64_to_text, "b64"),
260
+ one(base32_to_text, "b32"),
261
+ one(base85_to_text, "b85"),
262
+ one(binary_to_text, "binary"),
263
+ one(atbash, "atbash"),
264
+ rot13_all,
265
+ ]
266
+ expensive_search = [affine_all, rot47_all, xor1]
267
+
268
+ def magic(data, max_depth=3, beam=6, top=10):
269
+ results = []
270
+ frontier = [(data, [])]
271
+ seen = {data}
272
+ for depth in range(max_depth):
273
+ decoders = cheap_search + (expensive_search if depth == 0 else [])
274
+ scored = []
275
+ for text, recipe in frontier:
276
+ for dec in decoders:
277
+ for label, out in dec(text):
278
+ if not out or not isinstance(out, str) or out in seen:
279
+ continue
280
+ seen.add(out)
281
+ sc = score(out)
282
+ step = recipe + [label]
283
+ results.append((sc, step, out))
284
+ scored.append((sc, out, step))
285
+ scored.sort(key=lambda x: x[0], reverse=True)
286
+ frontier = [(out, step) for _, out, step in scored[:beam]]
287
+ if not frontier:
288
+ break
289
+ results.sort(key=lambda x: x[0], reverse=True)
290
+ return results[:top]
291
+
292
+ def do_codec(args):
293
+ enc, dec = codecs[args.op]
294
+ fn = dec if args.decode else enc
295
+ out = fn(args.text)
296
+ if out is False:
297
+ mode = "decode" if args.decode else "encode"
298
+ print(f"error: {args.op} {mode} failed", file=sys.stderr)
299
+ sys.exit(1)
300
+ print(out)
301
+
302
+ def do_rot(args):
303
+ fn = rot47 if args.variant == "rot47" else rot13
304
+ if args.brute:
305
+ res = fn(args.text, bruteforce=True)
306
+ if not res:
307
+ sys.exit(1)
308
+ for i, v in enumerate(res):
309
+ print(f"{i}: {v}")
310
+ else:
311
+ kwargs = {"rot": args.n} if args.n is not None else {}
312
+ print(fn(args.text, **kwargs))
313
+
314
+ def do_affine(args):
315
+ fn = affine_decode if args.decode else affine_encode
316
+ out = fn(args.text, args.a, args.b)
317
+ if out is False:
318
+ print("error: invalid a (must be coprime with 26)", file=sys.stderr)
319
+ sys.exit(1)
320
+ print(out)
321
+
322
+ def do_xor(args):
323
+ print(xor(args.text, args.key))
324
+
325
+ def do_decimal(args):
326
+ if args.decode:
327
+ nums = [int(x) for x in args.text.split()]
328
+ print(decimal_to_text(nums))
329
+ else:
330
+ print(" ".join(str(n) for n in text_to_decimal(args.text)))
331
+
332
+ def do_analyse(args):
333
+ hits = magic(args.text, max_depth=args.depth, beam=args.beam)
334
+ if not hits:
335
+ print("no candidates found", file=sys.stderr)
336
+ sys.exit(1)
337
+ for sc, recipe, out in hits:
338
+ preview = out if len(out) <= 80 else out[:77] + "..."
339
+ print(f"[{sc:5.2f}] {' -> '.join(recipe):<28} {preview!r}")
340
+
341
+ def build_parser():
342
+ p = argparse.ArgumentParser(prog="vicrypt", description="Crypto/encoding toolset for CTFs")
343
+ sub = p.add_subparsers(dest="cmd", required=True)
344
+
345
+ for name in codecs:
346
+ c = sub.add_parser(name, help=f"{name} encode/decode")
347
+ c.add_argument("text")
348
+ c.add_argument("-d", "--decode", action="store_true")
349
+ c.set_defaults(func=do_codec, op=name)
350
+
351
+ ro = sub.add_parser("rot", help="rot13/rot47")
352
+ ro.add_argument("text")
353
+ ro.add_argument("--variant", choices=["rot13", "rot47"], default="rot13")
354
+ ro.add_argument("-n", type=int, default=None)
355
+ ro.add_argument("--brute", action="store_true")
356
+ ro.set_defaults(func=do_rot)
357
+
358
+ af = sub.add_parser("affine", help="affine cipher")
359
+ af.add_argument("text")
360
+ af.add_argument("-a", type=int, required=True)
361
+ af.add_argument("-b", type=int, required=True)
362
+ af.add_argument("-d", "--decode", action="store_true")
363
+ af.set_defaults(func=do_affine)
364
+
365
+ x = sub.add_parser("xor", help="repeating-key xor")
366
+ x.add_argument("text")
367
+ x.add_argument("--key", required=True)
368
+ x.set_defaults(func=do_xor)
369
+
370
+ de = sub.add_parser("decimal", help="text <-> decimal codepoints")
371
+ de.add_argument("text")
372
+ de.add_argument("-d", "--decode", action="store_true")
373
+ de.set_defaults(func=do_decimal)
374
+
375
+ an = sub.add_parser("analyse", aliases=["magic"], help="auto-detect & decode")
376
+ an.add_argument("text")
377
+ an.add_argument("-d", "--depth", type=int, default=3)
378
+ an.add_argument("-b", "--beam", type=int, default=6)
379
+ an.set_defaults(func=do_analyse)
380
+
381
+ return p
382
+
383
+ def main():
384
+ args = build_parser().parse_args()
385
+ args.func(args)
386
+
387
+ if __name__ == "__main__":
388
+ main()
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "vicrypt"
7
+ version = "0.1.0"
8
+ description = "A Python cryptography toolset for CTF work"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "Vincent Chen" }]
13
+ keywords = ["cryptography", "ctf", "cipher", "encoding", "security"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Security :: Cryptography",
20
+ ]
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["pytest>=7.0", "ruff>=0.4"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://github.com/vincentchen18/vicrypt"
28
+ Repository = "https://github.com/vincentchen18/vicrypt"
29
+ Issues = "https://github.com/vincentchen18/vicrypt/issues"
30
+
31
+ [project.scripts]
32
+ vicrypt = "main:main"
33
+
34
+ [tool.hatch.build.targets.wheel]
35
+ only-include = ["main.py"]
36
+
37
+ [tool.hatch.build.targets.wheel.sources]
38
+ "main.py" = "vicrypt.py"
39
+
40
+ [tool.ruff]
41
+ line-length = 100
42
+ target-version = "py39"
43
+
44
+ [tool.ruff.lint]
45
+ select = ["E", "F", "I", "UP", "B"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]