You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

54 lines
1.9 KiB
Python

# backend/app/crypto.py — 커넥터 토큰 대칭 암복호화 (phase-13)
# 평문 토큰은 절대 DB 에 저장하지 않는다. ARI_SECRET_KEY → Fernet 키 파생.
import base64
import hashlib
import json
from cryptography.fernet import Fernet, InvalidToken
from .config import get_settings
def _fernet() -> Fernet:
# ARI_SECRET_KEY -> sha256(32바이트) -> urlsafe base64 -> Fernet 키
raw = get_settings().ari_secret_key.encode("utf-8")
key = base64.urlsafe_b64encode(hashlib.sha256(raw).digest())
return Fernet(key)
def encrypt_token(token_dict: dict) -> str:
"""{'access_token':..., 'refresh_token':..., 'expires_at':...} -> 암호문 str."""
return _fernet().encrypt(json.dumps(token_dict).encode("utf-8")).decode("utf-8")
def decrypt_token(enc: str) -> dict:
if not enc:
return {}
try:
return json.loads(_fernet().decrypt(enc.encode("utf-8")).decode("utf-8"))
except (InvalidToken, ValueError):
return {} # 키 회전/손상 → 빈 dict (호출측은 token_expired 처리)
# LLM API 키 등 단일 문자열 비밀의 대칭 암복호화. 평문은 DB 에 저장하지 않는다.
_ENC_PREFIX = "enc:" # 암호문 식별 접두사(평문/암호문 혼재 시 구분)
def encrypt_str(plain: str) -> str:
"""평문 → 'enc:<암호문>'. 빈 값은 그대로 빈 문자열."""
if not plain:
return ""
return _ENC_PREFIX + _fernet().encrypt(plain.encode("utf-8")).decode("utf-8")
def decrypt_str(enc: str) -> str:
"""'enc:<암호문>' → 평문. 접두사 없으면(구 평문값) 그대로 반환(하위호환)."""
if not enc:
return ""
if not enc.startswith(_ENC_PREFIX):
return enc # 접두사 없음 = 암호화 전 평문(하위호환)
try:
return _fernet().decrypt(enc[len(_ENC_PREFIX) :].encode("utf-8")).decode("utf-8")
except (InvalidToken, ValueError):
return "" # 키 회전/손상