hmac_key_service.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """
  2. HMAC密钥管理服务
  3. 处理动态HMAC密钥的生成、验证、同步等功能
  4. """
  5. import secrets
  6. import hashlib
  7. import hmac as hmac_lib
  8. from datetime import datetime, timedelta
  9. from typing import Optional, Tuple, Dict, Any
  10. from sqlalchemy.orm import Session
  11. from sqlalchemy import and_, or_
  12. from app.models.hmac_key import HmacKey
  13. from app.models.user import User
  14. from app.models.software import Software
  15. from app.models.user_software_subscription import UserSoftwareSubscription
  16. from app.core.logger import logger
  17. from app.core.encrypt import encryptor
  18. from app.utils.timezone import beijing_now_naive
  19. class HmacKeyService:
  20. """HMAC密钥管理服务"""
  21. # HMAC密钥长度
  22. HMAC_KEY_LENGTH = 64
  23. # 密钥有效期(秒)
  24. KEY_EXPIRY_SECONDS = 3600 * 24 # 24小时
  25. # 密钥轮换阈值(秒)- 提前1小时开始轮换
  26. KEY_ROTATION_THRESHOLD = 3600
  27. # 密钥颁发者标识
  28. KEY_ISSUER = "buff-server"
  29. def __init__(self, db: Session):
  30. self.db = db
  31. def generate_hmac_key(self) -> str:
  32. """
  33. 生成安全的HMAC密钥
  34. Returns:
  35. Base64编码的HMAC密钥
  36. """
  37. # 生成随机字节
  38. key_bytes = secrets.token_bytes(self.HMAC_KEY_LENGTH)
  39. return key_bytes.hex()
  40. def create_hmac_key(
  41. self,
  42. user_id: int,
  43. username: str,
  44. machine_code: str,
  45. expires_in_seconds: int = None
  46. ) -> Tuple[bool, str, Dict[str, Any]]:
  47. """
  48. 为用户创建新的HMAC密钥
  49. Args:
  50. user_id: 用户ID
  51. username: 用户名
  52. machine_code: 机器码
  53. expires_in_seconds: 密钥有效期(秒)
  54. Returns:
  55. (success, message, data)
  56. """
  57. try:
  58. # 检查用户是否存在
  59. user = self.db.query(User).filter(User.id == user_id).first()
  60. if not user:
  61. return False, "用户不存在", {}
  62. # 检查用户状态
  63. if user.status == 0:
  64. return False, "账号已被禁用", {}
  65. # 检查机器码是否匹配
  66. if user.machine_code and user.machine_code != machine_code:
  67. return False, "机器码不匹配", {}
  68. # 获取当前活跃密钥
  69. current_key = self.db.query(HmacKey).filter(
  70. HmacKey.user_id == user_id,
  71. HmacKey.machine_code == machine_code,
  72. HmacKey.is_active == 1
  73. ).first()
  74. # 如果存在活跃密钥,检查是否需要轮换
  75. key_version = 1
  76. if current_key:
  77. # 检查是否即将过期
  78. time_until_expiry = (current_key.expires_at - beijing_now_naive()).total_seconds()
  79. if time_until_expiry > self.KEY_ROTATION_THRESHOLD:
  80. # 密钥仍然有效,直接返回
  81. return True, "密钥有效", {
  82. "hmac_key": current_key.hmac_key,
  83. "expires_at": current_key.expires_at.isoformat(),
  84. "key_version": current_key.key_version,
  85. "rotated": False
  86. }
  87. # 停用旧密钥
  88. current_key.is_active = 0
  89. key_version = current_key.key_version + 1
  90. logger.info(f"轮换用户 {username} 的HMAC密钥,版本: {key_version}")
  91. # 生成新密钥
  92. hmac_key = self.generate_hmac_key()
  93. expiry_seconds = expires_in_seconds or self.KEY_EXPIRY_SECONDS
  94. expires_at = beijing_now_naive() + timedelta(seconds=expiry_seconds)
  95. # 创建新密钥记录
  96. hmac_key_record = HmacKey(
  97. user_id=user_id,
  98. username=username,
  99. machine_code=machine_code,
  100. hmac_key=hmac_key,
  101. key_version=key_version,
  102. expires_at=expires_at,
  103. is_active=1
  104. )
  105. self.db.add(hmac_key_record)
  106. self.db.commit()
  107. logger.info(f"为用户 {username} 创建HMAC密钥成功,版本: {key_version}")
  108. return True, "密钥创建成功", {
  109. "hmac_key": hmac_key,
  110. "expires_at": expires_at.isoformat(),
  111. "key_version": key_version,
  112. "rotated": True
  113. }
  114. except Exception as e:
  115. self.db.rollback()
  116. logger.exception(f"创建HMAC密钥失败: {e}")
  117. return False, f"创建HMAC密钥失败: {str(e)}", {}
  118. def get_hmac_key(
  119. self,
  120. user_id: int,
  121. machine_code: str
  122. ) -> Tuple[bool, str, Dict[str, Any]]:
  123. """
  124. 获取用户的HMAC密钥
  125. Args:
  126. user_id: 用户ID
  127. machine_code: 机器码
  128. Returns:
  129. (success, message, data)
  130. """
  131. try:
  132. hmac_key_record = self.db.query(HmacKey).filter(
  133. HmacKey.user_id == user_id,
  134. HmacKey.machine_code == machine_code,
  135. HmacKey.is_active == 1
  136. ).first()
  137. if not hmac_key_record:
  138. return False, "未找到活跃的HMAC密钥", {}
  139. # 检查是否过期
  140. if hmac_key_record.is_expired():
  141. hmac_key_record.is_active = 0
  142. self.db.commit()
  143. return False, "HMAC密钥已过期", {}
  144. return True, "获取成功", {
  145. "hmac_key": hmac_key_record.hmac_key,
  146. "expires_at": hmac_key_record.expires_at.isoformat(),
  147. "key_version": hmac_key_record.key_version
  148. }
  149. except Exception as e:
  150. logger.exception(f"获取HMAC密钥失败: {e}")
  151. return False, f"获取HMAC密钥失败: {str(e)}", {}
  152. def verify_hmac_signature(
  153. self,
  154. data: str,
  155. signature: str,
  156. hmac_key: str
  157. ) -> Tuple[bool, str]:
  158. """
  159. 验证HMAC签名
  160. Args:
  161. data: 原始数据
  162. signature: 要验证的签名
  163. hmac_key: HMAC密钥
  164. Returns:
  165. (is_valid, message)
  166. """
  167. try:
  168. # 计算期望的签名
  169. expected_signature = hmac_lib.new(
  170. hmac_key.encode(),
  171. data.encode(),
  172. hashlib.sha256
  173. ).hexdigest()
  174. # 使用constant-time比较
  175. if secrets.compare_digest(signature, expected_signature):
  176. return True, "签名验证成功"
  177. else:
  178. return False, "签名验证失败"
  179. except Exception as e:
  180. logger.exception(f"HMAC签名验证异常: {e}")
  181. return False, f"签名验证异常: {str(e)}"
  182. def revoke_hmac_key(
  183. self,
  184. user_id: int,
  185. machine_code: str = None
  186. ) -> Tuple[bool, str]:
  187. """
  188. 撤销用户的HMAC密钥
  189. Args:
  190. user_id: 用户ID
  191. machine_code: 机器码(可选,不指定则撤销所有)
  192. Returns:
  193. (success, message)
  194. """
  195. try:
  196. query = self.db.query(HmacKey).filter(
  197. HmacKey.user_id == user_id,
  198. HmacKey.is_active == 1
  199. )
  200. if machine_code:
  201. query = query.filter(HmacKey.machine_code == machine_code)
  202. # 停用所有匹配的密钥
  203. count = query.update({"is_active": 0})
  204. self.db.commit()
  205. logger.info(f"撤销用户 {user_id} 的 {count} 个HMAC密钥")
  206. return True, f"已撤销 {count} 个密钥"
  207. except Exception as e:
  208. self.db.rollback()
  209. logger.exception(f"撤销HMAC密钥失败: {e}")
  210. return False, f"撤销HMAC密钥失败: {str(e)}"
  211. def cleanup_expired_keys(self) -> Tuple[bool, str, int]:
  212. """
  213. 清理过期的HMAC密钥
  214. Returns:
  215. (success, message, cleaned_count)
  216. """
  217. try:
  218. # 查找所有过期但仍标记为活跃的密钥
  219. expired_keys = self.db.query(HmacKey).filter(
  220. HmacKey.is_active == 1,
  221. HmacKey.expires_at < beijing_now_naive()
  222. ).all()
  223. cleaned_count = 0
  224. for key in expired_keys:
  225. key.is_active = 0
  226. cleaned_count += 1
  227. self.db.commit()
  228. logger.info(f"清理了 {cleaned_count} 个过期的HMAC密钥")
  229. return True, f"清理完成", cleaned_count
  230. except Exception as e:
  231. self.db.rollback()
  232. logger.exception(f"清理HMAC密钥失败: {e}")
  233. return False, f"清理失败: {str(e)}", 0
  234. def validate_user_for_hmac(
  235. self,
  236. username: str,
  237. machine_code: str
  238. ) -> Tuple[bool, str, int]:
  239. """
  240. 验证用户是否有资格获取HMAC密钥
  241. Args:
  242. username: 用户名
  243. machine_code: 机器码
  244. Returns:
  245. (is_valid, message, user_id)
  246. """
  247. try:
  248. user = self.db.query(User).filter(User.username == username).first()
  249. if not user:
  250. return False, "用户不存在", 0
  251. if user.status == 0:
  252. return False, "账号已被禁用", 0
  253. # 验证机器码
  254. if user.machine_code and user.machine_code != machine_code:
  255. return False, "机器码不匹配", 0
  256. # 验证用户卡密/订阅是否过期(与心跳、登录一致)
  257. software_code = "buff-xp-steam-sh"
  258. sw = self.db.query(Software).filter(
  259. Software.code == software_code,
  260. Software.is_deleted == 0
  261. ).first()
  262. expire_time = None
  263. if sw:
  264. sub = self.db.query(UserSoftwareSubscription).filter(
  265. UserSoftwareSubscription.user_id == user.id,
  266. UserSoftwareSubscription.software_id == sw.id,
  267. UserSoftwareSubscription.is_deleted == 0
  268. ).first()
  269. if sub and sub.expire_time is not None:
  270. expire_time = sub.expire_time
  271. if expire_time is None:
  272. expire_time = user.expire_time
  273. if expire_time and expire_time < beijing_now_naive():
  274. return False, "您的卡密已到期,请前往续费或联系客户支持", 0
  275. return True, "验证通过", user.id
  276. except Exception as e:
  277. logger.exception(f"验证用户失败: {e}")
  278. return False, f"验证失败: {str(e)}", 0