acesecurity 2.0.0.7__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.
- acecurity/__init__.py +2 -0
- acecurity/_cli.py +0 -0
- acecurity/_direct.py +112 -0
- acecurity/main.py +237 -0
- acecurity/passwords.py +1589 -0
- acecurity/rand.py +496 -0
- acecurity/tests/__init__.py +0 -0
- acecurity/tests/test_crypto_cryptography.py +162 -0
- acecurity/tests/test_crypto_pycryptodomex.py +98 -0
- acecurity/tests/test_passwords.py +195 -0
- acecurity/tests/test_rand.py +422 -0
- acesecurity-2.0.0.7.dist-info/METADATA +27 -0
- acesecurity-2.0.0.7.dist-info/RECORD +17 -0
- acesecurity-2.0.0.7.dist-info/WHEEL +5 -0
- acesecurity-2.0.0.7.dist-info/entry_points.txt +2 -0
- acesecurity-2.0.0.7.dist-info/licenses/LICENSE +674 -0
- acesecurity-2.0.0.7.dist-info/top_level.txt +1 -0
acecurity/__init__.py
ADDED
acecurity/_cli.py
ADDED
|
File without changes
|
acecurity/_direct.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""TBA"""
|
|
2
|
+
|
|
3
|
+
from enum import Enum as _Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing_extensions import deprecated
|
|
7
|
+
|
|
8
|
+
# Standard typing imports for aps
|
|
9
|
+
import typing_extensions as _te
|
|
10
|
+
import collections.abc as _a
|
|
11
|
+
import typing as _ty
|
|
12
|
+
|
|
13
|
+
if _ty.TYPE_CHECKING:
|
|
14
|
+
import _typeshed as _tsh
|
|
15
|
+
import types as _ts
|
|
16
|
+
|
|
17
|
+
__all__ = ["GenericLabeledEnum", "EAN", "Security", "RiskLevel"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GenericLabeledEnum(_Enum):
|
|
21
|
+
value: _ty.Any
|
|
22
|
+
label: str
|
|
23
|
+
|
|
24
|
+
def __new__(cls, value: _ty.Any, label: str):
|
|
25
|
+
# Construct a base instance based on value type
|
|
26
|
+
obj = object.__new__(cls) # <-- FIXED: safe for nested or complex types
|
|
27
|
+
obj._value_ = value
|
|
28
|
+
obj.label = label
|
|
29
|
+
return obj
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return self.label # printed or str() shows label
|
|
33
|
+
|
|
34
|
+
def __repr__(self) -> str:
|
|
35
|
+
return f"{self.__class__.__name__}.{self.name}({repr(self.value)}, {repr(self.label)})"
|
|
36
|
+
|
|
37
|
+
def __int__(self) -> int:
|
|
38
|
+
try:
|
|
39
|
+
return int(self.value)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise TypeError("Cannot convert non-int value to int") from e
|
|
42
|
+
|
|
43
|
+
def __index__(self) -> int:
|
|
44
|
+
print(f"Converting {self, type(self)} to int")
|
|
45
|
+
return int(self)
|
|
46
|
+
|
|
47
|
+
def __eq__(self, other: _ty.Any) -> bool:
|
|
48
|
+
return self.value == other or super().__eq__(other)
|
|
49
|
+
|
|
50
|
+
def __hash__(self) -> int:
|
|
51
|
+
return hash(self.value)
|
|
52
|
+
|
|
53
|
+
def __getattr__(self, attr):
|
|
54
|
+
# 🔥 Here's the key: delegate missing attrs to `.value`
|
|
55
|
+
try:
|
|
56
|
+
return getattr(self.value, attr)
|
|
57
|
+
except AttributeError:
|
|
58
|
+
raise AttributeError(
|
|
59
|
+
f"{self.__class__.__name__} object has no attribute '{attr}'"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# @deprecated("Please use GenericLabeledEnum instead")
|
|
64
|
+
class EAN:
|
|
65
|
+
def __init__(self, value: _ty.Any, info: str) -> None:
|
|
66
|
+
self.value = value
|
|
67
|
+
self.info = info
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return self.info
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Security: # Changed to indices for easy selection from iterables
|
|
74
|
+
"""Baseclass for different security levels"""
|
|
75
|
+
|
|
76
|
+
BASIC = EAN(
|
|
77
|
+
0,
|
|
78
|
+
"An attacker can reverse whatever if they have enough info on you pretty easily",
|
|
79
|
+
)
|
|
80
|
+
AVERAGE = EAN(1, "A lot better than basic")
|
|
81
|
+
STRONG = EAN(2, "Practically impossible to reverse or crack")
|
|
82
|
+
SUPER_STRONG = EAN(
|
|
83
|
+
3,
|
|
84
|
+
"Great security, but at the cost of comfort features like readability and efficiency",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
check_not_available: bool = True
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RiskLevel(GenericLabeledEnum):
|
|
91
|
+
"""Risk assessment for various parts of security"""
|
|
92
|
+
|
|
93
|
+
HARMLESS = (
|
|
94
|
+
None,
|
|
95
|
+
"Harmless: Considered secure, even with the threat of future quantum computers.",
|
|
96
|
+
)
|
|
97
|
+
NOT_RECOMMENDED = (
|
|
98
|
+
None,
|
|
99
|
+
"Not recommended: Generally secure but there are better or more modern alternatives.",
|
|
100
|
+
)
|
|
101
|
+
KNOWN_UNSAFE = (
|
|
102
|
+
None,
|
|
103
|
+
"Deprecated: Known vulnerabilities exist; should not be used.",
|
|
104
|
+
)
|
|
105
|
+
KNOWN_UNSAFE_NOT_RECOMMENDED = (
|
|
106
|
+
None,
|
|
107
|
+
"Deprecated, Not recommended: Combination of known issues and better alternatives.",
|
|
108
|
+
)
|
|
109
|
+
HIGHLY_DANGEROUS = (
|
|
110
|
+
None,
|
|
111
|
+
"Highly dangerous: Easily broken and should not be used under any circumstances.",
|
|
112
|
+
)
|
acecurity/main.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from acecurity.crypto import PasswordManager, set_backend, Backend, PQPasswordManager, DataEncryptor, DigitalSigner
|
|
3
|
+
from acecurity import Security
|
|
4
|
+
|
|
5
|
+
set_backend([Backend.cryptography])
|
|
6
|
+
|
|
7
|
+
# ret: bytes = PasswordManager.hash_password("Test", strength=Security.SUPER_STRONG)
|
|
8
|
+
# print("RET", ret)
|
|
9
|
+
# print(PasswordManager.verify_password("Test", ret))
|
|
10
|
+
|
|
11
|
+
# ret: bytes = PQPasswordManager.hash_password("Test", strength=Security.STRONG)
|
|
12
|
+
# set_backend([Backend.argon2_cffi])
|
|
13
|
+
# print(PQPasswordManager.verify_password("Test", ret))
|
|
14
|
+
|
|
15
|
+
# ec = DataEncryptor.generate()
|
|
16
|
+
# print(ec.get_key())
|
|
17
|
+
# crypt: bytes = ec.encrypt_data(b"Test data")
|
|
18
|
+
# print(crypt)
|
|
19
|
+
# key: bytes = ec.get_key()
|
|
20
|
+
# new_ec = ec.from_key(key)
|
|
21
|
+
# print(new_ec.decrypt_data(crypt))
|
|
22
|
+
|
|
23
|
+
sign = DigitalSigner.generate()
|
|
24
|
+
print(sign.get_private_key())
|
|
25
|
+
signature: bytes = sign.sign_data(b"My data")
|
|
26
|
+
print("SIG", signature)
|
|
27
|
+
print(sign.verify_signature(b"My data", signature))
|
|
28
|
+
|
|
29
|
+
sys.exit(0)
|
|
30
|
+
|
|
31
|
+
from acecurity.tests.test_rand import test_random_generators, test_weighted_functions
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
test_random_generators()
|
|
35
|
+
# test_weighted_functions()
|
|
36
|
+
|
|
37
|
+
from acecurity.passwords import SecurePasswordGenerator
|
|
38
|
+
|
|
39
|
+
generator = SecurePasswordGenerator(security="super_strong")
|
|
40
|
+
pw_data = generator.passphrase()
|
|
41
|
+
|
|
42
|
+
print(pw_data["password"]) # Actual password
|
|
43
|
+
print(pw_data["extra_info"]) # Generation method metadata
|
|
44
|
+
|
|
45
|
+
generator.sentence() # Readable sentence
|
|
46
|
+
generator.pattern() # Format like Aa99##
|
|
47
|
+
generator.complex_pattern() # Pattern + random word mix
|
|
48
|
+
generator.complex() # High entropy, mixed format
|
|
49
|
+
generator.mnemonic() # Easy to remember
|
|
50
|
+
|
|
51
|
+
pw_data = generator.generate_secure_password(return_worst_case=True)
|
|
52
|
+
print(pw_data["worst_case"]) # e.g., "centuries"
|
|
53
|
+
|
|
54
|
+
from acecurity.crypto.algos import (
|
|
55
|
+
Sym,
|
|
56
|
+
Asym,
|
|
57
|
+
HashAlgorithm,
|
|
58
|
+
KeyDerivationFunction,
|
|
59
|
+
)
|
|
60
|
+
from acecurity.crypto.exceptions import NotSupportedError
|
|
61
|
+
from acecurity.crypto import set_backend, Backend
|
|
62
|
+
import os
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
set_backend() # Uses Backend.std_lib
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# MD5
|
|
69
|
+
h = HashAlgorithm.MD5.hash(b"hello world")
|
|
70
|
+
print("MD5:", h.hex())
|
|
71
|
+
print("Verify:", HashAlgorithm.MD5.verify(b"hello world", h))
|
|
72
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello world", h))
|
|
73
|
+
|
|
74
|
+
# SHA1
|
|
75
|
+
h = HashAlgorithm.SHA1.hash(b"hello world")
|
|
76
|
+
print("SHA1:", h.hex())
|
|
77
|
+
print("Verify:", HashAlgorithm.SHA1.verify(b"hello world", h))
|
|
78
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello world", h))
|
|
79
|
+
|
|
80
|
+
# SHA256
|
|
81
|
+
h = HashAlgorithm.SHA2.SHA256.hash(b"hello world")
|
|
82
|
+
print("SHA256:", h.hex())
|
|
83
|
+
print("Verify:", HashAlgorithm.SHA2.SHA256.verify(b"hello world", h))
|
|
84
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello world", h))
|
|
85
|
+
|
|
86
|
+
# SHA3-256
|
|
87
|
+
h = HashAlgorithm.SHA3.SHA256.hash(b"hello world")
|
|
88
|
+
print("SHA3-256:", h.hex())
|
|
89
|
+
print("Verify:", HashAlgorithm.SHA3.SHA256.verify(b"hello world", h))
|
|
90
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello world", h))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# SHAKE128 (8 bytes)
|
|
94
|
+
h = HashAlgorithm.SHA3.SHAKE128.hash(b"hello", 8)
|
|
95
|
+
print("SHAKE128:", h.hex())
|
|
96
|
+
print("Verify:", HashAlgorithm.SHA3.SHAKE128.verify(b"hello", h))
|
|
97
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello", h))
|
|
98
|
+
|
|
99
|
+
# BLAKE2s (8 bytes)
|
|
100
|
+
h = HashAlgorithm.BLAKE2.BLAKE2s.hash(b"hello", 8)
|
|
101
|
+
print("BLAKE2s:", h.hex())
|
|
102
|
+
print("Verify:", HashAlgorithm.BLAKE2.BLAKE2s.verify(b"hello", h))
|
|
103
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello", h))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# RIPEMD160
|
|
107
|
+
try:
|
|
108
|
+
h = HashAlgorithm.RIPEMD160.hash(b"hello")
|
|
109
|
+
print("RIPEMD160:", h.hex())
|
|
110
|
+
print("Verify:", HashAlgorithm.RIPEMD160.verify(b"hello", h))
|
|
111
|
+
print("Std-Verify:", HashAlgorithm.std_verify(b"hello", h))
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print("RIPEMD160 unsupported in std_lib:", e)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
password = b"my-password"
|
|
117
|
+
salt = os.urandom(16)
|
|
118
|
+
|
|
119
|
+
print("PBKDF2HMAC:", KeyDerivationFunction.PBKDF2HMAC.derive(password, salt=salt).hex())
|
|
120
|
+
print("PBKDF1 :", KeyDerivationFunction.PBKDF1.derive(password, salt=salt, length=16).hex())
|
|
121
|
+
print("Scrypt :", KeyDerivationFunction.Scrypt.derive(password, salt=salt, length=16).hex())
|
|
122
|
+
print("HKDF :", KeyDerivationFunction.HKDF.derive(password, salt=salt).hex())
|
|
123
|
+
print("ConcatKDF :", KeyDerivationFunction.ConcatKDF.derive(password, otherinfo=b"my-info").hex())
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
set_backend(
|
|
127
|
+
[
|
|
128
|
+
Backend.argon2_cffi, # Required for Argon2; Argon2 is also supported by cryptography but not 100% so we put quantcrypt first
|
|
129
|
+
Backend.cryptography,
|
|
130
|
+
Backend.pycryptodomex,
|
|
131
|
+
Backend.quantcrypt, # To enable post-quantum cryptography
|
|
132
|
+
Backend.bcrypt, # Required for BCrypt
|
|
133
|
+
Backend.std_lib, # Fallback
|
|
134
|
+
]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# Hash a password/message using Argon2
|
|
139
|
+
hashed = HashAlgorithm.ARGON2.hash(b"Ha", os.urandom(16))
|
|
140
|
+
print("\nArgon2 Hash:", hashed.decode())
|
|
141
|
+
|
|
142
|
+
# Verify the hash
|
|
143
|
+
is_valid = HashAlgorithm.ARGON2.verify(b"Ha", hashed)
|
|
144
|
+
print("Argon2 Valid:", is_valid)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
print(
|
|
148
|
+
"Std-Verify",
|
|
149
|
+
HashAlgorithm.std_verify(
|
|
150
|
+
b"Ha", hashed, fallback_algorithm="argon2", text_ids=False
|
|
151
|
+
),
|
|
152
|
+
) # Std-Verify can't decode special algos like argon2 or bcrypt
|
|
153
|
+
except NotSupportedError:
|
|
154
|
+
print("Std-Verify failed")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# Hash a password with BCrypt
|
|
158
|
+
bcrypt_hash = HashAlgorithm.BCRYPT.hash(b"my-secret-password")
|
|
159
|
+
print("BCrypt Hash:", bcrypt_hash.decode())
|
|
160
|
+
|
|
161
|
+
# Verify the password against the hash
|
|
162
|
+
is_valid = HashAlgorithm.BCRYPT.verify(b"my-secret-password", bcrypt_hash)
|
|
163
|
+
print("BCrypt Valid:", is_valid)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Derive a key using Argon2 KDF
|
|
167
|
+
derived_key = KeyDerivationFunction.ARGON2.derive(b"my-password", salt=os.urandom(16))
|
|
168
|
+
print("Argon2 Derived Key:", derived_key.hex())
|
|
169
|
+
|
|
170
|
+
# Derive a key using BCrypt KDF
|
|
171
|
+
bcrypt_key = KeyDerivationFunction.BCRYPT.derive(b"my-password", salt=os.urandom(16))
|
|
172
|
+
print("BCrypt Derived Key:", bcrypt_key.hex())
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Recipient generates a keypair
|
|
176
|
+
recipient_key = Asym.Cipher.KYBER.keypair.new("kyber1024")
|
|
177
|
+
|
|
178
|
+
# Extract public key from recipient and share it with the sender
|
|
179
|
+
pub_key_bytes = recipient_key.encode_public_key()
|
|
180
|
+
# Keys can't be regenerated and try: except: takes more space;
|
|
181
|
+
# This can only happen if you do not pass one of the keys when using .decode( ... )
|
|
182
|
+
if pub_key_bytes is None:
|
|
183
|
+
raise ValueError("recipient_key has no public key")
|
|
184
|
+
|
|
185
|
+
# Sender receives the public key and creates a key object with only the public key
|
|
186
|
+
sender_key = Asym.Cipher.KYBER.keypair.decode("kyber1024", public_key=pub_key_bytes)
|
|
187
|
+
# Sender encapsulates a shared secret for the recipient
|
|
188
|
+
ciphertext, shared_secret_sender = sender_key.encapsulate()
|
|
189
|
+
|
|
190
|
+
# Recipient decapsulates to recover the shared secret
|
|
191
|
+
shared_secret_recipient = recipient_key.decapsulate(ciphertext)
|
|
192
|
+
|
|
193
|
+
print("\n=== Kyber KEM Flow ===")
|
|
194
|
+
print(f"Ciphertext : {ciphertext.hex()}")
|
|
195
|
+
print(f"Sender Shared Secret : {shared_secret_sender.hex()}")
|
|
196
|
+
print(f"Recipient Shared Secret: {shared_secret_recipient.hex()}")
|
|
197
|
+
assert shared_secret_sender == shared_secret_recipient, "Shared secrets do not match!"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Generate the signing keypair (private + public)
|
|
201
|
+
sign_key = Asym.Cipher.DILITHIUM.keypair.new("dilithium5")
|
|
202
|
+
|
|
203
|
+
# Sign a message using the private key
|
|
204
|
+
message = b"Hello World"
|
|
205
|
+
signature = sign_key.sign(message)
|
|
206
|
+
|
|
207
|
+
# Extract and share only the public key
|
|
208
|
+
pub_key_bytes = sign_key.encode_public_key()
|
|
209
|
+
# Keys can't be regenerated and try: except: takes more space;
|
|
210
|
+
# This can only happen if you do not pass one of the keys when using .decode( ... )
|
|
211
|
+
if pub_key_bytes is None:
|
|
212
|
+
raise ValueError("sign_key has no public key")
|
|
213
|
+
|
|
214
|
+
# Create a new key object with only the public key for verification
|
|
215
|
+
verify_key = Asym.Cipher.DILITHIUM.keypair.decode(
|
|
216
|
+
"dilithium5", public_key=pub_key_bytes
|
|
217
|
+
)
|
|
218
|
+
# Verify the signature using the public key
|
|
219
|
+
is_valid = verify_key.sign_verify(message, signature)
|
|
220
|
+
|
|
221
|
+
print("\n=== Dilithium Signature Flow ===")
|
|
222
|
+
print(f"Signature : {signature.hex()}")
|
|
223
|
+
print(f"Signature Valid? {is_valid}")
|
|
224
|
+
assert is_valid, "Signature verification failed!"
|
|
225
|
+
|
|
226
|
+
set_backend(
|
|
227
|
+
[
|
|
228
|
+
Backend.cryptography,
|
|
229
|
+
]
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# AES test
|
|
233
|
+
crypt_key = Sym.Cipher.AES.key.new(128)
|
|
234
|
+
encrypted: bytes = crypt_key.encrypt(b"Hello World", Sym.Padding.PKCS7, Sym.Operation.GCM)
|
|
235
|
+
print(encrypted)
|
|
236
|
+
decrypted: bytes = crypt_key.decrypt(encrypted, Sym.Padding.PKCS7, Sym.Operation.GCM)
|
|
237
|
+
print(decrypted)
|