TimeRelease 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- timerelease-1.0.0/LICENSE +21 -0
- timerelease-1.0.0/MANIFEST.in +2 -0
- timerelease-1.0.0/PKG-INFO +165 -0
- timerelease-1.0.0/README.md +111 -0
- timerelease-1.0.0/TimeRelease/__init__.py +5 -0
- timerelease-1.0.0/TimeRelease/__main__.py +104 -0
- timerelease-1.0.0/TimeRelease/b64utils.py +11 -0
- timerelease-1.0.0/TimeRelease/bench.py +57 -0
- timerelease-1.0.0/TimeRelease/decrypt.py +56 -0
- timerelease-1.0.0/TimeRelease/encrypt.py +61 -0
- timerelease-1.0.0/TimeRelease.egg-info/PKG-INFO +165 -0
- timerelease-1.0.0/TimeRelease.egg-info/SOURCES.txt +17 -0
- timerelease-1.0.0/TimeRelease.egg-info/dependency_links.txt +1 -0
- timerelease-1.0.0/TimeRelease.egg-info/entry_points.txt +2 -0
- timerelease-1.0.0/TimeRelease.egg-info/requires.txt +12 -0
- timerelease-1.0.0/TimeRelease.egg-info/top_level.txt +1 -0
- timerelease-1.0.0/pyproject.toml +62 -0
- timerelease-1.0.0/setup.cfg +4 -0
- timerelease-1.0.0/tests/test_encrypt_decrypt.py +12 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Leo Pagano
|
|
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.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TimeRelease
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python utility to cryptographically lock secrets for a preset amount of time.
|
|
5
|
+
Author-email: Leo Pagano <leo@leoapagano.com>
|
|
6
|
+
Maintainer-email: Leo Pagano <leo@leoapagano.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Leo Pagano
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://github.com/leoapagano/TimeRelease
|
|
29
|
+
Project-URL: Documentation, https://github.com/leoapagano/TimeRelease/blob/main/README.md
|
|
30
|
+
Keywords: TimeRelease,Time,Release,Encryption,Crypto,Cryptography,Pagano
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
32
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
36
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
37
|
+
Classifier: Operating System :: MacOS
|
|
38
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
39
|
+
Requires-Python: >=3.13
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: mpmath~=1.0
|
|
43
|
+
Requires-Dist: pycryptodome~=3.0
|
|
44
|
+
Requires-Dist: sympy~=1.0
|
|
45
|
+
Requires-Dist: tqdm~=4.0
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: build==1.4.0; extra == "dev"
|
|
48
|
+
Requires-Dist: pre-commit==4.5.1; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
50
|
+
Requires-Dist: ruff==0.15.5; extra == "dev"
|
|
51
|
+
Requires-Dist: twine==6.2.0; extra == "dev"
|
|
52
|
+
Requires-Dist: ty==0.0.21; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# TimeRelease
|
|
56
|
+
|
|
57
|
+
## Introduction
|
|
58
|
+
|
|
59
|
+
A simple Python utility to lock (or encrypt) secrets, such that they require a preset amount of compute to unlock (or decrypt) them later.
|
|
60
|
+
|
|
61
|
+
Since each cryptographic step requires the last, it (by design) cannot be parallelized.
|
|
62
|
+
|
|
63
|
+
This could potentially be useful if you want to wait, for example, an hour, before being able to access a password; as a form of stolen device protection.
|
|
64
|
+
|
|
65
|
+
## WARNING!
|
|
66
|
+
|
|
67
|
+
Always keep your "encrypted" file secret, treating it as if it were a password. It is effectively unencrypted as it can be used to decrypt the secret (if such an attacker has sufficient time to complete the puzzle) without any other knowledge of the secret or the individual who encrypted it.
|
|
68
|
+
|
|
69
|
+
## Table of Contents
|
|
70
|
+
|
|
71
|
+
- [Interactive Usage](#interactive-usage)
|
|
72
|
+
- [Setup](#setup)
|
|
73
|
+
- [Encryption](#encryption)
|
|
74
|
+
- [Decryption](#decryption)
|
|
75
|
+
- [Module API](#module-api)
|
|
76
|
+
- [Development Usage](#development-usage)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Interactive Usage
|
|
81
|
+
|
|
82
|
+
### Setup
|
|
83
|
+
|
|
84
|
+
Install the package and its dependencies in a venv:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python3 -m venv ./venv
|
|
88
|
+
source ./venv/bin/activate
|
|
89
|
+
pip install TimeRelease
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Encryption
|
|
93
|
+
|
|
94
|
+
To encrypt a file `somefile.txt` -> `somefile.txt.timerelease`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Specify # of seconds it should take to unlock on your machine
|
|
98
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --time 3600
|
|
99
|
+
# Or, specify raw # of iterations needed to unlock the file
|
|
100
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --iters 1073741824
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can use this to determine how long (on your hardware) the decryption process will take. Encryption should be nearly instant for most settings, even for comically large numbers of iterations.
|
|
104
|
+
|
|
105
|
+
### Decryption
|
|
106
|
+
|
|
107
|
+
To decrypt a file `somefile.txt.timerelease` -> `somefile.txt`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
timerelease --decrypt somefile.txt.timerelease somefile.txt
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
It will take however long the person who encrypted the file dictates. The only way to speed it up is to find a CPU with really, really fast single-core performance.
|
|
114
|
+
|
|
115
|
+
## Module API
|
|
116
|
+
|
|
117
|
+
Like any module, TimeRelease can be installed via pip (`pip install TimeRelease`) and imported (`import TimeRelease`). The API for the TimeRelease module is as follows:
|
|
118
|
+
|
|
119
|
+
#### `TimeRelease.decrypt_secret(enc_package)`
|
|
120
|
+
|
|
121
|
+
* Given a dictionary which was generated by encrypt_secret(), decrypt_secret() decrypts it and returns the secret as a byte string.
|
|
122
|
+
* If `logging` (defaults to True) is set to True, a progress bar is printed, along with extra debug information.
|
|
123
|
+
|
|
124
|
+
#### `TimeRelease.encrypt_secret()`
|
|
125
|
+
|
|
126
|
+
* Given a byte string `secret`, encrypt_secret() applies `iterations` iterations to it, "encrypting" it.
|
|
127
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
128
|
+
|
|
129
|
+
#### `TimeRelease.run_single_benchmark()`
|
|
130
|
+
|
|
131
|
+
* Determines how long it takes for the CPU to run a fixed number of `iterations`.
|
|
132
|
+
* Amount of time elapsed is returned in seconds as a float.
|
|
133
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
134
|
+
|
|
135
|
+
#### `TimeRelease.run_benchmark()`
|
|
136
|
+
|
|
137
|
+
* Determines how many iterations a single core of the user's CPU is capable of processing, on average, per second (as an integer).
|
|
138
|
+
* `benches` (defaults to 10) describes the number of times the benchmark will be run.
|
|
139
|
+
* Higher values of `benches` = greater certainty in running time, but more time is spent benchmarking.
|
|
140
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
141
|
+
|
|
142
|
+
## Development Usage
|
|
143
|
+
|
|
144
|
+
To install development versions of this package, clone this repo and `cd` into it, then run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python3 -m venv ./venv
|
|
148
|
+
source ./venv/bin/activate
|
|
149
|
+
pip install -e .[dev]
|
|
150
|
+
pre-commit install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
To test the codebase, simply run `pytest`. To apply proper formatting to the codebase, run `ruff format .`. To ensure that the code has no other issues with PEP8 compliance, run `ruff check`. And to ensure that there are no type errors, please run `ty check TimeRelease`. Please do all of these things before you commit, and not after!
|
|
154
|
+
|
|
155
|
+
To build a distribution package, first update the version number in `pyproject.toml`, and make a tag for that release version at the current commit. Then:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python3 -m build
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
To upload this build to PyPI, run:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python3 -m twine upload dist/* --skip-existing
|
|
165
|
+
```
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# TimeRelease
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
A simple Python utility to lock (or encrypt) secrets, such that they require a preset amount of compute to unlock (or decrypt) them later.
|
|
6
|
+
|
|
7
|
+
Since each cryptographic step requires the last, it (by design) cannot be parallelized.
|
|
8
|
+
|
|
9
|
+
This could potentially be useful if you want to wait, for example, an hour, before being able to access a password; as a form of stolen device protection.
|
|
10
|
+
|
|
11
|
+
## WARNING!
|
|
12
|
+
|
|
13
|
+
Always keep your "encrypted" file secret, treating it as if it were a password. It is effectively unencrypted as it can be used to decrypt the secret (if such an attacker has sufficient time to complete the puzzle) without any other knowledge of the secret or the individual who encrypted it.
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [Interactive Usage](#interactive-usage)
|
|
18
|
+
- [Setup](#setup)
|
|
19
|
+
- [Encryption](#encryption)
|
|
20
|
+
- [Decryption](#decryption)
|
|
21
|
+
- [Module API](#module-api)
|
|
22
|
+
- [Development Usage](#development-usage)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Interactive Usage
|
|
27
|
+
|
|
28
|
+
### Setup
|
|
29
|
+
|
|
30
|
+
Install the package and its dependencies in a venv:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python3 -m venv ./venv
|
|
34
|
+
source ./venv/bin/activate
|
|
35
|
+
pip install TimeRelease
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Encryption
|
|
39
|
+
|
|
40
|
+
To encrypt a file `somefile.txt` -> `somefile.txt.timerelease`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Specify # of seconds it should take to unlock on your machine
|
|
44
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --time 3600
|
|
45
|
+
# Or, specify raw # of iterations needed to unlock the file
|
|
46
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --iters 1073741824
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
You can use this to determine how long (on your hardware) the decryption process will take. Encryption should be nearly instant for most settings, even for comically large numbers of iterations.
|
|
50
|
+
|
|
51
|
+
### Decryption
|
|
52
|
+
|
|
53
|
+
To decrypt a file `somefile.txt.timerelease` -> `somefile.txt`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
timerelease --decrypt somefile.txt.timerelease somefile.txt
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
It will take however long the person who encrypted the file dictates. The only way to speed it up is to find a CPU with really, really fast single-core performance.
|
|
60
|
+
|
|
61
|
+
## Module API
|
|
62
|
+
|
|
63
|
+
Like any module, TimeRelease can be installed via pip (`pip install TimeRelease`) and imported (`import TimeRelease`). The API for the TimeRelease module is as follows:
|
|
64
|
+
|
|
65
|
+
#### `TimeRelease.decrypt_secret(enc_package)`
|
|
66
|
+
|
|
67
|
+
* Given a dictionary which was generated by encrypt_secret(), decrypt_secret() decrypts it and returns the secret as a byte string.
|
|
68
|
+
* If `logging` (defaults to True) is set to True, a progress bar is printed, along with extra debug information.
|
|
69
|
+
|
|
70
|
+
#### `TimeRelease.encrypt_secret()`
|
|
71
|
+
|
|
72
|
+
* Given a byte string `secret`, encrypt_secret() applies `iterations` iterations to it, "encrypting" it.
|
|
73
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
74
|
+
|
|
75
|
+
#### `TimeRelease.run_single_benchmark()`
|
|
76
|
+
|
|
77
|
+
* Determines how long it takes for the CPU to run a fixed number of `iterations`.
|
|
78
|
+
* Amount of time elapsed is returned in seconds as a float.
|
|
79
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
80
|
+
|
|
81
|
+
#### `TimeRelease.run_benchmark()`
|
|
82
|
+
|
|
83
|
+
* Determines how many iterations a single core of the user's CPU is capable of processing, on average, per second (as an integer).
|
|
84
|
+
* `benches` (defaults to 10) describes the number of times the benchmark will be run.
|
|
85
|
+
* Higher values of `benches` = greater certainty in running time, but more time is spent benchmarking.
|
|
86
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
87
|
+
|
|
88
|
+
## Development Usage
|
|
89
|
+
|
|
90
|
+
To install development versions of this package, clone this repo and `cd` into it, then run:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python3 -m venv ./venv
|
|
94
|
+
source ./venv/bin/activate
|
|
95
|
+
pip install -e .[dev]
|
|
96
|
+
pre-commit install
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To test the codebase, simply run `pytest`. To apply proper formatting to the codebase, run `ruff format .`. To ensure that the code has no other issues with PEP8 compliance, run `ruff check`. And to ensure that there are no type errors, please run `ty check TimeRelease`. Please do all of these things before you commit, and not after!
|
|
100
|
+
|
|
101
|
+
To build a distribution package, first update the version number in `pyproject.toml`, and make a tag for that release version at the current commit. Then:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python3 -m build
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
To upload this build to PyPI, run:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python3 -m twine upload dist/* --skip-existing
|
|
111
|
+
```
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from .bench import run_benchmark
|
|
6
|
+
from .decrypt import decrypt_secret
|
|
7
|
+
from .encrypt import encrypt_secret
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main(argv: list[str] | None = None) -> int | None:
|
|
11
|
+
# Read arguments
|
|
12
|
+
parser = argparse.ArgumentParser(prog="TimeRelease")
|
|
13
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
14
|
+
group.add_argument(
|
|
15
|
+
"--encrypt",
|
|
16
|
+
nargs=2,
|
|
17
|
+
metavar=("INFILE", "OUTFILE"),
|
|
18
|
+
help="Encrypt input file to output file path",
|
|
19
|
+
)
|
|
20
|
+
group.add_argument(
|
|
21
|
+
"--decrypt",
|
|
22
|
+
nargs=2,
|
|
23
|
+
metavar=("INFILE", "OUTFILE"),
|
|
24
|
+
help="Decrypt input file to output file path",
|
|
25
|
+
)
|
|
26
|
+
iters_group = parser.add_mutually_exclusive_group()
|
|
27
|
+
iters_group.add_argument(
|
|
28
|
+
"--iters",
|
|
29
|
+
type=int,
|
|
30
|
+
metavar="N",
|
|
31
|
+
help="Number of iterations for encryption (required with --encrypt)",
|
|
32
|
+
)
|
|
33
|
+
iters_group.add_argument(
|
|
34
|
+
"--time",
|
|
35
|
+
type=float,
|
|
36
|
+
metavar="SECONDS",
|
|
37
|
+
help="Target decryption time in seconds; benchmarks your CPU to compute iterations (required with --encrypt)",
|
|
38
|
+
)
|
|
39
|
+
args = parser.parse_args(argv)
|
|
40
|
+
|
|
41
|
+
# Validate --iters/--time usage
|
|
42
|
+
if args.encrypt and args.iters is None and args.time is None:
|
|
43
|
+
parser.error("--encrypt requires either --iters or --time")
|
|
44
|
+
if (args.iters is not None or args.time is not None) and not args.encrypt:
|
|
45
|
+
parser.error("--iters and --time can only be used with --encrypt")
|
|
46
|
+
|
|
47
|
+
# Determine and validate infile and outfile
|
|
48
|
+
if args.encrypt:
|
|
49
|
+
infile, outfile = Path(args.encrypt[0]), Path(args.encrypt[1])
|
|
50
|
+
elif args.decrypt:
|
|
51
|
+
infile, outfile = Path(args.decrypt[0]), Path(args.decrypt[1])
|
|
52
|
+
if not infile.is_file():
|
|
53
|
+
print(f"{infile} does not exist or is not a file")
|
|
54
|
+
return -1
|
|
55
|
+
if outfile.is_file():
|
|
56
|
+
print(f"{outfile} will be overwritten! Proceed anyway? [y/n]")
|
|
57
|
+
if input(">>> ").strip().lower() != "y":
|
|
58
|
+
print("Exiting.")
|
|
59
|
+
return -1
|
|
60
|
+
if outfile.is_dir():
|
|
61
|
+
print(f"{outfile} cannot be overwritten: is a directory")
|
|
62
|
+
return -1
|
|
63
|
+
|
|
64
|
+
# ENCRYPTION
|
|
65
|
+
if args.encrypt:
|
|
66
|
+
# Determine # of iterations
|
|
67
|
+
if args.iters is not None:
|
|
68
|
+
iters = args.iters
|
|
69
|
+
else:
|
|
70
|
+
print("TimeRelease is benchmarking your CPU. This may take up to a minute.")
|
|
71
|
+
ips = run_benchmark(logging=False)
|
|
72
|
+
iters = int(args.time * ips)
|
|
73
|
+
|
|
74
|
+
print(f"Encrypting {infile} -> {outfile}:")
|
|
75
|
+
|
|
76
|
+
# Read infile
|
|
77
|
+
with open(infile, "rb") as f:
|
|
78
|
+
secret = f.read()
|
|
79
|
+
|
|
80
|
+
# Encrypt secret & get JSON package
|
|
81
|
+
enc_package = encrypt_secret(secret, iters)
|
|
82
|
+
|
|
83
|
+
# Write outfile
|
|
84
|
+
with open(outfile, "w") as f:
|
|
85
|
+
json.dump(enc_package, f, indent=4)
|
|
86
|
+
|
|
87
|
+
# DECRYPTION
|
|
88
|
+
elif args.decrypt:
|
|
89
|
+
print(f"Decrypting {infile} -> {outfile}:")
|
|
90
|
+
|
|
91
|
+
# Read infile
|
|
92
|
+
with open(infile, "r") as f:
|
|
93
|
+
enc_package = json.load(f)
|
|
94
|
+
|
|
95
|
+
# Decrypt
|
|
96
|
+
decrypted = decrypt_secret(enc_package)
|
|
97
|
+
|
|
98
|
+
# Write outfile
|
|
99
|
+
with open(outfile, "wb") as f:
|
|
100
|
+
f.write(decrypted)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def base64_to_byte_str(base64_string: str) -> bytes:
|
|
5
|
+
"""Converts a base64 string to its byte string representation."""
|
|
6
|
+
return base64.b64decode(base64_string.encode("ASCII"))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def byte_str_to_base64(byte_string: bytes | bytearray | memoryview) -> str:
|
|
10
|
+
"""Converts a byte string to its base64 (non-byte string) representation."""
|
|
11
|
+
return base64.b64encode(byte_string).decode("ASCII")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from sympy import nextprime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run_single_benchmark(iterations: int, logging: bool = True) -> float:
|
|
8
|
+
"""
|
|
9
|
+
Determines how long it takes for the CPU to run a fixed number of `iterations`.
|
|
10
|
+
Amount of time elapsed is returned in seconds as a float.
|
|
11
|
+
If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
12
|
+
"""
|
|
13
|
+
# Setup dummy puzzle parameters
|
|
14
|
+
if logging:
|
|
15
|
+
print("Preparing time lock...")
|
|
16
|
+
p = nextprime(random.getrandbits(512))
|
|
17
|
+
q = nextprime(random.getrandbits(512))
|
|
18
|
+
modulus = p * q
|
|
19
|
+
|
|
20
|
+
# Execute & time it
|
|
21
|
+
if logging:
|
|
22
|
+
print("Running benchmark...")
|
|
23
|
+
start_time = time.time()
|
|
24
|
+
r = random.randint(2, modulus - 1)
|
|
25
|
+
for _ in range(iterations):
|
|
26
|
+
r = pow(r, 2, modulus)
|
|
27
|
+
end_time = time.time()
|
|
28
|
+
|
|
29
|
+
return end_time - start_time
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_benchmark(benches: int = 10, logging: bool = True) -> int:
|
|
33
|
+
"""
|
|
34
|
+
Determines how many iterations a single core of the user's CPU is capable of processing, on average, per second (as an integer).
|
|
35
|
+
`benches` (defaults to 10) describes the number of times the benchmark will be run.
|
|
36
|
+
Higher values of `benches` = greater certainty in running time, but more time is spent benchmarking.
|
|
37
|
+
If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
38
|
+
"""
|
|
39
|
+
# Repeat single benchmarks, doubling iterations until the time per benchmark is less than 1 second
|
|
40
|
+
iterations = 1
|
|
41
|
+
execution_time = 0
|
|
42
|
+
while execution_time < 1:
|
|
43
|
+
iterations *= 2
|
|
44
|
+
if logging:
|
|
45
|
+
print(f"Running benchmark @ iterations={iterations}")
|
|
46
|
+
execution_time = run_single_benchmark(iterations, logging=logging)
|
|
47
|
+
|
|
48
|
+
# Run benches (default: 10) benchmarks with this number of iterations
|
|
49
|
+
results = []
|
|
50
|
+
for i in range(benches):
|
|
51
|
+
if logging:
|
|
52
|
+
print(f"Running standard benchmark {i + 1} / {benches}")
|
|
53
|
+
results.append(run_single_benchmark(iterations, logging=logging))
|
|
54
|
+
|
|
55
|
+
# Determine average iterations/sec and return
|
|
56
|
+
avg = sum(results) / benches
|
|
57
|
+
return int(iterations // avg)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from Crypto.Cipher import AES
|
|
6
|
+
from Crypto.Util.Padding import unpad
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
from .b64utils import base64_to_byte_str
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def decrypt_secret(enc_package: dict[str, Any], logging: bool = True) -> bytes:
|
|
13
|
+
"""
|
|
14
|
+
Given a dictionary which was generated by encrypt_secret(), decrypt_secret() decrypts it and returns the secret as a byte string.
|
|
15
|
+
If `logging` (defaults to True) is set to True, a progress bar is printed, along with extra debug information.
|
|
16
|
+
"""
|
|
17
|
+
base = enc_package["base"]
|
|
18
|
+
modulus = enc_package["modulus"]
|
|
19
|
+
iterations = enc_package["iterations"]
|
|
20
|
+
secret_iv = base64_to_byte_str(enc_package["secret_iv"])
|
|
21
|
+
encrypted_secret = base64_to_byte_str(enc_package["encrypted_secret"])
|
|
22
|
+
key_iv = base64_to_byte_str(enc_package["key_iv"])
|
|
23
|
+
encrypted_key = base64_to_byte_str(enc_package["encrypted_key"])
|
|
24
|
+
|
|
25
|
+
# Compute puzzle result r
|
|
26
|
+
if logging:
|
|
27
|
+
print("Unlocking encryption key...")
|
|
28
|
+
start_time = time.time()
|
|
29
|
+
r = base
|
|
30
|
+
if logging:
|
|
31
|
+
for _ in tqdm(range(iterations), desc="Unlocking", unit="iters"):
|
|
32
|
+
r = pow(r, 2, modulus)
|
|
33
|
+
else:
|
|
34
|
+
for _ in range(iterations):
|
|
35
|
+
r = pow(r, 2, modulus)
|
|
36
|
+
end_time = time.time()
|
|
37
|
+
if logging:
|
|
38
|
+
print(f"Completed in {end_time - start_time:.2f} seconds")
|
|
39
|
+
|
|
40
|
+
# Derive AES key from r
|
|
41
|
+
if logging:
|
|
42
|
+
print("Deriving AES key...")
|
|
43
|
+
derived_key = hashlib.sha256(str(r).encode()).digest()[:16]
|
|
44
|
+
|
|
45
|
+
# Decrypt AES key
|
|
46
|
+
if logging:
|
|
47
|
+
print("Decrypting AES key...")
|
|
48
|
+
cipher2 = AES.new(derived_key, AES.MODE_CBC, iv=key_iv)
|
|
49
|
+
aes_key = unpad(cipher2.decrypt(encrypted_key), AES.block_size)
|
|
50
|
+
|
|
51
|
+
# Decrypt secret
|
|
52
|
+
if logging:
|
|
53
|
+
print("Decrypting secret...")
|
|
54
|
+
cipher = AES.new(aes_key, AES.MODE_CBC, iv=secret_iv)
|
|
55
|
+
secret = unpad(cipher.decrypt(encrypted_secret), AES.block_size)
|
|
56
|
+
return secret
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import random
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from Crypto.Cipher import AES
|
|
6
|
+
from Crypto.Random import get_random_bytes
|
|
7
|
+
from Crypto.Util.Padding import pad
|
|
8
|
+
from sympy import nextprime
|
|
9
|
+
|
|
10
|
+
from .b64utils import byte_str_to_base64
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def encrypt_secret(
|
|
14
|
+
secret: bytes, iterations: int, logging: bool = True
|
|
15
|
+
) -> dict[str, Any]:
|
|
16
|
+
"""
|
|
17
|
+
Given a byte string `secret`, encrypt_secret() applies `iterations` iterations to it, "encrypting" it.
|
|
18
|
+
If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
19
|
+
"""
|
|
20
|
+
# Create new (symmetric) AES key and encrypt secret with it
|
|
21
|
+
if logging:
|
|
22
|
+
print("Generating encryption key...")
|
|
23
|
+
aes_key = get_random_bytes(16)
|
|
24
|
+
cipher = AES.new(aes_key, AES.MODE_CBC)
|
|
25
|
+
secret_iv = cipher.iv
|
|
26
|
+
encrypted_secret = cipher.encrypt(pad(secret, AES.block_size))
|
|
27
|
+
|
|
28
|
+
# Time-lock puzzle setup
|
|
29
|
+
# Generate N = p * q
|
|
30
|
+
if logging:
|
|
31
|
+
print("Preparing time lock...")
|
|
32
|
+
p = nextprime(random.getrandbits(512))
|
|
33
|
+
q = nextprime(random.getrandbits(512))
|
|
34
|
+
modulus = p * q
|
|
35
|
+
|
|
36
|
+
# Puzzle: compute r = base^(2^iterations) mod modulus
|
|
37
|
+
base = random.randint(2, modulus - 1)
|
|
38
|
+
r = pow(base, pow(2, iterations, (p - 1) * (q - 1)), modulus)
|
|
39
|
+
|
|
40
|
+
# Derive AES key from r (e.g. using SHA256)
|
|
41
|
+
if logging:
|
|
42
|
+
print("Deriving AES key from time lock...")
|
|
43
|
+
derived_key = hashlib.sha256(str(r).encode()).digest()[:16]
|
|
44
|
+
|
|
45
|
+
# Encrypt AES key with derived key
|
|
46
|
+
if logging:
|
|
47
|
+
print("Encrypting derived key...")
|
|
48
|
+
cipher2 = AES.new(derived_key, AES.MODE_CBC)
|
|
49
|
+
key_iv = cipher2.iv
|
|
50
|
+
encrypted_key = cipher2.encrypt(pad(aes_key, AES.block_size))
|
|
51
|
+
|
|
52
|
+
# Stash stuff needed to decrypt later
|
|
53
|
+
return {
|
|
54
|
+
"base": base,
|
|
55
|
+
"modulus": modulus,
|
|
56
|
+
"iterations": iterations,
|
|
57
|
+
"secret_iv": byte_str_to_base64(secret_iv),
|
|
58
|
+
"encrypted_secret": byte_str_to_base64(encrypted_secret),
|
|
59
|
+
"key_iv": byte_str_to_base64(key_iv),
|
|
60
|
+
"encrypted_key": byte_str_to_base64(encrypted_key),
|
|
61
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TimeRelease
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python utility to cryptographically lock secrets for a preset amount of time.
|
|
5
|
+
Author-email: Leo Pagano <leo@leoapagano.com>
|
|
6
|
+
Maintainer-email: Leo Pagano <leo@leoapagano.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Leo Pagano
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
Project-URL: Homepage, https://github.com/leoapagano/TimeRelease
|
|
29
|
+
Project-URL: Documentation, https://github.com/leoapagano/TimeRelease/blob/main/README.md
|
|
30
|
+
Keywords: TimeRelease,Time,Release,Encryption,Crypto,Cryptography,Pagano
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
32
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
36
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
37
|
+
Classifier: Operating System :: MacOS
|
|
38
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
39
|
+
Requires-Python: >=3.13
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
License-File: LICENSE
|
|
42
|
+
Requires-Dist: mpmath~=1.0
|
|
43
|
+
Requires-Dist: pycryptodome~=3.0
|
|
44
|
+
Requires-Dist: sympy~=1.0
|
|
45
|
+
Requires-Dist: tqdm~=4.0
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: build==1.4.0; extra == "dev"
|
|
48
|
+
Requires-Dist: pre-commit==4.5.1; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest==9.0.2; extra == "dev"
|
|
50
|
+
Requires-Dist: ruff==0.15.5; extra == "dev"
|
|
51
|
+
Requires-Dist: twine==6.2.0; extra == "dev"
|
|
52
|
+
Requires-Dist: ty==0.0.21; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# TimeRelease
|
|
56
|
+
|
|
57
|
+
## Introduction
|
|
58
|
+
|
|
59
|
+
A simple Python utility to lock (or encrypt) secrets, such that they require a preset amount of compute to unlock (or decrypt) them later.
|
|
60
|
+
|
|
61
|
+
Since each cryptographic step requires the last, it (by design) cannot be parallelized.
|
|
62
|
+
|
|
63
|
+
This could potentially be useful if you want to wait, for example, an hour, before being able to access a password; as a form of stolen device protection.
|
|
64
|
+
|
|
65
|
+
## WARNING!
|
|
66
|
+
|
|
67
|
+
Always keep your "encrypted" file secret, treating it as if it were a password. It is effectively unencrypted as it can be used to decrypt the secret (if such an attacker has sufficient time to complete the puzzle) without any other knowledge of the secret or the individual who encrypted it.
|
|
68
|
+
|
|
69
|
+
## Table of Contents
|
|
70
|
+
|
|
71
|
+
- [Interactive Usage](#interactive-usage)
|
|
72
|
+
- [Setup](#setup)
|
|
73
|
+
- [Encryption](#encryption)
|
|
74
|
+
- [Decryption](#decryption)
|
|
75
|
+
- [Module API](#module-api)
|
|
76
|
+
- [Development Usage](#development-usage)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Interactive Usage
|
|
81
|
+
|
|
82
|
+
### Setup
|
|
83
|
+
|
|
84
|
+
Install the package and its dependencies in a venv:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python3 -m venv ./venv
|
|
88
|
+
source ./venv/bin/activate
|
|
89
|
+
pip install TimeRelease
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Encryption
|
|
93
|
+
|
|
94
|
+
To encrypt a file `somefile.txt` -> `somefile.txt.timerelease`:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Specify # of seconds it should take to unlock on your machine
|
|
98
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --time 3600
|
|
99
|
+
# Or, specify raw # of iterations needed to unlock the file
|
|
100
|
+
timerelease --encrypt somefile.txt somefile.txt.timerelease --iters 1073741824
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can use this to determine how long (on your hardware) the decryption process will take. Encryption should be nearly instant for most settings, even for comically large numbers of iterations.
|
|
104
|
+
|
|
105
|
+
### Decryption
|
|
106
|
+
|
|
107
|
+
To decrypt a file `somefile.txt.timerelease` -> `somefile.txt`:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
timerelease --decrypt somefile.txt.timerelease somefile.txt
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
It will take however long the person who encrypted the file dictates. The only way to speed it up is to find a CPU with really, really fast single-core performance.
|
|
114
|
+
|
|
115
|
+
## Module API
|
|
116
|
+
|
|
117
|
+
Like any module, TimeRelease can be installed via pip (`pip install TimeRelease`) and imported (`import TimeRelease`). The API for the TimeRelease module is as follows:
|
|
118
|
+
|
|
119
|
+
#### `TimeRelease.decrypt_secret(enc_package)`
|
|
120
|
+
|
|
121
|
+
* Given a dictionary which was generated by encrypt_secret(), decrypt_secret() decrypts it and returns the secret as a byte string.
|
|
122
|
+
* If `logging` (defaults to True) is set to True, a progress bar is printed, along with extra debug information.
|
|
123
|
+
|
|
124
|
+
#### `TimeRelease.encrypt_secret()`
|
|
125
|
+
|
|
126
|
+
* Given a byte string `secret`, encrypt_secret() applies `iterations` iterations to it, "encrypting" it.
|
|
127
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
128
|
+
|
|
129
|
+
#### `TimeRelease.run_single_benchmark()`
|
|
130
|
+
|
|
131
|
+
* Determines how long it takes for the CPU to run a fixed number of `iterations`.
|
|
132
|
+
* Amount of time elapsed is returned in seconds as a float.
|
|
133
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
134
|
+
|
|
135
|
+
#### `TimeRelease.run_benchmark()`
|
|
136
|
+
|
|
137
|
+
* Determines how many iterations a single core of the user's CPU is capable of processing, on average, per second (as an integer).
|
|
138
|
+
* `benches` (defaults to 10) describes the number of times the benchmark will be run.
|
|
139
|
+
* Higher values of `benches` = greater certainty in running time, but more time is spent benchmarking.
|
|
140
|
+
* If `logging` (defaults to True) is set to True, extra debug information is printed.
|
|
141
|
+
|
|
142
|
+
## Development Usage
|
|
143
|
+
|
|
144
|
+
To install development versions of this package, clone this repo and `cd` into it, then run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
python3 -m venv ./venv
|
|
148
|
+
source ./venv/bin/activate
|
|
149
|
+
pip install -e .[dev]
|
|
150
|
+
pre-commit install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
To test the codebase, simply run `pytest`. To apply proper formatting to the codebase, run `ruff format .`. To ensure that the code has no other issues with PEP8 compliance, run `ruff check`. And to ensure that there are no type errors, please run `ty check TimeRelease`. Please do all of these things before you commit, and not after!
|
|
154
|
+
|
|
155
|
+
To build a distribution package, first update the version number in `pyproject.toml`, and make a tag for that release version at the current commit. Then:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python3 -m build
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
To upload this build to PyPI, run:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python3 -m twine upload dist/* --skip-existing
|
|
165
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
TimeRelease/__init__.py
|
|
6
|
+
TimeRelease/__main__.py
|
|
7
|
+
TimeRelease/b64utils.py
|
|
8
|
+
TimeRelease/bench.py
|
|
9
|
+
TimeRelease/decrypt.py
|
|
10
|
+
TimeRelease/encrypt.py
|
|
11
|
+
TimeRelease.egg-info/PKG-INFO
|
|
12
|
+
TimeRelease.egg-info/SOURCES.txt
|
|
13
|
+
TimeRelease.egg-info/dependency_links.txt
|
|
14
|
+
TimeRelease.egg-info/entry_points.txt
|
|
15
|
+
TimeRelease.egg-info/requires.txt
|
|
16
|
+
TimeRelease.egg-info/top_level.txt
|
|
17
|
+
tests/test_encrypt_decrypt.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TimeRelease
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "TimeRelease"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
requires-python = ">=3.13"
|
|
9
|
+
description = "Python utility to cryptographically lock secrets for a preset amount of time."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Leo Pagano", email = "leo@leoapagano.com" }
|
|
13
|
+
]
|
|
14
|
+
maintainers = [
|
|
15
|
+
{ name = "Leo Pagano", email = "leo@leoapagano.com" }
|
|
16
|
+
]
|
|
17
|
+
license = { file = "LICENSE" }
|
|
18
|
+
keywords = [
|
|
19
|
+
"TimeRelease",
|
|
20
|
+
"Time",
|
|
21
|
+
"Release",
|
|
22
|
+
"Encryption",
|
|
23
|
+
"Crypto",
|
|
24
|
+
"Cryptography",
|
|
25
|
+
"Pagano"
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Intended Audience :: Developers",
|
|
32
|
+
"Operating System :: POSIX :: Linux",
|
|
33
|
+
"Operating System :: Microsoft :: Windows",
|
|
34
|
+
"Operating System :: MacOS",
|
|
35
|
+
"Operating System :: MacOS :: MacOS X",
|
|
36
|
+
]
|
|
37
|
+
dependencies = [
|
|
38
|
+
"mpmath~=1.0",
|
|
39
|
+
"pycryptodome~=3.0",
|
|
40
|
+
"sympy~=1.0",
|
|
41
|
+
"tqdm~=4.0"
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
dev = [
|
|
46
|
+
"build==1.4.0",
|
|
47
|
+
"pre-commit==4.5.1",
|
|
48
|
+
"pytest==9.0.2",
|
|
49
|
+
"ruff==0.15.5",
|
|
50
|
+
"twine==6.2.0",
|
|
51
|
+
"ty==0.0.21"
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://github.com/leoapagano/TimeRelease"
|
|
56
|
+
Documentation = "https://github.com/leoapagano/TimeRelease/blob/main/README.md"
|
|
57
|
+
|
|
58
|
+
[project.scripts]
|
|
59
|
+
timerelease = "TimeRelease.__main__:main"
|
|
60
|
+
|
|
61
|
+
[tool.ruff.format]
|
|
62
|
+
indent-style = "tab"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from TimeRelease import decrypt_secret, encrypt_secret
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_hello_world():
|
|
5
|
+
"""
|
|
6
|
+
Very basic test case. Just ensures that an input, when encrypted and decrypted,
|
|
7
|
+
produces the same output that it started with.
|
|
8
|
+
"""
|
|
9
|
+
input = b"hello world"
|
|
10
|
+
encrypted = encrypt_secret(input, 2**16, logging=False)
|
|
11
|
+
decrypted = decrypt_secret(encrypted, logging=False)
|
|
12
|
+
assert decrypted == b"hello world"
|