Python 코드 난독화

2025. 9. 24. 23:38·TIL

프로젝트를 진행하는 과정에서, 파이썬 프로젝트의 내부 비즈니스 로직을 숨기고자 하는 요구사항이 생겼다.

- 파이썬 프로젝트는 도커 이미지로 빌드하더라도, 그 내부에 프로젝트 디렉토리 구조와 소스코드가 그대로 유지된다.

- 자바 프로젝트는 .jar 파일 프로젝트를 gradle 빌드하고, 이를 도커 이미지로 만드는 방식인데

   .jar 내부에 컴파일된 파일들이 있어 원본 소스 코드를 바로 확인하기는 어렵지만, 이 또한 디컴파일하면 확인할 수 있다.

 

이를 해결하기 위해, 파이썬 코드 난독화 방법들을 리서치했고 Cython과 Nuitka 라이브러리를 알게 되었다.

이번 글에서는 각 라이브러리가 어떤 방식으로 동작하는지 큰 그림에서 이해한 내용을 정리하고, 실제 적용을 어떻게 했는지 기록하고자 한다.

 


 

1. Cython


Cython을 사용할 경우 기존 `.py` python 코드들을 C 코드로 변환하고, C 컴파일러로 빌드해서 `.so` 바이너리 파일을 만든다. 

간단히 흐름으로 표시를 하면 `.py` → `.c` → `.so` 로 표현할 수 있다.

 

이 때 만들어진 `.c` 파일은 파이썬 코드와 동등한 로직을 가진 순수한 C 코드가 아니라 CPython의 내부 함수들을 호출하는 C 코드라고 한다.

 

 

이 때 CPython의 내부 함수들을 호출하는 C 코드 라는 부분이 이해가 되지 않아 조금 더 찾아보았다.

 

- python 동작 방식

이를 이해하기 위해서는 먼저 파이썬의 동작 방식을 한 번 짚어볼 필요가 있다.

  • 기본적으로 Python은 인터프리터 언어로, C/C++ 같은 컴파일 언어처럼 전체 소스 코드를 기계어로 미리 변환해두지 않고 한 줄씩 번역하고 실행한다.
  • 실제 동작 과정은 `.py` 소스코드 → `.pyc` 바이트코드로 변환하고, 이를 파이썬 인터프리터가 한 줄씩 읽어 실행하는 방식이다.
  • 대표적으로 C언어로 구현된 CPython 인터프리터가 사용되고, 이 CPython은 내부에 이미 기계어로 변환된 수많은 C 함수들을 가지고 있다. 파이썬 코드를 실행한다는 것은 결국 이 C 함수들을 호출하여 작업을 처리하는 구조인 것이다.

 

그래서 여기에서 말하는 CPython C-API를 호출한다는 것은 Cython이 파이썬 코드를 CPython 인터프리터가 제공하는 내장 C 함수(API)들을 직접 호출하는 형태의 C 소스 코드로 변환한다는 의미로 이해할 수 있다.

따라서 이렇게 만들어진 `.c`, `.so` 바이너리 파일은 순수 `.c` 파일처럼 C 컴파일러 위에서 실행할 수 없고, CPython 인터프리터 위에서만 동작할 수 있게 된다.

 


 

Q. 그럼 왜 Cython을 사용할까?? CPython 인터프리터로 실행되는 건 똑같은 것 같은데..

일반적인 파이썬 프로젝트는 `.py`나 `.pyc`로 배포되고 CPython 인터프리터가 이를 기계어로 해석하고 실행한다.

이러한 방식은 내부 비즈니스 로직이 노출될 위험이 있다.

 

Cython을 사용할 경우, `.py`을 기계어로 번역된 `.so`로 만들어서 배포가 가능하기 때문에 내부 로직을 이해하기가 어려워진다.

(이론적으로 이를 디컴파일하여 다시 `.py`로 되돌릴 수는 어렵다..)

 


 

Cython 난독화 (with. 도커 이미지 빌드)

1. 먼저 setup.py에서 난독화 할 파일들을 지정해주면 된다.

  • 모든 `.py` 파일을 난독화하기 위해 glob 라이브러리를 사용해 지정해주었다.
  • 프로그램 실행 지점인 `main.py`와 모듈 구조를 유지하기 위해 `__init__.py`는 제외했다.
# setup.py (필요한 파일만 컴파일)
import glob
from setuptools import setup
from Cython.Build import cythonize

# '.py' 확장자를 가진 모든 파일을 찾되, 제외할 파일들 정의
py_files = glob.glob('**/*.py', recursive=True)
exclude_files = ['setup.py', 'main.py']

# __init__.py 파일들 제외 (모듈 구조 유지용)
exclude_files.extend([f for f in py_files if f.endswith('__init__.py')])

# 제외할 파일들 제거
for exclude_file in exclude_files:
    if exclude_file in py_files:
        py_files.remove(exclude_file)

setup(
    ext_modules=cythonize(
        py_files,
        compiler_directives={'language_level': "3"}
    )
)

 

 

2. DockerFile에서 Cython 라이브러리를 설치해주고, 앞서 `setup.py`에서 정의한 파일들을 Cython으로 컴파일 해주면 된다.

  • Cython 컴파일 이후, 원본 코드 노출을 막기 위해서 `.py`, `.c` 파일은 삭제해주도록 한다.
  • 최종 이미지에 프로젝트에 필요한 의존성을 모두 설치한 이후 `requirements.txt`도 삭제해주도록 한다.
# ===== Builder =====
FROM python:3.9 AS builder

WORKDIR /app

COPY . .

# Cython 및 필요한 파이썬 라이브러리 설치
COPY requirements.txt .
RUN pip install --upgrade pip setuptools cython \
    && pip install --no-cache-dir -r requirements.txt

# Python을 C로 컴파일 (.so 생성)
# .c 파일 삭제 및 .py 파일 삭제 (__init__.py 제외)
RUN python setup.py build_ext --inplace \
 && find . -name "*.c" -delete \
 && find . -name "*.py" ! -name "__init__.py" ! -name "main.py" -delete \
 && strip --strip-unneeded $(find . -name "*.so" || true)


# ===== Runtime =====
FROM python:3.9-slim
WORKDIR /app

RUN apt-get update && apt-get install -y \
    fontconfig \
    libhdf5-dev \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt \
    && rm requirements.txt

# 빌드한 결과물 최종 이미지로 복사
COPY --from=builder /app/app/ ./app/
COPY --from=builder /app/font ./font
COPY --from=builder /app/main.py ./

ENV APP_ENV=production
ENV TZ=Asia/Seoul
ENV PYTHONUNBUFFERED=1

CMD ["python", "-u", "./main.py"]

 

 

 

 

2. Nuitka


Nuitka는 `.py` 코드를 C++ 코드로 변환하고, 바로 C++ 컴파일러로 빌드해서 바이너리 파일을 만들어준다.

여기서도 마찬가지로 순수 C++ 코드로 변환되기 보다는, CPython API를 활용해서 빌드되는 듯 하다.

 

특징으로는 프로젝트 전체를 빌드해 하나의 `.exe` 실행파일로 만드는데 있다.

이 때 CPython 인터프리터도 같이 패키징하여, 파이썬 설치 없이 배포가 가능하다.

(도커로 배포하려고 하는 관점에서는 큰 이점은 아닐 것 같기도 하다.)

 

그리고 `requirements.txt`에 있는 프로젝트의 의존성 패키지 라이브러리 역시 C++ 코드로 변환하고 컴파일을 한다. (라이브러리에서 미리 컴파일 된 바이너리 파일이 있으면, 이를 사용할 수도..!)

이 과정에서 실행에 필요한 모든 파일을 통째로 넣기 때문에, 의존성이 많은 프로젝트들은 빌드 시간과 메모리 사용량, 실행 파일 용량이 매우 커질 수 있다.

 

 

Nuitka 난독화 (with. 도커 이미지 빌드)

 

Cython 테스트 이후, Nuitka도 적용해보기 위해 Dockerfile을 수정하고 이미지 빌드를 해보았다!

결과는 약 27분동안 빌드하다가.. Nuitka 컴파일 단계에서 메모리가 부족해 강제 종료되었다..

 

추가로 더 테스트해보고 싶은 마음도 있었지만,

- Cython으로 충분히 난독화를 적용할 수 있었고

- Nuitka의 긴 빌드 시간

- 서버의 높은 리소스를 요구한다는 점

 

에서 Nuitka 적용은 여기에서 마무리하기로 했다!

 

 

 

3. PyInstaller


번외로 PyInstaller를 일부 블로그에서 난독화의 방법으로 소개되고 있어 간략히 적어본다.

 

PyInstaller의 공식문서를 보면 `.py` 코드가 포함되어 있지는 않지만, `.pyc` 바이트코드가 제공되어 디컴파일하여 코드의 로직을 파악이 가능하다고 한다. 그래서 소스 코드를 숨기려면 Cython으로 컴파일하는 것을 공식문서에서도 권하고 있다.

 

https://pyinstaller.org/en/stable/operating-mode.html#hiding-the-source-code

 


 

 

참고사항

 

1. Cython docs

- https://cython.readthedocs.io/en/latest/src/quickstart/overview.html

 

 

2. PyInstaller docs

- https://pyinstaller.org/en/stable/operating-mode.html#hiding-the-source-code

 

 

 

 

 

저작자표시 (새창열림)

'TIL' 카테고리의 다른 글

블로그 글쓰기의 '이미지 업로드'는 어떻게 동작할까??  (0) 2025.12.17
jar 파일 실행해 보셨어요?  (1) 2025.05.25
MCP 에 대해 알아보자  (0) 2025.05.21
자바 모듈, Gradle 모듈 무엇이 다를까? (feat. 멀티 모듈)  (0) 2025.05.15
애플 소셜 로그인 (with. Spring)  (1) 2024.11.05
'TIL' 카테고리의 다른 글
  • 블로그 글쓰기의 '이미지 업로드'는 어떻게 동작할까??
  • jar 파일 실행해 보셨어요?
  • MCP 에 대해 알아보자
  • 자바 모듈, Gradle 모듈 무엇이 다를까? (feat. 멀티 모듈)
wch_t
wch_t
  • wch_t
    끄적끄적(TIL)
    wch_t
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • 분류 전체보기 (180)
      • Architecture (0)
      • Algorithm (67)
        • Math (5)
        • Simulation (1)
        • Data Structure (4)
        • DP (7)
        • Brute Fource (10)
        • Binary Search (6)
        • Greedy (2)
        • Graph (11)
        • Mst (1)
        • Shortest path (10)
        • Two Pointer (1)
        • Tsp (3)
        • Union Find (2)
        • Mitm (1)
      • CS (12)
        • 데이터베이스 (5)
        • 네트워크 (5)
      • DB (8)
      • DevOps (19)
        • AWS (11)
        • Docker (1)
        • CI-CD (5)
      • Error (1)
      • Project (0)
        • kotrip (0)
      • Spring (60)
        • 끄적끄적 (5)
        • 기본 (9)
        • MVC 1 (7)
        • MVC 2 (11)
        • ORM (9)
        • JPA 1 (7)
        • JPA 2 (5)
        • Spring Data Jpa (7)
      • Test (2)
      • TIL (9)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    spring-cloud-starter-aws-secrets-manager-config
    docker
    aws secrets manager
    spring-cloud-starter-bootstrap
    form_post
    docker: not found
    response_mode
    애플
    scope
    백준 17289 파이썬
    Jenkins
    TempTable
    view algorithm
    Sxssf
    Merge
    백준 3015 파이썬
    백준 17299 파이썬
    apache poi
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
wch_t
Python 코드 난독화
상단으로

티스토리툴바