ghostbytes 1.0.0__py3-none-any.whl
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.
- ghostbytes/__init__.py +3 -0
- ghostbytes/__main__.py +9 -0
- ghostbytes/crypto/config.py +155 -0
- ghostbytes/crypto/crypto.py +162 -0
- ghostbytes/crypto/crypto.pyi +149 -0
- ghostbytes/crypto/kyber.py +257 -0
- ghostbytes/crypto/oaep_extension.py +125 -0
- ghostbytes/crypto/primitives.py +360 -0
- ghostbytes/error.py +177 -0
- ghostbytes/gui/gui.py +2809 -0
- ghostbytes/gui/theme.py +95 -0
- ghostbytes/gui/wrappers.py +566 -0
- ghostbytes/img/icon.ico +0 -0
- ghostbytes/img/icon.png +0 -0
- ghostbytes/tools/benchmark.py +218 -0
- ghostbytes/tools/rand.py +118 -0
- ghostbytes/tools/shred.py +384 -0
- ghostbytes/tools/tools.pyi +67 -0
- ghostbytes-1.0.0.dist-info/METADATA +182 -0
- ghostbytes-1.0.0.dist-info/RECORD +22 -0
- ghostbytes-1.0.0.dist-info/WHEEL +4 -0
- ghostbytes-1.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""
|
|
2
|
+
benchmark.py
|
|
3
|
+
|
|
4
|
+
This module provides benchmarking utilities for the cryptographic features
|
|
5
|
+
implemented throughout this Ghostbytes.
|
|
6
|
+
|
|
7
|
+
The benchmarks measure encryption, decryption, hashing, random-data generation,
|
|
8
|
+
and key-generation times using a high-resolution performance counter.
|
|
9
|
+
Results are returned as formatted tuple containing the algorithm name and elapsed time
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from hashlib import md5
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from os import name as _os_name
|
|
17
|
+
from time import perf_counter_ns
|
|
18
|
+
from random import randbytes
|
|
19
|
+
|
|
20
|
+
from Crypto.PublicKey import RSA as _rsa
|
|
21
|
+
from ghostbytes.crypto import primitives
|
|
22
|
+
from ghostbytes.crypto.config import AVAIL_ALG, AVAIL_HASH, \
|
|
23
|
+
AVAIL_HASH_STR, AVAIL_RANDOM_STR, CryptoConfig
|
|
24
|
+
from ghostbytes.crypto.kyber import genkey, kyber_decrypt, kyber_encrypt
|
|
25
|
+
from ghostbytes.crypto.oaep_extension import oaep_extended_decrypt, oaep_extended_encrypt
|
|
26
|
+
from ghostbytes.error import algorithm_not_supported, hash_not_supported
|
|
27
|
+
from ghostbytes.tools import rand
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def benchmark(algorithm, length, rsa_keysize):
|
|
31
|
+
"""
|
|
32
|
+
Benchmark a supported cryptographic, hashing, or random-data algorithm.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
algorithm: Name of the algorithm to benchmark
|
|
36
|
+
length: Number of bytes to use as benchmark input
|
|
37
|
+
rsa_keysize: RSA key size, in bits, used by RSA-based algorithms.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
tuple: benchmark results.
|
|
41
|
+
hash, random algorithms: ``(algorithm, elapsed_time)``
|
|
42
|
+
cryptographic algorithms: ``(encryption_result, decryption_result), keygen_result``
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
algorithm_not_supported: if ``algorithm`` is not configured as an available algorithm
|
|
46
|
+
"""
|
|
47
|
+
if algorithm in AVAIL_HASH_STR:
|
|
48
|
+
benchmark_data = randbytes(length)
|
|
49
|
+
return _benchmark_hash(algorithm, benchmark_data)
|
|
50
|
+
if algorithm in AVAIL_RANDOM_STR:
|
|
51
|
+
return _benchmark_random(algorithm, length)
|
|
52
|
+
if algorithm in AVAIL_ALG:
|
|
53
|
+
benchmark_data = randbytes(length)
|
|
54
|
+
return (
|
|
55
|
+
_benchmark_crypto(algorithm, benchmark_data, rsa_keysize),
|
|
56
|
+
_benchmark_keygen(algorithm, rsa_keysize)
|
|
57
|
+
)
|
|
58
|
+
raise algorithm_not_supported()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def benchmark_all(length, rsa_keysize):
|
|
62
|
+
"""
|
|
63
|
+
Benchmark every configured hash, cryptographic, and random algorithms.
|
|
64
|
+
|
|
65
|
+
Results are turned in the order defined by the configured algorithm lists.
|
|
66
|
+
Sections labels are inserted before each category of algorithm.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
length: Number of bytes to use as benchmark input
|
|
70
|
+
rsa_keysize: RSA key size, in bits, used by RSA-based algorithms
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of tuples. ``(algorithm_name, algorithm_time_or_error)``
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
all_algorithms = AVAIL_HASH_STR + AVAIL_ALG + AVAIL_RANDOM_STR
|
|
77
|
+
|
|
78
|
+
results = []
|
|
79
|
+
for algorithm in all_algorithms:
|
|
80
|
+
if algorithm in AVAIL_HASH_STR and AVAIL_HASH_STR.index(
|
|
81
|
+
algorithm) == 0:
|
|
82
|
+
results.append("========== HASH ALGORITHMS ==========")
|
|
83
|
+
if algorithm in AVAIL_ALG and AVAIL_ALG.index(algorithm) == 0:
|
|
84
|
+
results.append("\n========== CRYPTO ALGORITHMS ==========")
|
|
85
|
+
if algorithm in AVAIL_RANDOM_STR and AVAIL_RANDOM_STR.index(
|
|
86
|
+
algorithm) == 0:
|
|
87
|
+
results.append("\n========== RANDOM ALGORITHMS ==========")
|
|
88
|
+
result = benchmark(algorithm, length, rsa_keysize)
|
|
89
|
+
if algorithm in AVAIL_ALG:
|
|
90
|
+
results.append(result[0][0])
|
|
91
|
+
results.append(result[0][1])
|
|
92
|
+
results.append(result[1])
|
|
93
|
+
else:
|
|
94
|
+
results.append(result)
|
|
95
|
+
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _benchmark_hash(algorithm, data):
|
|
100
|
+
try:
|
|
101
|
+
index = AVAIL_HASH_STR.index(algorithm)
|
|
102
|
+
except ValueError as e:
|
|
103
|
+
raise hash_not_supported() from e
|
|
104
|
+
|
|
105
|
+
alg = AVAIL_HASH[index]
|
|
106
|
+
start = perf_counter_ns()
|
|
107
|
+
if alg != md5:
|
|
108
|
+
alg.new(data=data).digest()
|
|
109
|
+
else:
|
|
110
|
+
md5(data).digest()
|
|
111
|
+
consumed = perf_counter_ns() - start
|
|
112
|
+
|
|
113
|
+
return (algorithm, _format_time(consumed))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _benchmark_random(algorithm, length):
|
|
117
|
+
if _os_name != 'posix' and '(*nux only)' in algorithm:
|
|
118
|
+
return (algorithm, '-')
|
|
119
|
+
|
|
120
|
+
start = perf_counter_ns()
|
|
121
|
+
|
|
122
|
+
if "dd" in algorithm:
|
|
123
|
+
is_secure = "/dev/urandom" in algorithm
|
|
124
|
+
bs = min(length, 4 * 1024 * 1024)
|
|
125
|
+
count = (length + bs - 1) // bs
|
|
126
|
+
rand.dd_random_to_file(is_secure, "/dev/null", bs, count)
|
|
127
|
+
else:
|
|
128
|
+
rand.random(algorithm, length)
|
|
129
|
+
|
|
130
|
+
consumed = perf_counter_ns() - start
|
|
131
|
+
|
|
132
|
+
return (algorithm, _format_time(consumed))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _benchmark_crypto(algorithm, data, rsa_keysize=None):
|
|
136
|
+
plaintext = bytes()
|
|
137
|
+
encrypt_consumed = 0
|
|
138
|
+
decrypt_consumed = 0
|
|
139
|
+
if algorithm == 'aes':
|
|
140
|
+
key = randbytes(16)
|
|
141
|
+
|
|
142
|
+
start_encrypt = perf_counter_ns()
|
|
143
|
+
ciphertext = primitives.aes_encrypt(CryptoConfig(), key, data)
|
|
144
|
+
encrypt_consumed = perf_counter_ns() - start_encrypt
|
|
145
|
+
|
|
146
|
+
start_decrypt = perf_counter_ns()
|
|
147
|
+
plaintext = primitives.aes_decrypt(CryptoConfig(), key, ciphertext)
|
|
148
|
+
decrypt_consumed = perf_counter_ns() - start_decrypt
|
|
149
|
+
elif algorithm == "rsa-oaep":
|
|
150
|
+
priv_key = _rsa.generate(rsa_keysize)
|
|
151
|
+
pub_key = priv_key.public_key()
|
|
152
|
+
|
|
153
|
+
start_encrypt = perf_counter_ns()
|
|
154
|
+
ciphertext = primitives.rsa_oaep_encrypt(CryptoConfig(), pub_key, data)
|
|
155
|
+
encrypt_consumed = perf_counter_ns() - start_encrypt
|
|
156
|
+
|
|
157
|
+
start_decrypt = perf_counter_ns()
|
|
158
|
+
plaintext = primitives.rsa_oaep_decrypt(
|
|
159
|
+
CryptoConfig(), priv_key, ciphertext)
|
|
160
|
+
decrypt_consumed = perf_counter_ns() - start_decrypt
|
|
161
|
+
elif algorithm == "extended_oaep":
|
|
162
|
+
priv_key = _rsa.generate(rsa_keysize)
|
|
163
|
+
pub_key = priv_key.public_key()
|
|
164
|
+
|
|
165
|
+
start_encrypt = perf_counter_ns()
|
|
166
|
+
ciphertext = oaep_extended_encrypt(CryptoConfig(), pub_key, data)
|
|
167
|
+
encrypt_consumed = perf_counter_ns() - start_encrypt
|
|
168
|
+
|
|
169
|
+
start_decrypt = perf_counter_ns()
|
|
170
|
+
plaintext = oaep_extended_decrypt(CryptoConfig(), priv_key, ciphertext)
|
|
171
|
+
decrypt_consumed = perf_counter_ns() - start_decrypt
|
|
172
|
+
elif re.search(r"^ML-KEM-(768|1024)$", algorithm):
|
|
173
|
+
pubkey, privkey = genkey(algorithm, "PEM")
|
|
174
|
+
|
|
175
|
+
start_encrypt = perf_counter_ns()
|
|
176
|
+
ciphertext = kyber_encrypt(CryptoConfig(), pubkey, data)
|
|
177
|
+
encrypt_consumed = perf_counter_ns() - start_encrypt
|
|
178
|
+
|
|
179
|
+
start_decrypt = perf_counter_ns()
|
|
180
|
+
plaintext = kyber_decrypt(CryptoConfig(), privkey, ciphertext)
|
|
181
|
+
decrypt_consumed = perf_counter_ns() - start_decrypt
|
|
182
|
+
|
|
183
|
+
if plaintext != data:
|
|
184
|
+
return (
|
|
185
|
+
(f'{algorithm}-encrypt', _format_time(encrypt_consumed)),
|
|
186
|
+
(f'{algorithm}-decrypt', 'failed')
|
|
187
|
+
)
|
|
188
|
+
return (
|
|
189
|
+
(f'{algorithm}-encrypt', _format_time(encrypt_consumed)),
|
|
190
|
+
(f'{algorithm}-decrypt', _format_time(decrypt_consumed))
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _benchmark_keygen(algorithm, rsa_keysize):
|
|
195
|
+
start = perf_counter_ns()
|
|
196
|
+
if algorithm == "aes":
|
|
197
|
+
randbytes(32)
|
|
198
|
+
label = "aes-keygen"
|
|
199
|
+
elif algorithm in ("rsa-oaep", "extended_oaep"):
|
|
200
|
+
primitives.genrsa(rsa_keysize, 65537, None)
|
|
201
|
+
label = f"{algorithm}-keygen"
|
|
202
|
+
elif re.search(r"^ML-KEM-(768|1024)$", algorithm):
|
|
203
|
+
genkey(algorithm, "PEM")
|
|
204
|
+
label = f"{algorithm}-keygen"
|
|
205
|
+
else:
|
|
206
|
+
raise algorithm_not_supported()
|
|
207
|
+
consumed = perf_counter_ns() - start
|
|
208
|
+
return label, _format_time(consumed)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _format_time(ns):
|
|
212
|
+
if ns < 1_000_000:
|
|
213
|
+
return f"{ns} ns"
|
|
214
|
+
if ns < 1_000_000_000:
|
|
215
|
+
return f"{ns / 1_000_000:.3f} ms"
|
|
216
|
+
if ns < 60_000_000_000:
|
|
217
|
+
return f"{ns / 1_000_000_000:.3f} s"
|
|
218
|
+
return f"{ns / 60_000_000_000:.3f} m"
|
ghostbytes/tools/rand.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
random.py
|
|
3
|
+
|
|
4
|
+
This modules provides a unified interface for generating random bytes
|
|
5
|
+
from different libraries, operating sytems, and cryptographic sources.
|
|
6
|
+
|
|
7
|
+
Supported sources are listed in crypto/config.py
|
|
8
|
+
|
|
9
|
+
It also provides a helper for using the Unix ``dd`` command to write random
|
|
10
|
+
data directly to a file.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import subprocess as _subprocess
|
|
14
|
+
from secrets import token_bytes as _secrets_randbytes
|
|
15
|
+
from random import randbytes as _random_randbytes
|
|
16
|
+
from os import urandom as _os_urandom
|
|
17
|
+
from os import name as _os_name
|
|
18
|
+
|
|
19
|
+
from Crypto.Random import get_random_bytes as _cryptodome_randbytes
|
|
20
|
+
from ghostbytes.crypto.config import AVAIL_RANDOM_STR as _AVAIL_RANDOM_STR
|
|
21
|
+
from ghostbytes.error import invalid_random_length as _invalid_random_length
|
|
22
|
+
from ghostbytes.error import random_function_not_supported as _random_function_not_supported
|
|
23
|
+
from ghostbytes.error import unix_only_function as _unix_only_function
|
|
24
|
+
|
|
25
|
+
def random(algorithm, length):
|
|
26
|
+
"""
|
|
27
|
+
Generate random bytes using the selected random-data sources (algorithms)
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
algorithm: Name of the random-data sources. Support values are:
|
|
31
|
+
- ``"os.urandom"``
|
|
32
|
+
- ``"cryptodome_random"``
|
|
33
|
+
- ``"secrets_random"``
|
|
34
|
+
- ``"random lib (python)"``
|
|
35
|
+
- ``"/dev/urandom (*nux only)"``
|
|
36
|
+
- ``"/dev/random (*nux only)"``
|
|
37
|
+
length: Number of random bytes to generate. Must be a non-negative integer
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
bytes: Randomly generated bytes of length ``length``
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
invalid_random_length: if ``length`` is not an integer or is negative.
|
|
44
|
+
random_function_not_supported: if ``algorithm`` is not supported
|
|
45
|
+
unix_only_function: if a unix-only sources is requested on a non-POSIX OS.
|
|
46
|
+
subprocess.CalledProcessError: if a Unix device source fails while being read.
|
|
47
|
+
"""
|
|
48
|
+
if not isinstance(length, int) or length < 0:
|
|
49
|
+
raise _invalid_random_length()
|
|
50
|
+
if algorithm not in _AVAIL_RANDOM_STR:
|
|
51
|
+
raise _random_function_not_supported()
|
|
52
|
+
if _os_name != "posix" and "(*nux only)" in algorithm:
|
|
53
|
+
raise _unix_only_function(algorithm)
|
|
54
|
+
|
|
55
|
+
match algorithm:
|
|
56
|
+
case "os.urandom":
|
|
57
|
+
return _os_urandom(length)
|
|
58
|
+
case "cryptodome_random":
|
|
59
|
+
return _cryptodome_randbytes(length)
|
|
60
|
+
case "secrets_random":
|
|
61
|
+
return _secrets_randbytes(length)
|
|
62
|
+
case "random lib (python)":
|
|
63
|
+
return _random_randbytes(length)
|
|
64
|
+
case "/dev/urandom (*nux only)":
|
|
65
|
+
return _subprocess.run(
|
|
66
|
+
["head","-c",str(length),"/dev/urandom"],
|
|
67
|
+
stdout=_subprocess.PIPE,
|
|
68
|
+
stderr=_subprocess.PIPE,
|
|
69
|
+
check=True
|
|
70
|
+
).stdout
|
|
71
|
+
case "/dev/random (*nux only)":
|
|
72
|
+
return _subprocess.run(
|
|
73
|
+
["head","-c",str(length),"/dev/random"],
|
|
74
|
+
stdout=_subprocess.PIPE,
|
|
75
|
+
stderr=_subprocess.PIPE,
|
|
76
|
+
check=True
|
|
77
|
+
).stdout
|
|
78
|
+
case _:
|
|
79
|
+
raise _random_function_not_supported()
|
|
80
|
+
|
|
81
|
+
def dd_random_to_file(is_secure,outfile,bs,count):
|
|
82
|
+
"""
|
|
83
|
+
Write random data to a file using the Unix ``dd`` command
|
|
84
|
+
|
|
85
|
+
The function copies ``bs * count`` bytes from
|
|
86
|
+
``/dev/urandom`` or ``/dev/random`` to ``outfile``.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
is_secure: select the random source.
|
|
90
|
+
``True`` uses ``/dev/urandom``; ``False`` uses ``/dev/random``
|
|
91
|
+
outfile: path of the output file
|
|
92
|
+
bs: number of bytes per block passed to ``dd``
|
|
93
|
+
count: number of blocks to write
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
subprocess.CompletedProcess: The completed ``dd`` process.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
unix_only_function: if called on a non-POSIX OS.
|
|
100
|
+
subprocess.CalledProcessError: if the ``dd`` command fails.
|
|
101
|
+
"""
|
|
102
|
+
if _os_name != 'posix':
|
|
103
|
+
raise _unix_only_function("dd random sources")
|
|
104
|
+
if is_secure:
|
|
105
|
+
return _subprocess.run(
|
|
106
|
+
["dd","if=/dev/urandom",f"of={outfile}",
|
|
107
|
+
f"bs={str(bs)}",f"count={str(count)}","status=none"],
|
|
108
|
+
stdout=_subprocess.PIPE,
|
|
109
|
+
stderr=_subprocess.PIPE,
|
|
110
|
+
check=True
|
|
111
|
+
)
|
|
112
|
+
return _subprocess.run(
|
|
113
|
+
["dd","if=/dev/random",f"of={outfile}",
|
|
114
|
+
f"bs={str(bs)}",f"count={str(count)}","status=none"],
|
|
115
|
+
stdout=_subprocess.PIPE,
|
|
116
|
+
stderr=_subprocess.PIPE,
|
|
117
|
+
check=True
|
|
118
|
+
)
|