docrypt 0.1.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.
docrypt/max.py ADDED
@@ -0,0 +1,59 @@
1
+ from .aes import decrypt_aes, encrypt_aes
2
+ from .m import derive_key
3
+ from .p import from_base64, from_hex, to_base64, to_bytes, to_hex
4
+ from .sha512 import hash_sha512_bytes
5
+ from .xor import xor_encrypt
6
+ from .z import secure_compare
7
+
8
+ DIGEST_SIZE = 64
9
+ INFO_ONE = b"docrypt-max-layer-one"
10
+ INFO_TWO = b"docrypt-max-layer-two"
11
+ INFO_THREE = b"docrypt-max-layer-three"
12
+
13
+
14
+ def max_crypt(main, *keys):
15
+ if not keys:
16
+ raise ValueError("at least one key is required")
17
+ data = to_bytes(main)
18
+ key_one = derive_key(*keys, info=INFO_ONE)
19
+ key_two = derive_key(*keys, info=INFO_TWO)
20
+ key_three = derive_key(*keys, info=INFO_THREE)
21
+ data = encrypt_aes(data, key_one)
22
+ data = xor_encrypt(data, key_three)
23
+ data = encrypt_aes(data, key_two)
24
+ return data + hash_sha512_bytes(data)
25
+
26
+
27
+ def max_decrypt(main, *keys):
28
+ if not keys:
29
+ raise ValueError("at least one key is required")
30
+ token = to_bytes(main)
31
+ if len(token) < DIGEST_SIZE + 2 * (12 + 16):
32
+ raise ValueError("invalid token size")
33
+ body = token[:-DIGEST_SIZE]
34
+ digest = token[-DIGEST_SIZE:]
35
+ if not secure_compare(hash_sha512_bytes(body), digest):
36
+ raise ValueError("integrity check failed, data is corrupted")
37
+ key_one = derive_key(*keys, info=INFO_ONE)
38
+ key_two = derive_key(*keys, info=INFO_TWO)
39
+ key_three = derive_key(*keys, info=INFO_THREE)
40
+ data = decrypt_aes(body, key_two)
41
+ data = xor_encrypt(data, key_three)
42
+ data = decrypt_aes(data, key_one)
43
+ return data
44
+
45
+
46
+ def max_crypt_hex(main, *keys):
47
+ return to_hex(max_crypt(main, *keys))
48
+
49
+
50
+ def max_decrypt_hex(main, *keys):
51
+ return max_decrypt(from_hex(main), *keys)
52
+
53
+
54
+ def max_crypt_base64(main, *keys):
55
+ return to_base64(max_crypt(main, *keys))
56
+
57
+
58
+ def max_decrypt_base64(main, *keys):
59
+ return max_decrypt(from_base64(main), *keys)
docrypt/much.py ADDED
@@ -0,0 +1,65 @@
1
+ from .decrypt import decrypt, decrypt_hex
2
+ from .maincrypt import crypt, crypt_hex
3
+ from .p import to_str
4
+
5
+
6
+ def crypt_many(items, *keys):
7
+ return [crypt(item, *keys) for item in items]
8
+
9
+
10
+ def decrypt_many(tokens, *keys):
11
+ return [decrypt(token, *keys) for token in tokens]
12
+
13
+
14
+ def decrypt_many_str(tokens, *keys):
15
+ return [to_str(decrypt(token, *keys)) for token in tokens]
16
+
17
+
18
+ def crypt_dict(mapping, *keys):
19
+ result = {}
20
+ for name, value in mapping.items():
21
+ result[name] = crypt(value, *keys)
22
+ return result
23
+
24
+
25
+ def decrypt_dict(mapping, *keys):
26
+ result = {}
27
+ for name, value in mapping.items():
28
+ result[name] = decrypt(value, *keys)
29
+ return result
30
+
31
+
32
+ def crypt_file(source_path, target_path, *keys):
33
+ with open(source_path, "rb") as handle:
34
+ raw = handle.read()
35
+ token = crypt(raw, *keys)
36
+ with open(target_path, "wb") as handle:
37
+ handle.write(token)
38
+ return len(token)
39
+
40
+
41
+ def decrypt_file(source_path, target_path, *keys):
42
+ with open(source_path, "rb") as handle:
43
+ token = handle.read()
44
+ raw = decrypt(token, *keys)
45
+ with open(target_path, "wb") as handle:
46
+ handle.write(raw)
47
+ return len(raw)
48
+
49
+
50
+ def crypt_file_hex(source_path, target_path, *keys):
51
+ with open(source_path, "rb") as handle:
52
+ raw = handle.read()
53
+ token = crypt_hex(raw, *keys)
54
+ with open(target_path, "w") as handle:
55
+ handle.write(token)
56
+ return len(token)
57
+
58
+
59
+ def decrypt_file_hex(source_path, target_path, *keys):
60
+ with open(source_path, "r") as handle:
61
+ token = handle.read()
62
+ raw = decrypt_hex(token, *keys)
63
+ with open(target_path, "wb") as handle:
64
+ handle.write(raw)
65
+ return len(raw)
docrypt/mych.py ADDED
@@ -0,0 +1,58 @@
1
+ import hashlib
2
+
3
+ from .p import from_hex, to_bytes, to_hex
4
+
5
+
6
+ def mix_bytes(data):
7
+ raw = to_bytes(data)
8
+ left = raw[:(len(raw) + 1) // 2]
9
+ right = raw[(len(raw) + 1) // 2:]
10
+ mixed = bytearray()
11
+ for index in range(len(right)):
12
+ mixed.append(left[index])
13
+ mixed.append(right[index])
14
+ if len(left) > len(right):
15
+ mixed.append(left[-1])
16
+ return bytes(mixed)
17
+
18
+
19
+ def unmix_bytes(data):
20
+ raw = to_bytes(data)
21
+ return bytes(raw[0::2] + raw[1::2])
22
+
23
+
24
+ def mix_hex(data):
25
+ return to_hex(mix_bytes(data))
26
+
27
+
28
+ def unmix_hex(data):
29
+ return unmix_bytes(from_hex(data))
30
+
31
+
32
+ def mix_rounds(data, rounds=2):
33
+ if rounds < 1:
34
+ raise ValueError("rounds must be at least 1")
35
+ raw = to_bytes(data)
36
+ for _ in range(rounds):
37
+ raw = mix_bytes(raw)
38
+ return raw
39
+
40
+
41
+ def unmix_rounds(data, rounds=2):
42
+ if rounds < 1:
43
+ raise ValueError("rounds must be at least 1")
44
+ raw = to_bytes(data)
45
+ for _ in range(rounds):
46
+ raw = unmix_bytes(raw)
47
+ return raw
48
+
49
+
50
+ def mix_keys(*keys):
51
+ if not keys:
52
+ raise ValueError("at least one key is required")
53
+ digests = [hashlib.sha512(to_bytes(item)).digest() for item in keys]
54
+ result = bytearray(digests[0])
55
+ for digest in digests[1:]:
56
+ for index in range(64):
57
+ result[index] ^= digest[index]
58
+ return bytes(result)
docrypt/p.py ADDED
@@ -0,0 +1,141 @@
1
+ import base64
2
+
3
+
4
+ def to_bytes(data):
5
+ if isinstance(data, bytes):
6
+ return data
7
+ if isinstance(data, bytearray):
8
+ return bytes(data)
9
+ if isinstance(data, memoryview):
10
+ return bytes(data)
11
+ if isinstance(data, str):
12
+ return data.encode("utf-8")
13
+ if isinstance(data, (int, float)):
14
+ return str(data).encode("utf-8")
15
+ raise TypeError("unsupported data type for conversion to bytes")
16
+
17
+
18
+ def to_str(data):
19
+ return to_bytes(data).decode("utf-8")
20
+
21
+
22
+ def to_hex(data):
23
+ return to_bytes(data).hex()
24
+
25
+
26
+ def from_hex(data):
27
+ if isinstance(data, (bytes, bytearray, memoryview)):
28
+ data = bytes(data).decode("ascii")
29
+ return bytes.fromhex(data)
30
+
31
+
32
+ def to_base64(data):
33
+ return base64.b64encode(to_bytes(data)).decode("ascii")
34
+
35
+
36
+ def from_base64(data):
37
+ if isinstance(data, (bytes, bytearray, memoryview)):
38
+ data = bytes(data).decode("ascii")
39
+ return base64.b64decode(data)
40
+
41
+
42
+ def to_base64_url(data):
43
+ return base64.urlsafe_b64encode(to_bytes(data)).decode("ascii")
44
+
45
+
46
+ def from_base64_url(data):
47
+ if isinstance(data, (bytes, bytearray, memoryview)):
48
+ data = bytes(data).decode("ascii")
49
+ return base64.urlsafe_b64decode(data)
50
+
51
+
52
+ def to_base32(data):
53
+ return base64.b32encode(to_bytes(data)).decode("ascii")
54
+
55
+
56
+ def from_base32(data):
57
+ if isinstance(data, (bytes, bytearray, memoryview)):
58
+ data = bytes(data).decode("ascii")
59
+ return base64.b32decode(data)
60
+
61
+
62
+ def to_base85(data):
63
+ return base64.b85encode(to_bytes(data)).decode("ascii")
64
+
65
+
66
+ def from_base85(data):
67
+ if isinstance(data, (bytes, bytearray, memoryview)):
68
+ data = bytes(data).decode("ascii")
69
+ return base64.b85decode(data)
70
+
71
+
72
+ def to_int(data):
73
+ return int.from_bytes(to_bytes(data), "big")
74
+
75
+
76
+ def from_int(value, length=None):
77
+ if not isinstance(value, int):
78
+ raise TypeError("value must be an integer")
79
+ if value < 0:
80
+ raise ValueError("value must not be negative")
81
+ if length is None:
82
+ length = max(1, (value.bit_length() + 7) // 8)
83
+ return value.to_bytes(length, "big")
84
+
85
+
86
+ def pad_pkcs7(data, block_size=16):
87
+ raw = to_bytes(data)
88
+ if block_size < 1 or block_size > 255:
89
+ raise ValueError("block size must be between 1 and 255")
90
+ pad_length = block_size - (len(raw) % block_size)
91
+ return raw + bytes([pad_length] * pad_length)
92
+
93
+
94
+ def unpad_pkcs7(data, block_size=16):
95
+ raw = to_bytes(data)
96
+ if len(raw) == 0 or len(raw) % block_size != 0:
97
+ raise ValueError("invalid padded data length")
98
+ pad_length = raw[-1]
99
+ if pad_length < 1 or pad_length > block_size:
100
+ raise ValueError("invalid padding byte")
101
+ if raw[-pad_length:] != bytes([pad_length] * pad_length):
102
+ raise ValueError("invalid padding block")
103
+ return raw[:-pad_length]
104
+
105
+
106
+ def chunk_bytes(data, size):
107
+ raw = to_bytes(data)
108
+ if size <= 0:
109
+ raise ValueError("chunk size must be positive")
110
+ return [raw[i:i + size] for i in range(0, len(raw), size)]
111
+
112
+
113
+ def join_bytes(parts):
114
+ result = bytearray()
115
+ for part in parts:
116
+ result.extend(to_bytes(part))
117
+ return bytes(result)
118
+
119
+
120
+ def split_bytes(data, first_length, second_length=None):
121
+ raw = to_bytes(data)
122
+ if second_length is None:
123
+ return raw[:first_length], raw[first_length:]
124
+ return raw[:first_length], raw[first_length:first_length + second_length], raw[first_length + second_length:]
125
+
126
+
127
+ def reverse_bytes(data):
128
+ return to_bytes(data)[::-1]
129
+
130
+
131
+ def repeat_bytes(data, length):
132
+ raw = to_bytes(data)
133
+ if len(raw) == 0:
134
+ raise ValueError("source data must not be empty")
135
+ if length <= 0:
136
+ raise ValueError("length must be positive")
137
+ return (raw * (length // len(raw) + 1))[:length]
138
+
139
+
140
+ def length_bytes(data):
141
+ return len(to_bytes(data))
docrypt/plus.py ADDED
@@ -0,0 +1,61 @@
1
+ import hashlib
2
+ import hmac as hmac_module
3
+
4
+ from .aes import decrypt_aes, encrypt_aes
5
+ from .decrypt import decrypt
6
+ from .m import derive_key
7
+ from .maincrypt import crypt
8
+ from .p import to_bytes
9
+ from .z import random_bytes, secure_compare
10
+
11
+ WRAPPED_KEY_SIZE = 12 + 16 + 32
12
+
13
+
14
+ def sign_data(data, *keys):
15
+ if not keys:
16
+ raise ValueError("at least one key is required")
17
+ key = derive_key(*keys, info=b"docrypt-sign")
18
+ return hmac_module.new(key, to_bytes(data), hashlib.sha512).digest()
19
+
20
+
21
+ def sign_data_hex(data, *keys):
22
+ return sign_data(data, *keys).hex()
23
+
24
+
25
+ def verify_signature(data, signature, *keys):
26
+ expected = sign_data(data, *keys)
27
+ return secure_compare(expected, to_bytes(signature))
28
+
29
+
30
+ def crypt_and_sign(main, *keys):
31
+ token = crypt(main, *keys)
32
+ return token, sign_data(token, *keys)
33
+
34
+
35
+ def decrypt_and_verify(main, signature, *keys):
36
+ if not verify_signature(main, signature, *keys):
37
+ raise ValueError("signature verification failed")
38
+ return decrypt(main, *keys)
39
+
40
+
41
+ def envelope_seal(main, *keys):
42
+ if not keys:
43
+ raise ValueError("at least one key is required")
44
+ session_key = random_bytes(32)
45
+ master_key = derive_key(*keys, info=b"docrypt-envelope")
46
+ body = encrypt_aes(main, session_key)
47
+ wrapped = encrypt_aes(session_key, master_key)
48
+ return wrapped + body
49
+
50
+
51
+ def envelope_open(token, *keys):
52
+ if not keys:
53
+ raise ValueError("at least one key is required")
54
+ token = to_bytes(token)
55
+ if len(token) < WRAPPED_KEY_SIZE + 12 + 16:
56
+ raise ValueError("invalid token size")
57
+ wrapped = token[:WRAPPED_KEY_SIZE]
58
+ body = token[WRAPPED_KEY_SIZE:]
59
+ master_key = derive_key(*keys, info=b"docrypt-envelope")
60
+ session_key = decrypt_aes(wrapped, master_key)
61
+ return decrypt_aes(body, session_key)
docrypt/rych.py ADDED
@@ -0,0 +1,67 @@
1
+ import hashlib
2
+ import hmac as hmac_module
3
+ import secrets
4
+
5
+ from .p import from_hex, to_bytes, to_hex
6
+
7
+ DIGEST_SIZE = 32
8
+ NONCE_SIZE = 12
9
+
10
+
11
+ def rych_hash(data):
12
+ return hashlib.sha256(to_bytes(data)).hexdigest()
13
+
14
+
15
+ def rych_mac(key, data):
16
+ return hmac_module.new(to_bytes(key), to_bytes(data), hashlib.sha256).hexdigest()
17
+
18
+
19
+ def rych_derive(*keys):
20
+ if not keys:
21
+ raise ValueError("at least one key is required")
22
+ parts = [hashlib.sha256(to_bytes(item)).digest() for item in keys]
23
+ return hashlib.sha256(b"".join(parts)).digest()
24
+
25
+
26
+ def rych_keystream(key, nonce, length):
27
+ if length <= 0:
28
+ raise ValueError("length must be positive")
29
+ stream = bytearray()
30
+ counter = 0
31
+ while len(stream) < length:
32
+ stream.extend(hmac_module.new(key, nonce + counter.to_bytes(8, "big"), hashlib.sha256).digest())
33
+ counter += 1
34
+ return bytes(stream[:length])
35
+
36
+
37
+ def rych_crypt(main, *keys):
38
+ key = rych_derive(*keys)
39
+ raw = to_bytes(main)
40
+ nonce = secrets.token_bytes(NONCE_SIZE)
41
+ stream = rych_keystream(key, nonce, len(raw))
42
+ ciphertext = bytes(a ^ b for a, b in zip(raw, stream))
43
+ body = nonce + ciphertext
44
+ return body + hmac_module.new(key, body, hashlib.sha256).digest()
45
+
46
+
47
+ def rych_decrypt(main, *keys):
48
+ key = rych_derive(*keys)
49
+ token = to_bytes(main)
50
+ if len(token) < NONCE_SIZE + DIGEST_SIZE:
51
+ raise ValueError("invalid token size")
52
+ body = token[:-DIGEST_SIZE]
53
+ digest = token[-DIGEST_SIZE:]
54
+ if not hmac_module.compare_digest(hmac_module.new(key, body, hashlib.sha256).digest(), digest):
55
+ raise ValueError("decryption failed, invalid key or corrupted data")
56
+ nonce = body[:NONCE_SIZE]
57
+ ciphertext = body[NONCE_SIZE:]
58
+ stream = rych_keystream(key, nonce, len(ciphertext))
59
+ return bytes(a ^ b for a, b in zip(ciphertext, stream))
60
+
61
+
62
+ def rych_crypt_hex(main, *keys):
63
+ return to_hex(rych_crypt(main, *keys))
64
+
65
+
66
+ def rych_decrypt_hex(main, *keys):
67
+ return rych_decrypt(from_hex(main), *keys)
docrypt/sha256.py ADDED
@@ -0,0 +1,64 @@
1
+ import hashlib
2
+ import hmac as hmac_module
3
+
4
+ from .p import to_bytes
5
+
6
+
7
+ def hash_sha256(data):
8
+ return hashlib.sha256(to_bytes(data)).hexdigest()
9
+
10
+
11
+ def hash_sha256_bytes(data):
12
+ return hashlib.sha256(to_bytes(data)).digest()
13
+
14
+
15
+ def hmac_sha256(key, data):
16
+ return hmac_module.new(to_bytes(key), to_bytes(data), hashlib.sha256).hexdigest()
17
+
18
+
19
+ def hmac_sha256_bytes(key, data):
20
+ return hmac_module.new(to_bytes(key), to_bytes(data), hashlib.sha256).digest()
21
+
22
+
23
+ def double_sha256(data):
24
+ return hashlib.sha256(hashlib.sha256(to_bytes(data)).digest()).hexdigest()
25
+
26
+
27
+ def double_sha256_bytes(data):
28
+ return hashlib.sha256(hashlib.sha256(to_bytes(data)).digest()).digest()
29
+
30
+
31
+ def sha256_rounds(data, rounds=2):
32
+ if rounds < 1:
33
+ raise ValueError("rounds must be at least 1")
34
+ digest = hashlib.sha256(to_bytes(data)).digest()
35
+ for _ in range(rounds - 1):
36
+ digest = hashlib.sha256(digest).digest()
37
+ return digest.hex()
38
+
39
+
40
+ def sha256_rounds_bytes(data, rounds=2):
41
+ if rounds < 1:
42
+ raise ValueError("rounds must be at least 1")
43
+ digest = hashlib.sha256(to_bytes(data)).digest()
44
+ for _ in range(rounds - 1):
45
+ digest = hashlib.sha256(digest).digest()
46
+ return digest
47
+
48
+
49
+ def sha256_file(path, chunk_size=65536):
50
+ hasher = hashlib.sha256()
51
+ with open(path, "rb") as handle:
52
+ while True:
53
+ chunk = handle.read(chunk_size)
54
+ if not chunk:
55
+ break
56
+ hasher.update(chunk)
57
+ return hasher.hexdigest()
58
+
59
+
60
+ def verify_sha256(data, digest):
61
+ candidate = to_bytes(digest)
62
+ if len(candidate) == 32:
63
+ return hmac_module.compare_digest(hashlib.sha256(to_bytes(data)).digest(), candidate)
64
+ return hmac_module.compare_digest(hashlib.sha256(to_bytes(data)).hexdigest().encode("ascii"), candidate)
docrypt/sha512.py ADDED
@@ -0,0 +1,64 @@
1
+ import hashlib
2
+ import hmac as hmac_module
3
+
4
+ from .p import to_bytes
5
+
6
+
7
+ def hash_sha512(data):
8
+ return hashlib.sha512(to_bytes(data)).hexdigest()
9
+
10
+
11
+ def hash_sha512_bytes(data):
12
+ return hashlib.sha512(to_bytes(data)).digest()
13
+
14
+
15
+ def hmac_sha512(key, data):
16
+ return hmac_module.new(to_bytes(key), to_bytes(data), hashlib.sha512).hexdigest()
17
+
18
+
19
+ def hmac_sha512_bytes(key, data):
20
+ return hmac_module.new(to_bytes(key), to_bytes(data), hashlib.sha512).digest()
21
+
22
+
23
+ def double_sha512(data):
24
+ return hashlib.sha512(hashlib.sha512(to_bytes(data)).digest()).hexdigest()
25
+
26
+
27
+ def double_sha512_bytes(data):
28
+ return hashlib.sha512(hashlib.sha512(to_bytes(data)).digest()).digest()
29
+
30
+
31
+ def sha512_rounds(data, rounds=2):
32
+ if rounds < 1:
33
+ raise ValueError("rounds must be at least 1")
34
+ digest = hashlib.sha512(to_bytes(data)).digest()
35
+ for _ in range(rounds - 1):
36
+ digest = hashlib.sha512(digest).digest()
37
+ return digest.hex()
38
+
39
+
40
+ def sha512_rounds_bytes(data, rounds=2):
41
+ if rounds < 1:
42
+ raise ValueError("rounds must be at least 1")
43
+ digest = hashlib.sha512(to_bytes(data)).digest()
44
+ for _ in range(rounds - 1):
45
+ digest = hashlib.sha512(digest).digest()
46
+ return digest
47
+
48
+
49
+ def sha512_file(path, chunk_size=65536):
50
+ hasher = hashlib.sha512()
51
+ with open(path, "rb") as handle:
52
+ while True:
53
+ chunk = handle.read(chunk_size)
54
+ if not chunk:
55
+ break
56
+ hasher.update(chunk)
57
+ return hasher.hexdigest()
58
+
59
+
60
+ def verify_sha512(data, digest):
61
+ candidate = to_bytes(digest)
62
+ if len(candidate) == 64:
63
+ return hmac_module.compare_digest(hashlib.sha512(to_bytes(data)).digest(), candidate)
64
+ return hmac_module.compare_digest(hashlib.sha512(to_bytes(data)).hexdigest().encode("ascii"), candidate)
docrypt/xor.py ADDED
@@ -0,0 +1,41 @@
1
+ import hashlib
2
+ import hmac as hmac_module
3
+
4
+ from .p import from_hex, to_bytes, to_hex
5
+
6
+
7
+ def make_keystream(key, length):
8
+ if length <= 0:
9
+ raise ValueError("length must be positive")
10
+ key = to_bytes(key)
11
+ stream = bytearray()
12
+ counter = 0
13
+ while len(stream) < length:
14
+ stream.extend(hmac_module.new(key, counter.to_bytes(8, "big"), hashlib.sha512).digest())
15
+ counter += 1
16
+ return bytes(stream[:length])
17
+
18
+
19
+ def xor_bytes(first, second):
20
+ first = to_bytes(first)
21
+ second = to_bytes(second)
22
+ if len(first) != len(second):
23
+ raise ValueError("operands must have equal length")
24
+ return bytes(a ^ b for a, b in zip(first, second))
25
+
26
+
27
+ def xor_encrypt(data, key):
28
+ raw = to_bytes(data)
29
+ return xor_bytes(raw, make_keystream(key, len(raw)))
30
+
31
+
32
+ def xor_decrypt(data, key):
33
+ return xor_encrypt(data, key)
34
+
35
+
36
+ def xor_encrypt_hex(data, key):
37
+ return to_hex(xor_encrypt(data, key))
38
+
39
+
40
+ def xor_decrypt_hex(data, key):
41
+ return xor_decrypt(from_hex(data), key)
docrypt/z.py ADDED
@@ -0,0 +1,60 @@
1
+ import hmac
2
+ import secrets
3
+ import string
4
+
5
+ from .p import to_bytes
6
+
7
+
8
+ def random_bytes(length):
9
+ if not isinstance(length, int) or length <= 0:
10
+ raise ValueError("length must be a positive integer")
11
+ return secrets.token_bytes(length)
12
+
13
+
14
+ def random_hex(length):
15
+ if not isinstance(length, int) or length <= 0:
16
+ raise ValueError("length must be a positive integer")
17
+ return secrets.token_hex(length)
18
+
19
+
20
+ def random_int(lower, upper):
21
+ if lower > upper:
22
+ raise ValueError("lower bound must not exceed upper bound")
23
+ return secrets.randbelow(upper - lower + 1) + lower
24
+
25
+
26
+ def random_token(length=32):
27
+ if not isinstance(length, int) or length <= 0:
28
+ raise ValueError("length must be a positive integer")
29
+ alphabet = string.ascii_letters + string.digits
30
+ return "".join(secrets.choice(alphabet) for _ in range(length))
31
+
32
+
33
+ def generate_key(length=32):
34
+ if length not in (16, 24, 32):
35
+ raise ValueError("key length must be 16, 24 or 32 bytes")
36
+ return secrets.token_hex(length)
37
+
38
+
39
+ def generate_salt(length=16):
40
+ if not isinstance(length, int) or length <= 0:
41
+ raise ValueError("length must be a positive integer")
42
+ return secrets.token_bytes(length)
43
+
44
+
45
+ def generate_nonce(length=12):
46
+ if not isinstance(length, int) or length <= 0:
47
+ raise ValueError("length must be a positive integer")
48
+ return secrets.token_bytes(length)
49
+
50
+
51
+ def secure_compare(first, second):
52
+ return hmac.compare_digest(to_bytes(first), to_bytes(second))
53
+
54
+
55
+ def is_valid_key_length(key):
56
+ return len(to_bytes(key)) in (16, 24, 32)
57
+
58
+
59
+ def key_bit_length(key):
60
+ return len(to_bytes(key)) * 8