encrypt.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. """
  2. RSA+AES混合加密工具 - 支持密钥版本管理
  3. """
  4. import base64
  5. import json
  6. from typing import Dict, Any, Optional, Tuple
  7. from sqlalchemy.orm import Session
  8. from cryptography.hazmat.primitives import serialization
  9. from cryptography.hazmat.primitives.asymmetric import rsa, padding
  10. from cryptography.hazmat.primitives import hashes
  11. from cryptography.hazmat.backends import default_backend
  12. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  13. from cryptography.hazmat.primitives.padding import PKCS7
  14. from app.config import settings
  15. from app.core.logger import logger
  16. from app.models.api_key import APIKey
  17. from app.utils.timezone import beijing_now
  18. class RSAEncryptor:
  19. """RSA+AES混合加密器 - 支持多版本密钥"""
  20. def __init__(self):
  21. self.private_key: Optional[rsa.RSAPrivateKey] = None
  22. self.public_key: Optional[rsa.RSAPublicKey] = None
  23. self.current_key_version: int = 0
  24. self.key_cache: Dict[int, Tuple[str, str]] = {} # version -> (private_key, public_key)
  25. def generate_key_pair(self) -> tuple[str, str]:
  26. """生成密钥对"""
  27. private_key = rsa.generate_private_key(
  28. public_exponent=65537,
  29. key_size=2048,
  30. backend=default_backend()
  31. )
  32. public_key = private_key.public_key()
  33. # 序列化为PEM格式
  34. private_pem = private_key.private_bytes(
  35. encoding=serialization.Encoding.PEM,
  36. format=serialization.PrivateFormat.PKCS8,
  37. encryption_algorithm=serialization.NoEncryption()
  38. ).decode('utf-8')
  39. public_pem = public_key.public_bytes(
  40. encoding=serialization.Encoding.PEM,
  41. format=serialization.PublicFormat.SubjectPublicKeyInfo
  42. ).decode('utf-8')
  43. return private_pem, public_pem
  44. def load_private_key(self, private_key_pem: str, key_version: int = 0):
  45. """加载私钥"""
  46. try:
  47. self.private_key = serialization.load_pem_private_key(
  48. private_key_pem.encode(),
  49. password=None,
  50. backend=default_backend()
  51. )
  52. self.public_key = self.private_key.public_key()
  53. self.current_key_version = key_version
  54. logger.info(f"私钥加载成功,版本: {key_version}")
  55. except Exception as e:
  56. logger.exception("私钥加载失败")
  57. raise
  58. def load_key_by_version(self, db: Session, key_version: int) -> bool:
  59. """根据版本号加载密钥(主is_active,辅expire_at)"""
  60. try:
  61. # 查找指定版本的密钥,必须激活、未删除且未过期
  62. api_key = db.query(APIKey).filter(
  63. APIKey.key_version == key_version,
  64. APIKey.is_active == 1, # 必须激活
  65. APIKey.is_deleted == 0,
  66. APIKey.expire_at > beijing_now() # 且未过期
  67. ).first()
  68. if not api_key:
  69. logger.warning(f"未找到有效的密钥版本 {key_version}(可能未激活、已过期或已删除)")
  70. return False
  71. # 检查缓存是否仍然有效(激活状态可能已改变)
  72. if key_version in self.key_cache:
  73. cached_private_pem, cached_public_pem = self.key_cache[key_version]
  74. # 验证缓存内容是否与数据库完全一致
  75. if cached_private_pem == api_key.private_key and cached_public_pem == api_key.public_key:
  76. # 缓存有效,直接使用
  77. self.load_private_key(cached_private_pem, key_version)
  78. return True
  79. else:
  80. # 缓存已过期或不一致,清除旧缓存
  81. del self.key_cache[key_version]
  82. logger.info(f"清除过期缓存: 版本 {key_version}")
  83. self.load_private_key(api_key.private_key, key_version)
  84. self.key_cache[key_version] = (api_key.private_key, api_key.public_key)
  85. return True
  86. except Exception as e:
  87. logger.exception(f"加载密钥版本 {key_version} 失败")
  88. return False
  89. def get_current_public_key(self, db: Session) -> Tuple[str, int]:
  90. """获取当前激活的公钥(主is_active,辅expire_at)"""
  91. try:
  92. api_key = db.query(APIKey).filter(
  93. APIKey.is_active == 1, # 必须是激活状态
  94. APIKey.is_deleted == 0,
  95. APIKey.expire_at > beijing_now() # 且未过期
  96. ).order_by(APIKey.key_version.desc()).first()
  97. if api_key:
  98. return api_key.public_key, api_key.key_version
  99. logger.warning("未找到激活的有效公钥")
  100. return "", 0
  101. except Exception as e:
  102. logger.exception("获取当前公钥失败")
  103. return "", 0
  104. def decrypt(self, encrypted_data: str) -> Dict[str, Any]:
  105. """解密数据(支持RSA+AES混合加密和纯RSA加密)"""
  106. if not settings.ENCRYPT_ENABLED:
  107. return json.loads(encrypted_data)
  108. if not self.private_key:
  109. raise ValueError("私钥未加载")
  110. try:
  111. # Base64解码
  112. encrypted_bytes = base64.b64decode(encrypted_data)
  113. # 判断是混合加密还是纯RSA加密
  114. # 混合加密:加密的AES密钥(256字节) + IV(16字节) + 加密的数据
  115. # 纯RSA加密:直接是RSA加密的数据(通常小于256字节)
  116. # 如果数据长度大于256字节,认为是混合加密
  117. if len(encrypted_bytes) > 256:
  118. # 混合加密模式
  119. # 提取加密的AES密钥(前256字节)
  120. encrypted_aes_key = encrypted_bytes[:256]
  121. # 提取IV(接下来16字节)
  122. iv = encrypted_bytes[256:272]
  123. # 提取加密的数据(剩余部分)
  124. encrypted_payload = encrypted_bytes[272:]
  125. # 记录调试信息:各部分长度
  126. try:
  127. logger.debug(f"Encrypted total bytes: {len(encrypted_bytes)}, "
  128. f"enc_key_len: {len(encrypted_aes_key)}, iv_len: {len(iv)}, payload_len: {len(encrypted_payload)}")
  129. except Exception:
  130. pass
  131. # 使用RSA解密AES密钥
  132. aes_key = self.private_key.decrypt(
  133. encrypted_aes_key,
  134. padding.OAEP(
  135. mgf=padding.MGF1(algorithm=hashes.SHA256()),
  136. algorithm=hashes.SHA256(),
  137. label=None
  138. )
  139. )
  140. # 使用AES解密数据
  141. cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
  142. decryptor = cipher.decryptor()
  143. padded_data = decryptor.update(encrypted_payload) + decryptor.finalize()
  144. # 去除PKCS7填充
  145. unpadder = PKCS7(128).unpadder()
  146. decrypted_bytes = unpadder.update(padded_data) + unpadder.finalize()
  147. # 记录解密后字节预览,便于调试(限制长度,避免日志过大)
  148. try:
  149. preview = decrypted_bytes[:300]
  150. # 尝试以utf-8安全显示
  151. try:
  152. preview_text = preview.decode('utf-8', errors='replace')
  153. except Exception:
  154. preview_text = str(preview)
  155. logger.debug(f"Decrypted bytes preview (first 300): {preview_text}")
  156. logger.debug(f"Decrypted total length: {len(decrypted_bytes)}")
  157. except Exception:
  158. pass
  159. # JSON解析
  160. return json.loads(decrypted_bytes.decode('utf-8'))
  161. else:
  162. # 纯RSA加密模式(向后兼容)
  163. decrypted = self.private_key.decrypt(
  164. encrypted_bytes,
  165. padding.OAEP(
  166. mgf=padding.MGF1(algorithm=hashes.SHA256()),
  167. algorithm=hashes.SHA256(),
  168. label=None
  169. )
  170. )
  171. # JSON解析
  172. return json.loads(decrypted.decode('utf-8'))
  173. except Exception as e:
  174. logger.exception("数据解密失败")
  175. raise
  176. # 全局加密器实例
  177. encryptor = RSAEncryptor()