보안 강화: DB 자격증명(AppKey, Secret) 및 세션토큰(Access Token) 암호화 저장 구현 (AES-GCM/CBC), .env 정리

This commit is contained in:
2026-02-03 00:08:15 +09:00
parent 4f0cc05f39
commit ed8fc0943b
15 changed files with 131 additions and 30 deletions

Binary file not shown.

View File

@@ -29,6 +29,9 @@ class Settings(BaseSettings):
# Timezone
TIMEZONE: str = "Asia/Seoul"
# Encryption
SECRET_KEY: str = "dlrpwjdakfehlsmswl_skf!wkf!ahfmrpTDJ!@#unsafe_default_key_change_in_production_min_32_bytes"
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",

View File

@@ -1,7 +1,10 @@
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode
from Crypto.Util.Padding import unpad, pad
from base64 import b64decode, b64encode
from app.core.config import settings
import hashlib
# KIS WebSocket Decryption (CBC)
def aes_cbc_base64_dec(key: str, iv: str, cipher_text: str) -> str:
"""
Decrypts KIS WebSocket data using AES-256-CBC.
@@ -17,3 +20,54 @@ def aes_cbc_base64_dec(key: str, iv: str, cipher_text: str) -> str:
decrypted_bytes = unpad(cipher.decrypt(b64decode(cipher_text)), AES.block_size)
return bytes.decode(decrypted_bytes, 'utf-8')
# DB Field Encryption (AES-GCM)
def get_master_key():
# Derive a 32-byte key from the SECRET_KEY string, ensuring length
return hashlib.sha256(settings.SECRET_KEY.encode('utf-8')).digest()
def encrypt_str(plain_text: str) -> str:
"""
Encrypts string for DB storage using AES-GCM.
Returns: base64(nonce + ciphertext + tag)
"""
if not plain_text:
return ""
key = get_master_key()
cipher = AES.new(key, AES.MODE_GCM)
nonce = cipher.nonce # 16 bytes
ciphertext, tag = cipher.encrypt_and_digest(plain_text.encode('utf-8'))
# Combined: nonce(16) + tag(16) + ciphertext(n)
combined = nonce + tag + ciphertext
return b64encode(combined).decode('utf-8')
def decrypt_str(encrypted_text: str) -> str:
"""
Decrypts string from DB.
Input: base64(nonce + tag + ciphertext)
"""
if not encrypted_text:
return ""
try:
raw = b64decode(encrypted_text)
if len(raw) < 32: # Nonce(16) + Tag(16)
return "" # Invalid data
nonce = raw[:16]
tag = raw[16:32]
ciphertext = raw[32:]
key = get_master_key()
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
decrypted_data = cipher.decrypt_and_verify(ciphertext, tag)
return decrypted_data.decode('utf-8')
except Exception:
# Failed to decrypt (possibly not encrypted or wrong key).
# For safety, return empty or raise.
# In transition phase, might check if plain text? No, assume encrypted.
return "[Decryption Failed]"