""" GUARDiA ITSM — 자격증명 암호화 공통 유틸리티 외부 연동 자격증명(API 키·시크릿·토큰·SNMP 커뮤니티 문자열 등)을 DB에 평문으로 저장하지 않기 위한 AES-256-GCM 암호화/복호화 모듈. 여러 라우터(jira_sync·multicloud· ncloud·public_api_hub·snmp_discovery·sso_provider·upstage_ocr 등)가 공유한다. 키 소스: GUARDIA_ENC_KEY 환경변수 (문자열을 UTF-8 인코딩 후 32바이트로 패딩/절단). core/seed.py · routers/citizen_portal.py 와 동일한 컨벤션 — 운영 배포 시 반드시 32자 이상의 고유 값으로 설정할 것 (미설정 시 데모 기본값 사용). 저장 형식: base64(nonce[12] + ciphertext + GCM tag[16]) """ from __future__ import annotations import base64 import os from cryptography.hazmat.primitives.ciphers.aead import AESGCM def _enc_key() -> bytes: raw = os.environ.get("GUARDIA_ENC_KEY", "guardia-demo-key-32bytes-padding!").encode("utf-8") return raw[:32].ljust(32, b"\x00") def encrypt_secret(plain: str | None) -> str | None: """평문 자격증명을 AES-256-GCM으로 암호화하여 base64 문자열로 반환한다.""" if not plain: return plain nonce = os.urandom(12) ct = AESGCM(_enc_key()).encrypt(nonce, plain.encode("utf-8"), None) return base64.b64encode(nonce + ct).decode("ascii") def decrypt_secret(enc_value: str | None) -> str | None: """encrypt_secret()으로 암호화된 문자열을 복호화한다. 평문으로 저장된 레거시 값(이번 암호화 적용 이전 데이터)이나 손상된 값을 만나면 예외를 전파하지 않고 입력값을 그대로 반환한다 — Fail-Safe 원칙(CLAUDE.md)에 따라 기존 연동이 끊기지 않도록 한다. """ if not enc_value: return enc_value try: raw = base64.b64decode(enc_value, validate=True) nonce, ct = raw[:12], raw[12:] return AESGCM(_enc_key()).decrypt(nonce, ct, None).decode("utf-8") except Exception: return enc_value # kubernetes.py 등에서 참조하는 이름과 호환되는 별칭 encrypt_password = encrypt_secret decrypt_password = decrypt_secret