포스트

파이썬 서버 secret key 이중 삼중 보안

getPass를 사용해 중요정보를 타이핑으로 입력받도록 하면 보안을 더 강화할 수 있습니다.

입력받는 키를 api key 직접으로 하면 기억을 할 수 없으므로,

api key를 암호화해서 .env에 넣어놓고,

그 api key를 복호화할 키를 기억해놨다가 입력하면 됩니다.

복호화할 키와, 그대로 쓸 키를 구분하기 위해서 “ENC_” 로 시작하는 키만 복호화하도록 구성할 수 있습니다.

1
2
3
4
5
6
7
def decrypt_env_value(value: str, key: str) -> str:
    if value.startswith("ENC_"):
        encrypted = value[len("ENC_") :]

        return decrypt_password(encrypted, key)
    return value

그리고 저의 경우는 .env도 이중으로 불러오도록 해놓았습니다. 비밀스러운 env는 좀더 접근하기 어려운 곳에 두는거죠.

그리고 저는 config.py와 config_loader.py를 두어서, config에서 Settings 싱글톤 인스턴스를 두고, config_loader는 기능적 메소드만 분리했습니다.

그 결과 다음처럼 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# config_loader.py

def decrypt_env_value(value: str, key: str) -> str:
    if value.startswith("ENC_"):
        encrypted = value[len("ENC_") :]

        return decrypt_password(encrypted, key)
    return value


def load_envs(base_dir: Path, decrypt_key: str):
    # 1. .env 먼저! 항상 override=True
    base_env_path = base_dir / ".env"
    if base_env_path.exists():
        load_dotenv(dotenv_path=base_env_path, override=True, encoding="utf-8-sig")
        print(f"[ENV] .env 로드됨: {base_env_path}")
    else:
        print(f"[ENV] .env 없음: {base_env_path}")

    # 2. SECRET_PATH 다음! 항상 override=True
    secret_path = os.getenv("SECRET_PATH")
    if secret_path and Path(secret_path).exists():
        load_dotenv(dotenv_path=secret_path, override=True, encoding="utf-8-sig")
        print(f"[ENV] 비밀 설정 우선 적용됨: {secret_path}")
    else:
        print(f"[ENV] 비밀 설정 없음 또는 파일 없음: {secret_path}")

    # 🔑 복호화키가 주어졌을 때만 복호화 시도
    if decrypt_key:
        for key, value in list(os.environ.items()):
            if value and value.startswith("ENC_"):
                try:
                    plain = decrypt_env_value(value, decrypt_key)
                    os.environ[key] = plain
                    print(f"[ENV] 암호화 값 복호화 적용: {key}")
                except Exception as e:
                    print(f"[ENV] 복호화 실패: {key} ({e})")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# config.py

import os
import re
from typing import List, Optional
from pydantic_settings import BaseSettings
from core.config_loader import load_envs
from pathlib import Path

_settings = None

class Settings(BaseSettings):
...


def init_settings(base_dir: Path, decrypt_key: str = None):
    global _settings
    load_envs(base_dir, decrypt_key=decrypt_key)
    _settings = Settings()


def get_settings() -> Settings:
    if _settings is None:
        raise RuntimeError("Settings not initialized. Call init_settings(base_dir) first.")
    return _settings


다음은 제 main.py 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ===================== 복호화 키 입력 =====================
parser = argparse.ArgumentParser()
parser.add_argument("--decrypt-key", "-k", type=str, help="환경변수 복호화용 키 입력")
args, unknown = parser.parse_known_args()

decrypt_key = args.decrypt_key
if not decrypt_key:
    decrypt_key = getpass.getpass("환경변수 복호화 키를 입력하세요: ")


# ===================== 설정 초기화 =====================
project_root = Path(__file__).resolve().parent
init_settings(project_root)
settings = get_settings()

이렇게 설정해두면 서버가 해킹되더라도, api 키를 복호화할 수 있는 키는 제 머릿속에만 있기 때문에 api키를 보호할 수 있습니다.

그리고 저는 그러한 api키를 복호화하는데에 저의 수메리안 암호화 기능을 사용합니다. ㅎㅎ

맘에 드신다면 star 많이 눌러주세요~

https://github.com/southglory/sumerian-aes-vault/blob/main/version2/README.md

sumerian-aes-vault/version2/README.md at main · southglory/sumerian-aes-vault 🏺 Sumerian-AES Vault: Where ancient meets modern cryptography A unique password protection system that combines AES-256 encryption with Sumerian cuneiform visualization. Features GUI interface, bi… 🏺 Sumerian-AES Vault: Where ancient meets modern cryptography A unique password protection system that combines AES-256 encryption with Sumerian cuneiform visualization. Features GUI interface, bi…

크크크크 ㅋㅋ

sticker

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.