can link google accounts!
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""Encryption utilities for sensitive data."""
|
||||
import base64
|
||||
import os
|
||||
from cryptography.fernet import Fernet, InvalidToken
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
|
||||
|
||||
# Encryption key derivation settings
|
||||
SALT_LENGTH = 16
|
||||
KEY_ITERATIONS = 480000
|
||||
|
||||
|
||||
def _get_fernet_key(secret_key: str, salt: bytes = None) -> bytes:
|
||||
"""
|
||||
Derive a Fernet key from a secret key using PBKDF2.
|
||||
|
||||
Args:
|
||||
secret_key: The master secret key
|
||||
salt: Optional salt bytes (will be generated if not provided)
|
||||
|
||||
Returns:
|
||||
32-byte key suitable for Fernet encryption
|
||||
"""
|
||||
if salt is None:
|
||||
salt = os.urandom(SALT_LENGTH)
|
||||
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
iterations=KEY_ITERATIONS,
|
||||
)
|
||||
key = base64.urlsafe_b64encode(kdf.derive(secret_key.encode()))
|
||||
return key
|
||||
|
||||
|
||||
def encrypt(plaintext: str, secret_key: str = None) -> str:
|
||||
"""
|
||||
Encrypt a string using Fernet symmetric encryption.
|
||||
|
||||
Args:
|
||||
plaintext: The string to encrypt
|
||||
secret_key: The encryption key (uses app config if not provided)
|
||||
|
||||
Returns:
|
||||
Base64-encoded encrypted string with salt prepended
|
||||
"""
|
||||
from flask import current_app
|
||||
|
||||
if not plaintext:
|
||||
return ""
|
||||
|
||||
# Get secret key from app config or use provided key
|
||||
if secret_key is None:
|
||||
secret_key = current_app.config.get("ENCRYPTION_KEY", "")
|
||||
|
||||
if not secret_key:
|
||||
raise ValueError("Encryption key not configured")
|
||||
|
||||
# Generate a random salt for this encryption
|
||||
salt = os.urandom(SALT_LENGTH)
|
||||
fernet_key = _get_fernet_key(secret_key, salt)
|
||||
fernet = Fernet(fernet_key)
|
||||
|
||||
# Encrypt the plaintext
|
||||
encrypted_bytes = fernet.encrypt(plaintext.encode())
|
||||
|
||||
# Combine salt + encrypted data and base64 encode
|
||||
combined = salt + encrypted_bytes
|
||||
return base64.urlsafe_b64encode(combined).decode()
|
||||
|
||||
|
||||
def decrypt(encrypted_data: str, secret_key: str = None) -> str:
|
||||
"""
|
||||
Decrypt a string that was encrypted with the encrypt function.
|
||||
|
||||
Args:
|
||||
encrypted_data: Base64-encoded encrypted string with salt prepended
|
||||
secret_key: The encryption key (uses app config if not provided)
|
||||
|
||||
Returns:
|
||||
The original plaintext string
|
||||
"""
|
||||
from flask import current_app
|
||||
|
||||
if not encrypted_data:
|
||||
return ""
|
||||
|
||||
# Get secret key from app config or use provided key
|
||||
if secret_key is None:
|
||||
secret_key = current_app.config.get("ENCRYPTION_KEY", "")
|
||||
|
||||
if not secret_key:
|
||||
raise ValueError("Encryption key not configured")
|
||||
|
||||
try:
|
||||
# Decode from base64
|
||||
combined = base64.urlsafe_b64decode(encrypted_data.encode())
|
||||
|
||||
# Extract salt and encrypted data
|
||||
salt = combined[:SALT_LENGTH]
|
||||
encrypted_bytes = combined[SALT_LENGTH:]
|
||||
|
||||
# Derive the key and decrypt
|
||||
fernet_key = _get_fernet_key(secret_key, salt)
|
||||
fernet = Fernet(fernet_key)
|
||||
plaintext = fernet.decrypt(encrypted_bytes)
|
||||
|
||||
return plaintext.decode()
|
||||
except (InvalidToken, ValueError):
|
||||
raise ValueError("Failed to decrypt data - invalid key or corrupted data")
|
||||
Reference in New Issue
Block a user