PlainApp 보안 심층 분석
PlainApp이 로컬 Wi-Fi에서 데이터를 어떻게 보호하는지 — 오픈 소스 코드의 근거와 함께 설명합니다.
로컬 네트워크 보안이 왜 중요할까요?
PC 브라우저에서 Android 폰을 관리하는 앱을 사용하면, SMS, 사진, 연락처, 파일이 Wi-Fi 네트워크를 통해 전송됩니다. 대부분의 앱은 이 모든 데이터를 읽을 수 있는 평문으로 전송합니다. 같은 네트워크에 있는 누구나 이를 볼 수 있습니다.
"우리 집 Wi-Fi인데 누가 엿보겠어?"라고 생각하실 수 있습니다. 하지만 다음 상황을 고려해 보십시오:
- 공용 Wi-Fi — 카페, 공항, 호텔 — 근처에 있는 누구나 트래픽을 캐처할 수 있습니다
- 공유 네트워크 — 룸메이트, 손님, 또는 보안이 취약한 IoT 기기
- 라우터 취약점 — 알려진 취약점이 있는 구형 편웨어
앱이 SMS 메시지를 HTTP를 통해 평문으로 전송하면, Wireshark 같은 무료 도구를 가진 누구나 내용을 읽을 수 있습니다. 이것은 이론적인 이야기가 아닙니다 — 매우 쉽게 가능합니다.
대부분의 폰 관리 앱이 바로 이렇게 동작합니다. 웹 UI를 사용하면서 Chrome DevTools(F12 → Network 탭)를 열어보면, 모든 API 호출을 평문 JSON으로 읽을 수 있습니다 — SMS 내용, 파일 경로, 연락처 이름 등 모든 것을 볼 수 있습니다.
PlainApp은 어떻게 다를까요?
PlainApp은 여러 격의 암호화와 인증을 적용합니다. 각 계층이 무엇을 하는지 쉬운 말로 설명합니다.
GraphQL API — 필요한 데이터만 요청합니다
대부분의 앱은 구식 REST API를 사용합니다: 리소스마다 하나의 URL이 있고, 필요 여부와 관계없이 모든 데이터를 반환합니다. PlainApp은 GraphQL을 사용합니다 — 브라우저가 정확히 필요한 데이터만 요청하는 최신 방식입니다.
왜 중요한가:
- 노출되는 데이터 최소화 — 파일 이름만 필요하면 파일 이름만 전송됩니다
- 단일 엔드포인트 — 수십 개 대신 하나의 /graphql URL만 사용하여 공격 표면이 줄어듭니다
- 쿼리별 권한 확인 — 각 쿼리는 자체 권한을 확인합니다 (예: SMS 읽기는 로그인 후에도 READ_SMS 권한이 필요합니다)
암호화된 API 호출 (XChaCha20-Poly1305)
PlainApp과 대부분의 경쟁 앱 사이의 가장 큰 차이점입니다.
모든 API 요청과 응답——HTTP 및 WebSocket 트래픽 모두——은 HTTP와 HTTPS 모드에서 XChaCha20-Poly1305로 암호화됩니다. 이는 WireGuard VPN, Cloudflare, Google의 Tink 라이브러리에서 사용되는 최신 AEAD 암호화 알고리즘입니다.
쉽게 설명하면:
- 1브라우저가 요청을 생성합니다 (예: "내 SMS를 보여줘")
- 2재전송 공격 방지를 위해 타임스탬프와 랜덤 nonce를 추가합니다
- 3XChaCha20-Poly1305로 전체 페이로드를 읽을 수 없는 바이너리 데이터로 암호화합니다
- 4암호화된 바이너리가 네트워크로 전송됩니다 — base64 없이, JSON 없이, 순수 바이너리입니다
- 5오직 당신의 휴대폰만 복호화하고 검증할 수 있습니다
누군가 트래픽을 가로채더라도 의미 없는 바이너리 데이터만 보게 됩니다 — 읽을 수 있는 JSON이 아닙니다.
암호화 세부 사항:
- 256비트 키 (매우 강력)
- 192비트 nonce (다른 암호 방식에 영향을 미치는 특정 공격 유형을 방지)
- 내장 변조 감지 — 전송 중 데이터가 수정되면 복호화가 실패합니다
- Android 측: Google Tink 기반
- 웹 측: @noble/ciphers 기반 (신뢰성이 검증된 JavaScript 암호화 라이브러리)
디지털 서명 (Ed25519) — 기기 간 통신
두 PlainApp 기기가 통신할 때(예: 폰 대 폰 채팅), 암호화만으로는 충분하지 않습니다 — 각 기기는 메시지가 정말로 상대 기기에서 온 것이며 위조되지 않았는지 검증해야 합니다.
타임스탬프
수신 기기는 발신자의 공개 키로 서명을 검증하고, 타임스탬프가 5분 이상 지난 요청을 거부합니다. 이를 통해 위조와 재전송 공격 모두를 방지합니다.
안전한 기기 페어링 (ECDH 키 교환)
두 PlainApp 기기가 서로 페어링할 때 (예: 두 대의 폰), 공유 암호화 키가 필요합니다. 하지만 네트워크를 통해 키를 전송하면 의미가 없어집니다.
PlainApp은 ECDH (Elliptic Curve Diffie-Hellman) 키 교환으로 이 문제를 해결합니다 — TLS, Apple Secure Enclave, WebAuthn에서 사용하는 것과 동일한 수학 원리입니다:
- 1각 기기가 키 쌍(공개 키 + 비밀 키)을 생성합니다
- 2공개 키만 서로 교환합니다
- 3각 기기가 자신의 비밀 키와 상대방의 공개 키를 결합합니다
- 4양쪽 모두 동일한 공유 비밀에 도달합니다 — 이 비밀은 네트워크를 통해 전송된 적이 없습니다
암호화된 URL — 파일 경로 숨기기
대부분의 사람들이 눈치채지 못하는 세부 사항이 있습니다: URL 경로도 정보를 노출합니다.
파일을 다운로드할 때 URL은 서버에 어떤 파일을 원하는지 알려줍니다. 많은 앱에서 URL은 다음과 같습니다:
/fs?path=/sdcard/DCIM/Camera/photo_2024.jpg트래픽을 스니핑하거나 브라우저 기록을 보는 사람은 어떤 파일에 접근했는지, 폰의 폴더 구조가 어떤지를 정확히 알 수 있습니다.
PlainApp은 URL의 파일 경로를 암호화합니다:
/fs?id=AxK9f2mQ7vR3xP8nWz...id 파라미터는 URL Token이라는 랜덤 키를 사용하여 XChaCha20-Poly1305로 암호화된 파일 경로입니다. 기본적으로 이 키는 영구적이며 앱을 재시작해도 변경되지 않으므로, 공유한 링크가 계속 작동합니다. 키 없이는 실제 경로를 추측하거나, 파일을 열거하거나, 다운로드 URL을 생성할 수 없습니다.
자체 서명 인증서를 사용한 HTTPS
PlainApp은 폰에서 자체 서명 TLS 인증서를 생성합니다. 이를 통해 은행과 웹사이트에서 사용하는 것과 동일한 기술인 암호화된 HTTPS 터널을 만듭니다.
인증서 사양:
- ECDSA with P-256 curve (주요 웹사이트와 동일한 방식)
- SHA256 서명
- 앱의 비공개 디렉토리에 저장
애플리케이션 계층의 XChaCha20 암호화와 결합하면 두 개의 독립적인 암호화 계층을 갖게 됩니다. 하나를 깨더라도 다른 하나를 깨지 않으면 소용이 없습니다.
로그인 보호
PlainApp은 비밀번호 추측을 방지하기 위해 로그인 시도를 제한합니다:
- IP 주소당 60초에 5회 시도
- 초과 시 연결이 거부됩니다
- 만료된 속도 제한 기록은 자동으로 정리됩니다
HTTP vs HTTPS: 솔직한 설명
PlainApp은 HTTP 및 HTTPS 모드를 모두 제공합니다. 차이점을 솔직하게 설명합니다:
| HTTP 모드 | HTTPS 모드 | |
|---|---|---|
| 파일 경로 URL | 암호화됨 (XChaCha20) | 암호화됨 (XChaCha20) |
| API 호출 | 암호화됨 (XChaCha20) | 암호화됨 (XChaCha20 + TLS) |
| 파일 다운로드 | 전송 중 암호화되지 않음 | TLS로 암호화됨 |
| 대상 사용자 | 초보자 (브라우저 SSL 경고 없음) | 개인정보 보호를 중시하는 사용자 |
두 모드 모두 XChaCha20-Poly1305로 API 호출을 암호화합니다 — SMS, 연락처 등의 데이터는 HTTP든 HTTPS든 항상 암호화됩니다.
HTTP 모드가 존재하는 이유는 자체 서명 인증서가 브라우저 경고("연결이 비공개가 아닙니다")를 발생시키기 때문입니다. 기술에 익숙하지 않은 사용자에게 이 경고는 혼란스럽고 불안하게 느껴집니다. HTTP는 이 불편함을 없애줍니다.
다만, HTTP 모드는 파일 전송을 암호화하지 않습니다. 공유 또는 공용 네트워크에서는 반드시 HTTPS를 사용하십시오.
권장 사항: 가능하면 항상 HTTPS를 사용하십시오. 브라우저의 자체 서명 인증서 경고를 수락하세요 — 로컬 네트워크 앱에서는 정상적이고 예상되는 동작입니다.
공급망 보안 — 의존성이 적을수록 위험도 적습니다
많은 사용자가 생각하지 못하는 보안 위협이 있습니다: 공급망 공격입니다. 앱이 수백 개의 서드파티 라이브러리를 사용하면, 그 중 어느 하나라도 손상되어 데이터를 탈취하거나, 암호화폐를 채굴하거나, 백도어를 여는 악성 코드가 삽입될 수 있습니다.
JavaScript 생태계에서 특히 위험합니다. 최근 몇 년간 event-stream, ua-parser-js, colors 같은 인기 npm 패키지가 해킹되어 수백만 프로젝트에 악성 코드가 주입되었습니다. 폰 관리 앱의 웹 UI가 수백 개의 JS 패키지를 사용한다면, 각각이 잠재적인 공격 경로가 됩니다.
PlainApp은 의도적인 접근 방식을 채택합니다: 가능한 한 적은 의존성을 사용하고, 필요한 경우 직접 코드를 작성합니다.
Android 앱 (plain-app)
- 의존성 주입 프레임워크 없음 (Hilt, Dagger 없음) — 단순한 싱글톤 사용
- 암호화는 잘 알려진 3개의 라이브러리만 사용: Google Tink, Bouncy Castle, 내장 Java Cryptography Extension
- 서버는 Ktor(JetBrains의 공식 Kotlin 프레임워크)로 구동 — 잘 알려지지 않은 서드파티 서버 라이브러리 없음
웹 UI (plain-desktop)
런타임 의존성이 34개에 불과합니다 — 일반적인 웹 앱보다 훨씬 적습니다. 보통 npm 패키지가 필요한 여러 기능을 직접 구현했습니다:
| 기능 | 일반적인 앱 | PlainApp |
|---|---|---|
| 알림 소리 | howler.js / tone.js | Web Audio API 직접 구현 |
| UUID 생성 | uuid npm 패키지 | RFC 4122 직접 구현 |
| 파일 업로드 | uppy / axios | 청크 업로드 큐 직접 구현 |
| WebRTC 비디오 | Twilio / Daily.co SDK | PeerConnection 직접 구현 |
| HTTP 클라이언트 | axios / ky | 네이티브 브라우저 fetch |
웹 측 암호화는 @noble/ciphers를 사용합니다 — 크고 비대한 대안 대신 신뢰성이 검증된 무의존성 JavaScript 암호화 라이브러리입니다.
왜 중요한가
추가하는 모든 의존성은 사용자가 통제할 수 없는 코드입니다. PlainApp의 접근 방식은 다음을 의미합니다:
- 악성 코드가 숨을 수 있는 곳이 적습니다
- 공격 표면이 작습니다
- 감사하기 쉽습니다 (전체 코드베이스가 오픈 소스)
- 폐기되거나 해킹된 npm 패키지로 인한 위험이 없습니다
많은 경쟁 앱이 놓치는 점
특정 앱을 지목하는 것이 아닙니다. 이 카테고리에서 흔히 보이는 패턴에 대한 이야기입니다:
| 보안 항목 | PlainApp | 경쟁 앱에서 흔한 방식 |
|---|---|---|
| API 트래픽 | HTTP 및 HTTPS 모두에서 암호화됨 (XChaCha20) | HTTP를 통한 평문 JSON |
| WebSocket 트래픽 | 암호화된 바이너리 프레임 (XChaCha20) | 일반 텍스트 JSON 또는 WebSocket 미사용 |
| 인증 토큰 처리 | 암호화 키로 사용, 헤더에 포함하지 않음 | HTTP 헤더로 전송 (쉽게 유출) |
| 파일 URL | 암호화된 경로 ID | 평문 경로 노출 |
| 키 교환 | ECDH (P-256) | 없음 |
| 요청 서명 | Ed25519 (기기 간만) | 없음 |
| 재전송 방지 | 30초 + nonce (Web); 5분 + 서명 (기기 간) | 없음 |
| 로그인 보호 | 속도 제한 + 이중 확인 | 무제한 |
| 데이터 라우팅 | 100% 로컬, 클라우드 없음 | 일부는 클라우드를 경유 |
| 의존성 | 최소한의 잘 알려진 라이브러리 | 수백 개의 npm 패키지 |
| 소스 코드 | 오픈 소스 (GPL-3.0) | 대부분 비공개 소스 |
어떤 앱이든 확인하는 가장 쉬운 방법: 앱을 사용하면서 Chrome DevTools(F12) → Network 탭을 여십시오. API 응답을 JSON으로 읽을 수 있다면, 그 앱은 암호화가 없는 것입니다.
직접 확인해 보십시오
PlainApp은 완전한 오픈 소스입니다. 이 글의 모든 내용은 코드에서 직접 확인할 수 있습니다.
github.com/plainhub/plain-app (GPL-3.0)자동화되고 추적 가능하며 위변조에 강한 릴리스 파이프라인
모든 설치 패키지는 GitHub와 F-Droid 인프라에서 자동으로 빌드됩니다. 빌드는 추적 가능하고 안전하며 수동 빌드 개입 흔적이 없습니다. PlainApp은 검증 가능한 빌드(SLSA)와 VirusTotal 검사 릴리스를 모두 제공하는 드문 Android 앱입니다.
GitHub 및 F-Droid 자동 빌드
릴리스 산출물은 로컬 수동 패키징이 아닌 CI 파이프라인에서 생성됩니다. 모든 산출물은 소스 커밋과 재현 가능한 빌드 워크플로까지 추적할 수 있습니다.
SLSA Provenance(Level 3) + VirusTotal 검사
각 릴리스에는 SLSA provenance 증명과 VirusTotal 검사 결과가 포함되어 공급망 감사 가능성을 높이고 설치 전 무결성 검증을 돕습니다.
자주 묻는 질문
HTTP 모드를 사용해도 PlainApp은 정말 암호화되나요?
네. 모든 API 호출(SMS, 연락처, 파일 목록 등)은 HTTP 및 HTTPS 모드 모두에서 XChaCha20-Poly1305로 암호화됩니다. HTTP 모드에서는 파일 다운로드만 암호화되지 않습니다. 완전한 보호를 위해서는 HTTPS를 사용하십시오.
브라우저에 보안 경고가 뜨는 이유는 무엇인가요?
PlainApp은 암호화를 위해 자체 서명 TLS 인증서를 사용합니다. 공인 인증 기관에서 발급한 것이 아니기 때문에 브라우저가 경고를 표시합니다. 로컬 네트워크 앱에서는 정상적이고 예상되는 동작입니다. '고급'을 클릭한 다음 '계속'을 클릭하면 됩니다.
같은 Wi-Fi에 있는 사람이 내 데이터를 볼 수 있나요?
PlainApp을 사용하면 볼 수 없습니다. API 호출은 XChaCha20-Poly1305로 암호화되며, HTTPS 모드에서는 파일 전송도 TLS로 암호화됩니다. 대부분의 경쟁 앱에서는 가능합니다 — API 호출이 평문 JSON으로 전송되기 때문입니다.
PlainApp은 VPN과 어떻게 다른가요?
VPN은 기기와 VPN 서버 간의 트래픽을 암호화합니다. PlainApp은 로컬 네트워크에서 폰과 브라우저 간의 트래픽을 암호화합니다. 서로 다른 목적을 가지고 있습니다 — PlainApp은 로컬 기기 관리를 보호하고, VPN은 인터넷 트래픽을 보호합니다.
코드가 정말 오픈 소스인가요?
네. PlainApp은 GPL-3.0 라이선스입니다. 전체 소스 코드는 github.com/plainhub/plain-app에서 확인할 수 있습니다. 모든 암호화 구현을 검토하고, 소스에서 직접 빌드하거나, 기여할 수 있습니다.