encrypt.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. """
  2. RSA+AES混合加密工具 - 支持密钥版本管理
  3. """
  4. import base64
  5. import json
  6. import time
  7. from typing import Dict, Any, Optional, Tuple
  8. from sqlalchemy.orm import Session
  9. from cryptography.hazmat.primitives import serialization
  10. from cryptography.hazmat.primitives.asymmetric import rsa, padding
  11. from cryptography.hazmat.primitives import hashes
  12. from cryptography.hazmat.backends import default_backend
  13. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  14. from cryptography.hazmat.primitives.padding import PKCS7
  15. from app.config import settings
  16. from app.core.logger import logger
  17. from app.database import get_db
  18. from app.models.api_key import APIKey
  19. from app.utils.timestamp_validator import TimestampValidator
  20. class RSAEncryptor:
  21. """RSA+AES混合加密器 - 支持多版本密钥"""
  22. def __init__(self):
  23. self.private_key: Optional[rsa.RSAPrivateKey] = None
  24. self.public_key: Optional[rsa.RSAPublicKey] = None
  25. self.current_key_version: int = 0
  26. self.key_cache: Dict[int, Tuple[str, str]] = {} # version -> (private_key, public_key)
  27. def generate_key_pair(self) -> tuple[str, str]:
  28. """生成密钥对"""
  29. private_key = rsa.generate_private_key(
  30. public_exponent=65537,
  31. key_size=2048,
  32. backend=default_backend()
  33. )
  34. public_key = private_key.public_key()
  35. # 序列化为PEM格式
  36. private_pem = private_key.private_bytes(
  37. encoding=serialization.Encoding.PEM,
  38. format=serialization.PrivateFormat.PKCS8,
  39. encryption_algorithm=serialization.NoEncryption()
  40. ).decode('utf-8')
  41. public_pem = public_key.public_bytes(
  42. encoding=serialization.Encoding.PEM,
  43. format=serialization.PublicFormat.SubjectPublicKeyInfo
  44. ).decode('utf-8')
  45. return private_pem, public_pem
  46. def load_private_key(self, private_key_pem: str, key_version: int = 0):
  47. """加载私钥"""
  48. try:
  49. self.private_key = serialization.load_pem_private_key(
  50. private_key_pem.encode(),
  51. password=None,
  52. backend=default_backend()
  53. )
  54. self.public_key = self.private_key.public_key()
  55. self.current_key_version = key_version
  56. logger.info(f"私钥加载成功,版本: {key_version}")
  57. except Exception as e:
  58. logger.exception("私钥加载失败")
  59. raise
  60. def load_key_by_version(self, db: Session, key_version: int) -> bool:
  61. """根据版本号加载密钥"""
  62. try:
  63. if key_version in self.key_cache:
  64. private_pem, _ = self.key_cache[key_version]
  65. self.load_private_key(private_pem, key_version)
  66. return True
  67. api_key = db.query(APIKey).filter(
  68. APIKey.key_version == key_version,
  69. APIKey.is_active == 1,
  70. APIKey.is_deleted == 0
  71. ).first()
  72. if not api_key:
  73. logger.warning(f"未找到版本为 {key_version} 的密钥")
  74. return False
  75. self.load_private_key(api_key.private_key, key_version)
  76. self.key_cache[key_version] = (api_key.private_key, api_key.public_key)
  77. return True
  78. except Exception as e:
  79. logger.exception(f"加载密钥版本 {key_version} 失败")
  80. return False
  81. def get_current_public_key(self, db: Session) -> Tuple[str, int]:
  82. """获取当前激活的公钥"""
  83. try:
  84. api_key = db.query(APIKey).filter(
  85. APIKey.is_active == 1,
  86. APIKey.is_deleted == 0
  87. ).order_by(APIKey.key_version.desc()).first()
  88. if api_key:
  89. return api_key.public_key, api_key.key_version
  90. logger.warning("未找到激活的公钥")
  91. return "", 0
  92. except Exception as e:
  93. logger.exception("获取当前公钥失败")
  94. return "", 0
  95. def encrypt_for_client(self, data: Dict[str, Any], db: Session) -> Tuple[str, int]:
  96. """
  97. 为客户端加密数据
  98. Args:
  99. data: 要加密的数据
  100. db: 数据库会话
  101. Returns:
  102. tuple: (加密后的数据, 使用的密钥版本)
  103. """
  104. try:
  105. # 获取当前激活的公钥
  106. public_key_pem, key_version = self.get_current_public_key(db)
  107. if not public_key_pem:
  108. raise ValueError("未找到可用的公钥")
  109. # 如果当前加载的不是最新的密钥,则重新加载
  110. if self.current_key_version != key_version:
  111. self.load_key_by_version(db, key_version)
  112. # 生成AES密钥
  113. aes_key = self._generate_aes_key()
  114. # 使用AES加密数据
  115. encrypted_payload = self._aes_encrypt(json.dumps(data, ensure_ascii=False).encode('utf-8'), aes_key)
  116. # 使用RSA加密AES密钥
  117. encrypted_aes_key = self._rsa_encrypt_key(aes_key, public_key_pem)
  118. # 组合:encrypted_aes_key(256) + iv(16) + encrypted_payload
  119. iv = aes_key[16:32] # 从AES密钥中提取IV
  120. result = encrypted_aes_key + iv + encrypted_payload
  121. # Base64编码
  122. encrypted_data = base64.b64encode(result).decode('utf-8')
  123. logger.debug(f"客户端数据加密成功,版本: {key_version}")
  124. return encrypted_data, key_version
  125. except Exception as e:
  126. logger.exception("客户端数据加密失败")
  127. raise
  128. def decrypt_from_client(self, encrypted_data: str, key_version: int, db: Session) -> Dict[str, Any]:
  129. """
  130. 解密客户端发送的数据
  131. Args:
  132. encrypted_data: 加密的数据
  133. key_version: 使用的密钥版本
  134. db: 数据库会话
  135. Returns:
  136. dict: 解密后的数据
  137. """
  138. try:
  139. # 确保使用正确的密钥版本
  140. if self.current_key_version != key_version:
  141. if not self.load_key_by_version(db, key_version):
  142. raise ValueError(f"无法加载密钥版本 {key_version}")
  143. return self.decrypt(encrypted_data)
  144. except Exception as e:
  145. logger.exception("客户端数据解密失败")
  146. raise
  147. def _generate_aes_key(self) -> bytes:
  148. """生成AES密钥和IV"""
  149. import os
  150. return os.urandom(32) # 256位AES密钥
  151. def _aes_encrypt(self, data: bytes, key: bytes) -> bytes:
  152. """AES加密"""
  153. iv = key[16:32] # 使用密钥的后16字节作为IV
  154. cipher = Cipher(algorithms.AES(key[:16]), modes.CBC(iv), backend=default_backend())
  155. encryptor = cipher.encryptor()
  156. # PKCS7填充
  157. padder = PKCS7(128).padder()
  158. padded_data = padder.update(data) + padder.finalize()
  159. return encryptor.update(padded_data) + encryptor.finalize()
  160. def _rsa_encrypt_key(self, aes_key: bytes, public_key_pem: str) -> bytes:
  161. """使用RSA加密AES密钥"""
  162. public_key = serialization.load_pem_public_key(
  163. public_key_pem.encode(),
  164. backend=default_backend()
  165. )
  166. return public_key.encrypt(
  167. aes_key,
  168. padding.OAEP(
  169. mgf=padding.MGF1(algorithm=hashes.SHA256()),
  170. algorithm=hashes.SHA256(),
  171. label=None
  172. )
  173. )
  174. def decrypt(self, encrypted_data: str) -> Dict[str, Any]:
  175. """解密数据(支持RSA+AES混合加密和纯RSA加密)"""
  176. if not settings.ENCRYPT_ENABLED:
  177. return json.loads(encrypted_data)
  178. if not self.private_key:
  179. raise ValueError("私钥未加载")
  180. try:
  181. # Base64解码
  182. encrypted_bytes = base64.b64decode(encrypted_data)
  183. # 判断是混合加密还是纯RSA加密
  184. # 混合加密:加密的AES密钥(256字节) + IV(16字节) + 加密的数据
  185. # 纯RSA加密:直接是RSA加密的数据(通常小于256字节)
  186. # 如果数据长度大于256字节,认为是混合加密
  187. if len(encrypted_bytes) > 256:
  188. # 混合加密模式
  189. # 提取加密的AES密钥(前256字节)
  190. encrypted_aes_key = encrypted_bytes[:256]
  191. # 提取IV(接下来16字节)
  192. iv = encrypted_bytes[256:272]
  193. # 提取加密的数据(剩余部分)
  194. encrypted_payload = encrypted_bytes[272:]
  195. # 记录调试信息:各部分长度
  196. try:
  197. logger.debug(f"Encrypted total bytes: {len(encrypted_bytes)}, "
  198. f"enc_key_len: {len(encrypted_aes_key)}, iv_len: {len(iv)}, payload_len: {len(encrypted_payload)}")
  199. except Exception:
  200. pass
  201. # 使用RSA解密AES密钥
  202. aes_key = self.private_key.decrypt(
  203. encrypted_aes_key,
  204. padding.OAEP(
  205. mgf=padding.MGF1(algorithm=hashes.SHA256()),
  206. algorithm=hashes.SHA256(),
  207. label=None
  208. )
  209. )
  210. # 使用AES解密数据
  211. cipher = Cipher(algorithms.AES(aes_key), modes.CBC(iv), backend=default_backend())
  212. decryptor = cipher.decryptor()
  213. padded_data = decryptor.update(encrypted_payload) + decryptor.finalize()
  214. # 去除PKCS7填充
  215. unpadder = PKCS7(128).unpadder()
  216. decrypted_bytes = unpadder.update(padded_data) + unpadder.finalize()
  217. # 记录解密后字节预览,便于调试(限制长度,避免日志过大)
  218. try:
  219. preview = decrypted_bytes[:300]
  220. # 尝试以utf-8安全显示
  221. try:
  222. preview_text = preview.decode('utf-8', errors='replace')
  223. except Exception:
  224. preview_text = str(preview)
  225. logger.debug(f"Decrypted bytes preview (first 300): {preview_text}")
  226. logger.debug(f"Decrypted total length: {len(decrypted_bytes)}")
  227. except Exception:
  228. pass
  229. # JSON解析
  230. return json.loads(decrypted_bytes.decode('utf-8'))
  231. else:
  232. # 纯RSA加密模式(向后兼容)
  233. decrypted = self.private_key.decrypt(
  234. encrypted_bytes,
  235. padding.OAEP(
  236. mgf=padding.MGF1(algorithm=hashes.SHA256()),
  237. algorithm=hashes.SHA256(),
  238. label=None
  239. )
  240. )
  241. # JSON解析
  242. return json.loads(decrypted.decode('utf-8'))
  243. except Exception as e:
  244. logger.exception("数据解密失败")
  245. raise
  246. # 全局加密器实例
  247. encryptor = RSAEncryptor()