代码上传
This commit is contained in:
4
hertz_studio_django_utils/crypto/__init__.py
Normal file
4
hertz_studio_django_utils/crypto/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .encryption_utils import EncryptionUtils
|
||||
from .password_hashers import MD5PasswordHasher
|
||||
|
||||
__all__ = ['EncryptionUtils', 'MD5PasswordHasher']
|
||||
160
hertz_studio_django_utils/crypto/encryption_utils.py
Normal file
160
hertz_studio_django_utils/crypto/encryption_utils.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
from typing import Optional
|
||||
from cryptography.fernet import Fernet
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
|
||||
|
||||
class EncryptionUtils:
|
||||
"""
|
||||
加密工具类
|
||||
提供各种加密解密功能
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def generate_salt(length: int = 32) -> str:
|
||||
"""
|
||||
生成随机盐值
|
||||
|
||||
Args:
|
||||
length: 盐值长度
|
||||
|
||||
Returns:
|
||||
str: Base64编码的盐值
|
||||
"""
|
||||
salt = secrets.token_bytes(length)
|
||||
return base64.b64encode(salt).decode('utf-8')
|
||||
|
||||
@staticmethod
|
||||
def md5_hash(data: str, salt: str = '') -> str:
|
||||
"""
|
||||
MD5哈希加密
|
||||
|
||||
Args:
|
||||
data: 待加密数据
|
||||
salt: 盐值
|
||||
|
||||
Returns:
|
||||
str: MD5哈希值
|
||||
"""
|
||||
combined = data + salt
|
||||
md5_hash = hashlib.md5(combined.encode('utf-8'))
|
||||
return md5_hash.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def sha256_hash(data: str, salt: str = '') -> str:
|
||||
"""
|
||||
SHA256哈希加密
|
||||
|
||||
Args:
|
||||
data: 待加密数据
|
||||
salt: 盐值
|
||||
|
||||
Returns:
|
||||
str: SHA256哈希值
|
||||
"""
|
||||
combined = data + salt
|
||||
sha256_hash = hashlib.sha256(combined.encode('utf-8'))
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def generate_key_from_password(password: str, salt: bytes) -> bytes:
|
||||
"""
|
||||
从密码生成加密密钥
|
||||
|
||||
Args:
|
||||
password: 密码
|
||||
salt: 盐值
|
||||
|
||||
Returns:
|
||||
bytes: 加密密钥
|
||||
"""
|
||||
kdf = PBKDF2HMAC(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=32,
|
||||
salt=salt,
|
||||
iterations=100000,
|
||||
)
|
||||
key = base64.urlsafe_b64encode(kdf.derive(password.encode()))
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def encrypt_data(data: str, password: str) -> Optional[str]:
|
||||
"""
|
||||
使用密码加密数据
|
||||
|
||||
Args:
|
||||
data: 待加密数据
|
||||
password: 加密密码
|
||||
|
||||
Returns:
|
||||
Optional[str]: 加密后的数据(Base64编码),失败返回None
|
||||
"""
|
||||
try:
|
||||
# 生成随机盐值
|
||||
salt = secrets.token_bytes(16)
|
||||
|
||||
# 从密码生成密钥
|
||||
key = EncryptionUtils.generate_key_from_password(password, salt)
|
||||
|
||||
# 创建Fernet实例
|
||||
fernet = Fernet(key)
|
||||
|
||||
# 加密数据
|
||||
encrypted_data = fernet.encrypt(data.encode('utf-8'))
|
||||
|
||||
# 将盐值和加密数据组合
|
||||
combined = salt + encrypted_data
|
||||
|
||||
# 返回Base64编码的结果
|
||||
return base64.b64encode(combined).decode('utf-8')
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def decrypt_data(encrypted_data: str, password: str) -> Optional[str]:
|
||||
"""
|
||||
使用密码解密数据
|
||||
|
||||
Args:
|
||||
encrypted_data: 加密后的数据(Base64编码)
|
||||
password: 解密密码
|
||||
|
||||
Returns:
|
||||
Optional[str]: 解密后的数据,失败返回None
|
||||
"""
|
||||
try:
|
||||
# Base64解码
|
||||
combined = base64.b64decode(encrypted_data.encode('utf-8'))
|
||||
|
||||
# 分离盐值和加密数据
|
||||
salt = combined[:16]
|
||||
encrypted_bytes = combined[16:]
|
||||
|
||||
# 从密码生成密钥
|
||||
key = EncryptionUtils.generate_key_from_password(password, salt)
|
||||
|
||||
# 创建Fernet实例
|
||||
fernet = Fernet(key)
|
||||
|
||||
# 解密数据
|
||||
decrypted_data = fernet.decrypt(encrypted_bytes)
|
||||
|
||||
return decrypted_data.decode('utf-8')
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def generate_random_key() -> str:
|
||||
"""
|
||||
生成随机加密密钥
|
||||
|
||||
Returns:
|
||||
str: Base64编码的随机密钥
|
||||
"""
|
||||
key = Fernet.generate_key()
|
||||
return key.decode('utf-8')
|
||||
79
hertz_studio_django_utils/crypto/password_hashers.py
Normal file
79
hertz_studio_django_utils/crypto/password_hashers.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import hashlib
|
||||
from django.contrib.auth.hashers import BasePasswordHasher
|
||||
from django.utils.crypto import constant_time_compare
|
||||
|
||||
|
||||
class MD5PasswordHasher(BasePasswordHasher):
|
||||
"""
|
||||
MD5密码哈希器
|
||||
用于兼容旧系统的MD5密码加密
|
||||
"""
|
||||
algorithm = 'md5'
|
||||
library = 'hashlib'
|
||||
|
||||
def encode(self, password, salt):
|
||||
"""
|
||||
编码密码
|
||||
|
||||
Args:
|
||||
password: 原始密码
|
||||
salt: 盐值
|
||||
|
||||
Returns:
|
||||
str: 编码后的密码
|
||||
"""
|
||||
hash_obj = hashlib.md5((salt + password).encode('utf-8'))
|
||||
hash_value = hash_obj.hexdigest()
|
||||
return f'{self.algorithm}${salt}${hash_value}'
|
||||
|
||||
def verify(self, password, encoded):
|
||||
"""
|
||||
验证密码
|
||||
|
||||
Args:
|
||||
password: 原始密码
|
||||
encoded: 编码后的密码
|
||||
|
||||
Returns:
|
||||
bool: 验证结果
|
||||
"""
|
||||
algorithm, salt, hash_value = encoded.split('$', 2)
|
||||
assert algorithm == self.algorithm
|
||||
encoded_2 = self.encode(password, salt)
|
||||
return constant_time_compare(encoded, encoded_2)
|
||||
|
||||
def safe_summary(self, encoded):
|
||||
"""
|
||||
返回密码的安全摘要信息
|
||||
|
||||
Args:
|
||||
encoded: 编码后的密码
|
||||
|
||||
Returns:
|
||||
dict: 摘要信息
|
||||
"""
|
||||
algorithm, salt, hash_value = encoded.split('$', 2)
|
||||
assert algorithm == self.algorithm
|
||||
return {
|
||||
'algorithm': algorithm,
|
||||
'salt': salt[:6] + '...',
|
||||
'hash': hash_value[:6] + '...',
|
||||
}
|
||||
|
||||
def harden_runtime(self, password, encoded):
|
||||
"""
|
||||
硬化运行时间(MD5不需要)
|
||||
"""
|
||||
pass
|
||||
|
||||
def must_update(self, encoded):
|
||||
"""
|
||||
检查是否需要更新密码编码
|
||||
|
||||
Args:
|
||||
encoded: 编码后的密码
|
||||
|
||||
Returns:
|
||||
bool: 是否需要更新
|
||||
"""
|
||||
return False
|
||||
Reference in New Issue
Block a user