keys.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """
  2. 密钥管理接口
  3. """
  4. import hmac
  5. import hashlib
  6. import base64
  7. import time
  8. import secrets
  9. import json
  10. from typing import Dict, Any
  11. from fastapi import APIRouter, Depends, HTTPException
  12. from sqlalchemy.orm import Session
  13. from pydantic import BaseModel
  14. from app.database import get_db
  15. from app.models.api_key import APIKey
  16. from app.core.encrypt import encryptor
  17. from app.utils.timestamp_validator import TimestampValidator
  18. from app.utils.timezone import beijing_now
  19. from app.config import settings
  20. from app.core.logger import logger
  21. router = APIRouter()
  22. class PublicKeyRequest(BaseModel):
  23. """公钥获取请求"""
  24. aes_key: str # Base64编码的AES密钥(32字节)
  25. timestamp: int # 请求时间戳
  26. nonce: str # 随机字符串,防止重放攻击
  27. @router.post("/public")
  28. async def get_public_key(
  29. request: PublicKeyRequest,
  30. db: Session = Depends(get_db)
  31. ):
  32. """
  33. 获取公钥 (AES加密传输版本)
  34. 安全特性:
  35. - AES加密传输公钥数据
  36. - 时间戳验证防止重放攻击
  37. - HMAC签名确保数据完整性
  38. """
  39. try:
  40. # 1. 验证时间戳
  41. is_valid, error_msg = TimestampValidator.validate_timestamp(request.timestamp, db)
  42. if not is_valid:
  43. logger.warning(f"公钥获取请求时间戳验证失败: {error_msg}")
  44. return {
  45. "code": 400,
  46. "msg": f"时间戳验证失败: {error_msg}",
  47. "timestamp": TimestampValidator.generate_timestamp()
  48. }
  49. # 2. 获取当前激活的密钥(主is_active,辅expire_at)
  50. key = db.query(APIKey).filter(
  51. APIKey.is_active == 1,
  52. APIKey.is_deleted == 0,
  53. APIKey.expire_at > beijing_now()
  54. ).order_by(APIKey.key_version.desc()).first()
  55. if not key:
  56. logger.error("未找到激活的有效密钥")
  57. return {
  58. "code": 404,
  59. "msg": "系统密钥服务暂不可用",
  60. "timestamp": TimestampValidator.generate_timestamp()
  61. }
  62. # 3. 验证并解码AES密钥
  63. try:
  64. aes_key_bytes = base64.b64decode(request.aes_key)
  65. if len(aes_key_bytes) != 32:
  66. raise ValueError("AES密钥长度无效")
  67. except Exception as e:
  68. logger.warning(f"AES密钥解码失败: {e}")
  69. return {
  70. "code": 400,
  71. "msg": "无效的AES密钥格式",
  72. "timestamp": TimestampValidator.generate_timestamp()
  73. }
  74. # 4. 准备要加密的数据
  75. public_key_data = {
  76. "public_key": key.public_key,
  77. "key_version": key.key_version,
  78. "expire_at": key.expire_at.isoformat() if key.expire_at else None
  79. }
  80. # 5. 使用AES加密公钥数据
  81. # 生成新的AES密钥和IV用于加密响应数据
  82. response_aes_key = secrets.token_bytes(32) # 256位AES密钥
  83. response_iv = secrets.token_bytes(16) # 128位IV
  84. # AES加密公钥数据
  85. data_json = json.dumps(public_key_data, ensure_ascii=False)
  86. encrypted_data = _aes_encrypt(data_json.encode('utf-8'), response_aes_key, response_iv)
  87. # 使用请求中的AES密钥加密响应AES密钥和IV
  88. key_iv_combined = response_aes_key + response_iv
  89. encrypted_key_iv = _aes_encrypt(key_iv_combined, aes_key_bytes, aes_key_bytes[16:32])
  90. # 6. 生成签名
  91. timestamp = TimestampValidator.generate_timestamp()
  92. signature_data = f"{timestamp}:{request.nonce}:{base64.b64encode(encrypted_data).decode()}"
  93. signature = _generate_signature(signature_data, settings.SECRET_KEY)
  94. # 7. 返回响应
  95. response = {
  96. "code": 200,
  97. "msg": "成功",
  98. "data": {
  99. "encrypted_key": base64.b64encode(encrypted_key_iv).decode(),
  100. "encrypted_data": base64.b64encode(encrypted_data).decode(),
  101. "signature": signature,
  102. "timestamp": timestamp,
  103. "nonce": request.nonce
  104. }
  105. }
  106. logger.info(f"公钥获取成功,版本: {key.key_version}")
  107. return response
  108. except Exception as e:
  109. logger.exception("公钥获取异常")
  110. return {
  111. "code": 500,
  112. "msg": "系统异常,请稍后重试;若持续发生请联系客服",
  113. "timestamp": TimestampValidator.generate_timestamp()
  114. }
  115. def _aes_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes:
  116. """AES加密"""
  117. from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
  118. from cryptography.hazmat.primitives.padding import PKCS7
  119. from cryptography.hazmat.backends import default_backend
  120. cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
  121. encryptor = cipher.encryptor()
  122. # PKCS7填充
  123. padder = PKCS7(128).padder()
  124. padded_data = padder.update(data) + padder.finalize()
  125. return encryptor.update(padded_data) + encryptor.finalize()
  126. def _generate_signature(data: str, secret_key: str) -> str:
  127. """生成HMAC签名"""
  128. signature = hmac.new(
  129. secret_key.encode(),
  130. data.encode(),
  131. hashlib.sha256
  132. ).digest()
  133. return base64.b64encode(signature).decode()
  134. @router.get("/version")
  135. async def get_key_version(db: Session = Depends(get_db)):
  136. """
  137. 获取当前密钥版本信息
  138. 返回当前激活的密钥版本,用于客户端配置验证
  139. """
  140. # 获取当前激活的密钥(主is_active,辅expire_at)
  141. key = db.query(APIKey).filter(
  142. APIKey.is_active == 1,
  143. APIKey.is_deleted == 0,
  144. APIKey.expire_at > beijing_now()
  145. ).order_by(APIKey.key_version.desc()).first()
  146. if not key:
  147. return {"code": 404, "msg": "未找到激活的有效密钥"}
  148. # 获取所有可用的密钥版本列表(包括当前激活的和新预热的)
  149. available_keys = db.query(APIKey).filter(
  150. APIKey.is_active == 1, # 必须激活
  151. APIKey.is_deleted == 0,
  152. APIKey.expire_at > beijing_now() # 且未过期
  153. ).order_by(APIKey.key_version.desc()).all()
  154. available_versions = [k.key_version for k in available_keys]
  155. return {
  156. "code": 200,
  157. "msg": "成功",
  158. "data": {
  159. "current_version": key.key_version,
  160. "available_versions": available_versions,
  161. "public_key": key.public_key, # 仍然提供公钥,用于客户端更新配置
  162. "expire_at": key.expire_at.isoformat() if key.expire_at else None
  163. }
  164. }