보안 강화: DB 자격증명(AppKey, Secret) 및 세션토큰(Access Token) 암호화 저장 구현 (AES-GCM/CBC), .env 정리
This commit is contained in:
BIN
backend/app/core/__pycache__/config.cpython-312.pyc
Normal file
BIN
backend/app/core/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
backend/app/core/__pycache__/market_schedule.cpython-312.pyc
Normal file
BIN
backend/app/core/__pycache__/market_schedule.cpython-312.pyc
Normal file
Binary file not shown.
@@ -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",
|
||||
|
||||
@@ -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]"
|
||||
|
||||
Reference in New Issue
Block a user