보안 모델
런타임 게이트가 실제로 무엇을 검사하는지, 기기 잠금 오류가 의도적으로 지금과 같은 형태인 이유, 그리고 각 티어의 정직한 한계.
보호는 있거나 없거나 하는 단순한 스위치가 아닙니다 — 보호 대상 모듈마다 삽입되어, 그 모듈이 import될 때마다, 완전히 오프라인 상태에서, 해당 기기를 벗어나지 않는 암호학적 자료를 사용해 실행되는 런타임 검사입니다.
게이트가 검사하는 것
게이트가 걸린 코드가 실행될 때마다 MagicLock은 로컬에서 다음 순서로 검증합니다.
- 라이선스가 진짜인가 — 이 계정에서 발급되었고 서명이 검증됩니다.
- 라이선스가 여전히 유효한가 — 만료되지 않았고 취소되지 않았습니다.
- 기기가 일치하는가 — 라이선스(또는 산출물)가 묶인 바로 그 머신입니다.
- 역량이 허용되는가 — 지금 실행하려는 것이 라이선스가 부여한 범위 안에 있습니다.
- 증명이 신선한가 — 일회용 자격 증명으로, 캡처해 재생(replay)한 오래된 검사가 아님을 보장합니다.
어느 단계라도 실패하면 보호된 코드가 실행되기 전에 멈춥니다 — 부분적으로 실행되거나 "최선을 다해" 넘어가는 경로는 없습니다.
잘못된 머신과 변조된 파일이 똑같아 보이는 이유
VaultLockedError(API 레퍼런스 참고)는 산출물을 잘못된 머신에서 열었을 때와 로컬 상태가 변경되었을 때 모두 동일하게 발생합니다. 이는 의도된 설계입니다 — 두 경우를 구분해서 알려주는 것은 검증이 왜 실패했는지에 대한 정보를 흘리는 셈이며, 이는 취약점을 탐색하는 공격자가 정확히 원하는 종류의 오라클(oracle)입니다. 오류 메시지는 어느 경우든 동일합니다.
변조 탐지를 넘어선 변조 증거성
암호화된 산출물은 단순히 암호화만 되어 있는 것이 아니라 인증까지 되어 있습니다 — 단 한 바이트만 바뀌어도 복호화가 시작되기 전에 인증이 실패합니다. 변경된 파일을 부분적으로 복호화해서 들여다볼 방법은 없습니다. 그냥 실행을 거부할 뿐입니다.
각 티어가 실제로 막아내는 것
어떤 트레이드오프를 감수하고 있는지 정확히 짚어보겠습니다.
- 편의 티어(
.pya) — 단순 복사와 단순한 소스 코드 열람을 막습니다. 앱이 실행되는 동안에는 복호화된 바이트코드가 필연적으로 프로세스 메모리에 존재하며, 디버거와 실행 중인 프로세스에 대한 완전한 제어권을 가진, 충분히 작정한 공격자라면 원칙적으로 이를 추출할 수 있습니다. 이 티어는 "메모리 포렌식을 무력화하는 것"이 아니라 "쉽게 뚫리지 않게 만드는 것"을 목표로 합니다. - 컴파일 강력 보호(
magiclock build) — 평문 Python이 전혀 배포되지 않으며, 라이선스 검사는 빌드 시점에 컴파일된 모든 모듈의 제어 흐름에 자동으로 짜여 들어가지, 삭제할 수 있는 하나의 공유 검사 지점에 몰려 있지 않습니다. 이는 가장 단순한 우회("그냥 검사를 제거한다")를 막아, 난이도를 "소스를 읽는다"에서 "컴파일된 바이너리를 모듈 단위로 리버스 엔지니어링한다"로 끌어올립니다. - 기기 바인딩 — 로컬 하드웨어/OS 식별자로부터 얻은 머신 핑거프린트에 복호화를 묶습니다. 머신 간 단순 복사를 막지만, 하드웨어 보안 요소(secure element)를 대체하지는 못하며, 기기를 완전히 제어하는 충분히 자원이 있는 공격자는 핑거프린트를 스푸핑하려 시도할 수 있습니다.
MagicLock은 복사, 변조, 리버스 엔지니어링을 의미 있게 더 비싸게 만들도록 설계되었습니다 — 무제한의 시간과 하드웨어에 대한 완전한 제어권을 가진 공격자에 맞서 소프트웨어만으로 뚫을 수 없는 보호를 제공한다고 주장하는 것이 아닙니다. 위협 모델에 그 수준의 공격자가 포함된다면, 그것이 바로 하드웨어 기반 보호가 필요한 이유입니다.
서명과 키
라이선스는 Ed25519로 서명되고 벤더 키로 검증됩니다 — 동일한 서명 체계가 취소 목록도 보호하므로, 오래되었거나 위조된 취소 목록을 재생(replay)해서 취소를 되돌릴 수 없습니다. 검증 키는 배포물 안에 임베드되어 있으며, 개인 서명 키는 라이선스 서버를 벗어나지 않습니다.
참고
- 계정 및 활성화 — 오프라인 유예 창, 그리고 취소가 영향을 미치는 것과 미치지 않는 것.
- Python API 레퍼런스 — 각 실패 모드가 코드에서 발생시키는 예외.