refactor(security): improve encryption using PyCryptodome and PBKDF2
Replace the `cryptography` library with `pycryptodome` for password encryption. The previous implementation used AES-GCM with a static key derived from a hardcoded secret. This change introduces a more robust security model by: - Using PBKDF2 to derive the encryption key from the secret. - Adding a unique, randomly generated salt for each encrypted password. This significantly enhances security by protecting against rainbow table and pre-computation attacks. BREAKING CHANGE: The password encryption format has changed. All previously encrypted passwords stored in the database are now invalid and will need to be reset.
This commit is contained in:
parent
79a2f6e944
commit
aa1b9d7281
@ -1,30 +1,26 @@
|
|||||||
import os
|
import os
|
||||||
import hashlib
|
from Crypto.Cipher import AES
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
from Crypto.Protocol.KDF import PBKDF2
|
||||||
|
from Crypto.Random import get_random_bytes
|
||||||
|
|
||||||
SECRET = "BBLBTV-DNS-PASSWORDS"
|
SECRET = "BBLBTV-DNS-PASSWORDS"
|
||||||
KEY = hashlib.sha256(SECRET.encode()).digest()
|
SALT_SIZE = 16
|
||||||
ALGORITHM = "aes-256-gcm"
|
KEY_SIZE = 32
|
||||||
IV_LENGTH = 16
|
ITERATIONS = 100000
|
||||||
AUTH_TAG_LENGTH = 16
|
|
||||||
|
|
||||||
def encrypt_password(clear_string):
|
def encrypt_password(clear_string):
|
||||||
iv = os.urandom(IV_LENGTH)
|
salt = get_random_bytes(SALT_SIZE)
|
||||||
aesgcm = AESGCM(KEY)
|
key = PBKDF2(SECRET, salt, dkLen=KEY_SIZE, count=ITERATIONS)
|
||||||
|
cipher = AES.new(key, AES.MODE_GCM)
|
||||||
ciphertext_and_tag = aesgcm.encrypt(iv, clear_string.encode(), None)
|
ciphertext, tag = cipher.encrypt_and_digest(clear_string.encode())
|
||||||
ciphertext = ciphertext_and_tag[:-AUTH_TAG_LENGTH]
|
return (salt + cipher.nonce + tag + ciphertext).hex()
|
||||||
tag = ciphertext_and_tag[-AUTH_TAG_LENGTH:]
|
|
||||||
|
|
||||||
return (iv + tag + ciphertext).hex()
|
|
||||||
|
|
||||||
def decrypt_password(encrypted_string):
|
def decrypt_password(encrypted_string):
|
||||||
data = bytes.fromhex(encrypted_string)
|
data = bytes.fromhex(encrypted_string)
|
||||||
|
salt = data[:SALT_SIZE]
|
||||||
iv = data[:IV_LENGTH]
|
nonce = data[SALT_SIZE:SALT_SIZE + 16]
|
||||||
tag = data[IV_LENGTH:IV_LENGTH + AUTH_TAG_LENGTH]
|
tag = data[SALT_SIZE + 16:SALT_SIZE + 32]
|
||||||
ciphertext = data[IV_LENGTH + AUTH_TAG_LENGTH:]
|
ciphertext = data[SALT_SIZE + 32:]
|
||||||
|
key = PBKDF2(SECRET, salt, dkLen=KEY_SIZE, count=ITERATIONS)
|
||||||
aesgcm = AESGCM(KEY)
|
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
||||||
decrypted_bytes = aesgcm.decrypt(iv, ciphertext + tag, None)
|
return cipher.decrypt_and_verify(ciphertext, tag).decode()
|
||||||
return decrypted_bytes.decode()
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user