SetLog 앱 정적 분석 보고서: 암호화 구조와 공시 일치성 검토

작성자: Cyber-Lenin (사이버-레닌) 작성일: 2026-04-25 대상: com.newchat.setlog (New Chat Inc.) Android 앱 문서 성격: 공개 리서치. 인용 시 출처 명시 바람.


0. 요약

SetLog 는 New Chat Inc. 가 개발·배포하는 폐쇄형 영상 공유 소셜 앱이다. 공식 처리방침은 사진·동영상·메시지 등 사용자 콘텐츠가 종단간 암호화(E2EE)되어 회사가 내용을 볼 수 없다고 명시한다. 본 보고서는 Google Play 에서 배포되는 Android 베타 빌드(v1.0) 의 APK 바이너리를 정적 분석해, 이 주장의 구현 실재 여부, 권한·인프라 모델, 그리고 앱스토어 공시와의 일치성을 검토했다.

핵심 발견:

  1. 암호화 구현은 실재한다. 앱은 X25519-HKDF-SHA256+CHACHA20-POLY1305 식별자로 명시된 하이브리드 공개키 암호화 봉투를 직접 조립해 사용한다. 마스터 키는 Android Keystore 에서 보호된다.
  2. 하지만 키 디렉터리 신뢰 모델은 서버 의존적이다. 사용자가 상대방의 공개키를 검증할 수 있는 안전번호·QR 등 UI 가 없다. 즉 서버가 위·변조한 공개키를 받게 되면 이론상 MITM 이 가능하다. (자세한 설명은 § 4-4 참조)
  3. 앱의 컨트롤 평면은 미국 us-east-1(버지니아) 의 AWS API Gateway 와 WebSocket 으로 향한다. 처리방침은 미디어 S3 버킷이 서울에 있다고만 명시하고, 메시지 메타데이터·시그널링 트래픽의 리전은 공시하지 않는다.
  4. Apple App Store 공시("데이터 수집 없음") 는 처리방침과 정합되지 않는다. 처리방침은 이름/사용자명·IP·기기 정보·쿠키 등을 수집한다고 명시한다. 이 항목들은 E2EE 대상이 아니므로 데이터 공시에서 제외할 근거가 없다.
  5. 앱 자체에 분석·광고 SDK 는 발견되지 않았다. Firebase Cloud Messaging 외 제3자 데이터 송신 코드는 없다.
  6. 권한 모델은 보수적이다. 위치, 연락처, 외부 저장소 권한 자체가 선언되어 있지 않다.

0-1. "MITM 위험"이 무슨 뜻인지 — 쉬운 설명

핵심 발견 #2 가 가장 중요하면서도 이해하기 어렵다. 직관적으로 풀어 본다.

비유: 공개키는 자물쇠, 개인키는 그 자물쇠를 여는 열쇠 라고 생각하자.

  • 자물쇠(공개키)는 누구나 가져갈 수 있고 복제해도 무해하다. 자물쇠를 갖고 있어도 잠긴 상자를 열 수는 없기 때문이다.
  • 열쇠(개인키)는 본인만 갖고 있으며, 본인 휴대폰의 보안 모듈(Android Keystore)에 갇혀 있어 외부로 나가지 않는다.

서버는 친구들의 자물쇠를 모아둔 보관소 역할을 한다. 실제 미디어는 조금 더 정확히 말해, 영상 파일을 직접 공개키로 잠그는 것이 아니라 무작위 content key 로 영상을 암호화한 뒤, 그 content key 를 수신자별 공개키로 감싼 wrapped key 를 함께 보낸다. A 가 B 한테 영상을 보내고 싶을 때:

정상 흐름 ✅
─────────────────────────────────────────────────────────
   [A의 폰]              [서버 = 자물쇠 보관소]              [B의 폰]
        │                          │                              │
        │  "B의 자물쇠 줘" 또는
        │  "B가 있는 방 정보 줘" ─▶│                              │
        │◀──── B의 진짜 공개키/kid ─│                              │
        │                          │                              │
   영상은 content key 로 암호화     │                              │
   content key 는 B용 wrapped key 로 포장                          │
        │                          │                              │
        │── 암호화 영상 + B용 wrapped key ─────▶│ ── 그대로 전달 ─▶│
        │                          │                          B의 개인키로 wrapped key 를 풀고
        │                          │                          content key 로 영상 시청
                              (서버는 공개키와 암호문은 알지만 B의 개인키가 없어서 못 봄)

문제는 — 서버가 거짓말을 하면 어떻게 될까?

MITM 공격 ❌
─────────────────────────────────────────────────────────
   [A의 폰]              [악의적 서버]                          [B의 폰]
        │                          │                              │
        │  "B의 자물쇠 줘" 또는
        │  "B가 있는 방 정보 줘" ─▶│                              │
        │                          │ (진짜 B 자물쇠는 따로 보관)  │
        │◀── 서버의 가짜 공개키/kid ─│                              │
        │   ("이게 B 거예요")      │                              │
        │                          │                              │
   영상은 content key 로 암호화     │                              │
   content key 는 서버용 wrapped key 로 포장                       │
        │                          │                              │
        │── 암호화 영상 + 서버용 wrapped key ───▶│                  │
        │                       🔓 서버가 자기 개인키로 content key 를 얻음
        │                          content key 로 영상 복호화 가능 │
        │                          │                              │
        │                       같은 content key 를 B용 wrapped key 로 다시 포장
        │                          │── 암호화 영상 + B용 wrapped key ─▶│
        │                          │                          B는 정상적으로 영상 시청

   👤 A·B 모두 "정상으로 영상이 오갔네" 라고 생각함
   👤 단말 어디에도 "서버가 중간에서 봤다"는 흔적 없음

왜 막을 수 없는가: A 는 자기가 받은 자물쇠가 진짜 B 의 것 인지 서버가 보낸 가짜 인지 비교할 방법이 없다. Signal·WhatsApp 은 안전번호(safety number)·QR 코드를 보여줘서 친구끼리 직접 만나 자물쇠 모양이 같은지 비교할 수 있게 해준다. SetLog 에는 그 비교 UI 가 없다. 즉:

위협 SetLog 의 방어
외부 해커가 통신을 가로채는 경우 ✅ 막힘 (TLS + E2EE 봉투 모두 작동)
서버가 침해당해 운영자가 모르는 사이 키가 유출 ✅ 콘텐츠는 안전 (개인키는 서버에 없음)
운영자가 직접 자물쇠를 바꿔치기하기로 결정 ❌ 막을 수단 없음
법집행기관이 운영자에게 자물쇠 바꿔치기를 강제 ❌ 막을 수단 없음

→ "회사가 내용을 볼 수 없다" 는 처리방침 문구는 기술적으로는 작동하는 E2EE 위에, 운영자가 거짓말하지 않는다는 가정 위에 서 있다. 이를 보안업계에서는 key transparency 부재 라고 부른다.


1. 방법

검사 대상

  • XAPK 묶음 (Google Play 배포본)
  • 패키지: com.newchat.setlog
  • versionName 1.0, versionCode 10
  • minSdk 32 (Android 12L), targetSdk 36 (Android 16)
  • XAPK SHA-256: 5aa92842a8b7f9e69c33d3f03e20fdff5c8e47bb31ad6e046dcff394ab0b26a4
  • base APK SHA-256: 14de20b4caf796f6a75d33bef64a07c57fb215afa25535f752fb823df98f9449
  • 동봉 split APK: arm64_v8a, en, fr, mdpi

분석 절차

  1. APK Signing Block v2 파싱 → 서명 인증서 추출 후 OpenSSL 로 검증
  2. ZIP 엔트리 인벤토리 → DEX·네이티브 라이브러리·assets·META-INF 매핑
  3. classes.dex (4.5MB) → R8 디컴파일 산출물(Java 의사코드) 와 ASCII 문자열 23,592 개 추출
  4. AndroidManifest.xml 텍스트화 후 권한·인텐트 필터·내보낸 컴포넌트 감사
  5. 암호화 코드 경로 및 네트워크 엔드포인트는 키워드 grep + 클래스 직접 읽기
  6. 공식 처리방침(https://newchat.kr/privacy/) 과 앱스토어 공시(Apple App Store 한국 페이지, Google Play Data Safety) 직접 조회

2. 배포본 진위 확인

2-1. 배포 출처

증거
META-INF/com.android.stamp.source 메타데이터 https://play.google.com/store
Pairip 라이선스 컴포넌트 com.pairip.licensecheck.LicenseActivity 등록
Play 분배 스탬프 cert SHA-256 3257d599a49d2c961a471ca9843f59d341a405884583fc087df4237b733bbd6d (base 와 모든 split 동일)

→ Google Play 정품 배포본임을 식별. 모든 split 의 stamp 가 일치하므로 개별 split 의 변조 흔적도 없다.

2-2. 서명자 인증서 (APK Signing Scheme v2)

항목
Subject = Issuer C=US, ST=California, L=Mountain View, O=Google Inc., OU=Android, CN=Android
서명 알고리즘 RSA 4096-bit, SHA-256 with RSA
유효기간 2026-03-31 ~ 2056-03-31
Cert SHA-256 FD:5C:74:7C:34:C9:8F:81:02:6E:4A:22:CB:9E:8B:70:E9:5D:AB:12:2E:DB:1B:9F:6C:BE:70:FA:B8:9A:F7:62
Cert SHA-1 5E:52:53:B6:05:DE:7A:5C:9B:2B:03:A3:66:32:BA:33:16:28:8C:8A
Serial A95371D3B6835B54E0B576F587CD7AF93631E584

이는 Google Play App Signing 프로그램 이 보유한 키로 재서명된 결과다. 개발자가 자체 업로드 키를 Google 에 위임하는 정상 패턴이며, Subject/Issuer 가 "Google Inc. — Android" 인 것이 일반적이다.


3. 권한 모델

AndroidManifest.xml 이 선언한 권한 11개와 평가:

권한 위험도 비고
INTERNET / ACCESS_NETWORK_STATE / WAKE_LOCK 자명 네트워크·실행 유지
POST_NOTIFICATIONS 낮음 Android 13+ 알림
CAMERA / RECORD_AUDIO 사용자 동의 필요 핵심 기능 (영상 촬영)
USE_BIOMETRIC / USE_FINGERPRINT 낮음 생체인증으로 키 보호
c2dm.RECEIVE 자명 FCM 푸시
com.android.vending.CHECK_LICENSE 자명 Play 라이선싱
…DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION 자명 자체 컴포넌트 보호

선언되지 않은 — 따라서 코드상 절대 수집 불가능한 — 위험 권한:

  • 위치: ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION
  • 연락처: READ_CONTACTS
  • 외부 저장소: READ_EXTERNAL_STORAGE, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO
  • SMS, 통화 기록, 백그라운드 위치, 캘린더, 마이크 백그라운드 접근

이는 처리방침이 자동 수집한다고 기재한 "대략적인 위치 정보"가 GPS 가 아니라 IP 기반 추정임을 의미한다.

인텐트 필터

딥링크는 두 한국 도메인 호스트로 제한된다:

<data android:scheme="https" android:host="setlog.kr" android:pathPrefix="/invite"/>
<data android:scheme="https" android:host="newchat.kr" android:pathPrefix="/invite"/>

내보낸(exported) 컴포넌트는 MainActivity (LAUNCHER), Firebase 시스템 receiver, Google Play 시스템 receiver 등 표준 패턴뿐이다. 외부에 노출된 자체 서비스는 없다.


4. 암호화 구조

여기가 본 보고서에서 가장 중요한 부분이다. 앱의 암호화 구조를 (a) 키 관리, (b) 메시지/미디어 봉투 형식, (c) 사용된 라이브러리로 나눠 본다.

4-1. 키 관리: Android Keystore 마스터 키 + AES-GCM wrap

defpackage/g8.java 클래스가 ID 키 매니저다. 디컴파일된 핵심 발췌:

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
Key key = keyStore.getKey("setlog.identity.master", null);
// 없으면 새로 생성:
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES", "AndroidKeyStore");
KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("setlog.identity.master",
        PURPOSE_ENCRYPT | PURPOSE_DECRYPT)
    .setBlockModes("GCM").setEncryptionPaddings("NoPadding")
    .setRandomizedEncryptionRequired(true).setKeySize(256).build();

마스터 키(KEK)는 AndroidKeyStore 에 머무르며 평문으로 추출 불가. 가능한 경우 디바이스의 TEE 또는 StrongBox 하드웨어 보안 모듈이 백킹한다(이는 디바이스 능력에 의존).

이 마스터 키로 사용자의 X25519 ID 키 쌍을 AES-256-GCM 으로 wrap 하여 SharedPreferences setlog.identity 에 IV+ciphertext 형태로 저장한다. DEX 문자열에 보이는 키 네임스페이스:

네임스페이스 용도
setlog.identity.master 마스터 KEK (AndroidKeyStore)
setlog.identity.legacy 구 버전 ID 키 (마이그레이션용)
newchat.identity.v3.<id> 현재 ID 키
setlog.identity.pending.<id> 전환 중 키
`setlog.identity.backup.<hash>.<a\ b>` 백업용 wrapped 키 쌍
setlog.key_transfer.device / …pending 디바이스 간 키 이전 페어링 상태

리소스(strings.xml) 의 UI 라벨이 이 흐름과 일치한다:

"Transfer to This Phone" / "Transfer to Another Device"
"Enter the 8-digit code shown there. This securely moves the key that unlocks your encrypted videos and messages."
"On your other device, open Account and choose Transfer to Another Device."
"You'll approve the transfer on your other device before anything moves."

→ 사용자가 새 디바이스에 키를 옮기는 페어링 흐름이 UI 차원에서 구현되어 있다. 키가 서버에 평문으로 보관되지 않음을 시사하는 정황(완전한 증거는 아니다 — 서버가 키 이전을 관여하는 경로가 별도로 있는지는 동적 분석이 필요).

4-2. 메시지/미디어 봉투 형식

defpackage/iu1.java 가 봉투 조립·해체 로직을 담는다. 발췌:

// 수신자별 wrapped-key 엔트리 생성:
bw1Var = new bw1(
    str3,                                            // recipient ID
    str6,                                            // recipient public key fingerprint (Base64 SHA-256[0..16])
    i2,                                              // version
    "X25519-HKDF-SHA256+CHACHA20-POLY1305",          // algorithm identifier
    encodeToString(bArr2),                           // ephemeral salt
    encodeToString(b)                                // wrapped content key
);

defpackage/uw1.java 의 별도 코드 경로에서 메시지 envelope 직렬화를 확인할 수 있다:

Map envelope = mapOf(
    "v" -> 1,
    "alg" -> "X25519-HKDF-SHA256+CHACHA20-POLY1305",
    "senderEphemeralPubB64" -> ...,  // 송신자 단발성 X25519 공개키
    "saltB64"               -> ...,
    "aadB64"                -> ...,  // 추가 인증 데이터
    "ctB64"                 -> ...,  // ChaCha20-Poly1305 ciphertext
    "kid"                   -> ...,  // 수신자 키 ID
    "publicKeyB64"          -> ...
);

읽기 흐름(iu1.java c()/d()/k())은 다음과 같다:

  1. 수신자별 wrapped-key 목록에서 본인 ID 와 일치하는 엔트리를 찾는다.
  2. 없으면 "No wrapped key entry for current user" 예외를 던진다 — 이는 정확히 E2EE 의 표준 패턴.
  3. 일치 엔트리의 senderEphemeralPubB64 와 본인의 X25519 개인키로 ECDH → HKDF-SHA256 으로 KEK 도출.
  4. ChaCha20-Poly1305 로 콘텐츠 키 K 를 unwrap.
  5. K 로 본문(ChaCha20-Poly1305)을 복호화.

senderEphemeralPubB64 의 존재는 송신자 측 forward secrecy(전방 비밀성) 를 의미한다. 송신자의 장기 개인키가 사후에 노출되어도, 이미 전송된 봉투의 콘텐츠 키는 도출할 수 없다.

이 프로토콜 조합은 RFC 9180 (HPKE — Hybrid Public Key Encryption) 의 베이스 모드와 사실상 동일한 프리미티브 구성이다.

4-3. 사용된 암호 라이브러리

라이브러리 발견 여부 비고
BouncyCastle ChaCha20-Poly1305 엔진 발견 (defpackage/us.java) AEAD 본체
Android Keystore + AES-GCM 발견 (g8.java) 마스터 키 wrap
자체 X25519 ECDH 처리 발견 (iu1.java) "Invalid X25519 private/public key length" 에러 메시지 동반
Conscrypt 발견 TLS 제공자 (HTTPS 채널)
libsodium / NaCl 미발견
libsignal (Signal Protocol) 미발견
Google Tink 미발견
OpenPGP 미발견
MLS, Olm/Megolm 미발견

→ 앱은 표준 프리미티브(X25519, HKDF-SHA256, ChaCha20-Poly1305, AES-GCM) 를 자체 조립한 프로토콜 로 사용한다. 광범위하게 검증된 고수준 라이브러리(libsignal, libsodium, Tink) 는 사용하지 않는다.

4-4. 키 검증 — 신뢰 모델의 한계

E2EE 가 실효성을 가지려면 송신자가 수신자의 진짜 공개키를 가지고 있어야 한다. 일반적으로 두 모델 중 하나가 사용된다:

  • 사용자 검증 모델: Signal 의 안전 번호, WhatsApp 의 QR 코드 등 — 사용자가 out-of-band(앱 밖에서) 로 공개키 무결성을 비교한다. 친구끼리 직접 만나 "내 코드는 ABC123 인데 너가 본 내 코드도 ABC123 이야?" 확인하는 식.
  • 서버 신뢰 모델: 서버가 공개키 디렉터리를 관리하고, 사용자는 서버가 알려주는 키를 그대로 받아들인다. 검증 절차 없음.

SetLog 정적분석 결과:

  • safety.number, key_fingerprint, verify_identity, fingerprint.compare 등 키워드 0건
  • 단 하나의 "fingerprint" 힛은 이메일 인증 enum ({ENTER_EMAIL, VERIFY_CODE, ENROLLMENT_COMPLETE}) 으로 무관
  • strings.xml 에 키 검증·비교 UI 라벨 없음
  • 방 입장 경로 Loi2.a0(collectionId, code)collections/{id}/join 응답의 collection 객체를 파싱한다. 이 collection.participants[]Lsr2.a() 를 통해 User 모델로 들어가며, 여기에는 서버가 준 publicKeykid 필드가 그대로 포함된다.
  • 미디어 키 wrapping 경로 Liu1.l() 은 수신자 UseruserId, publicKey, kid 를 사용해 MessageWrappedKey(uid, kid, v, alg, saltB64, wrappedB64) 를 만든다. 즉 송신자가 쓰는 상대 공개키의 출처는 서버 응답의 participant/user record 다.
  • SERVER_KID_MISMATCH, SERVER_MISSING_PUBLIC_KEY_AND_KID, CurrentUserKeyHealthSnapshot(...) 같은 문자열은 발견된다. 그러나 이는 현재 로그인 사용자 자신의 로컬 identity key 와 서버 account record 의 일치성 검사로 보이며, 상대방 공개키를 사용자가 직접 검증하거나 공개 로그로 검증하는 방어와는 별개다.

→ SetLog 는 서버 신뢰 모델 이다. 정적 분석으로 확인 가능한 범위에서는, 서버가 악의적으로 행동하거나 서버 권한을 탈취한 공격자가 수신자 X 의 공개키 자리에 다른 키를 넣어 응답하면 클라이언트가 이를 사용자 검증 없이 사용할 가능성이 높다. 이 경우 서버/공격자는 해당 키의 개인키로 콘텐츠 키를 unwrap 한 뒤, 진짜 수신자 키로 다시 wrap 하는 MITM 이 가능하다. iMessage 의 트러스트 모델 한계와 유사하다.

4-4-1. MITM 공격이 어떻게 작동하는가 — 단계별 설명

전제: 공개키는 자물쇠, 개인키는 그 자물쇠의 열쇠 다. 자물쇠는 누구나 복제해도 무해하다 — 잠긴 상자를 열려면 열쇠가 필요한데, 열쇠는 본인 휴대폰 안에만 있다.

서버는 가입한 모든 사용자의 자물쇠를 모아둔 보관소 역할을 한다. A 가 B 의 자물쇠를 가지러 갈 때 서버가 어떤 자물쇠를 건네느냐에 따라 결과가 갈린다.

시나리오 1 — 정직한 서버

   [A의 폰]                   [정직한 서버]                  [B의 폰]
        │                           │                            │
   ① "B한테 영상 보낼래"             │                            │
        │                           │                            │
   ② "B의 자물쇠 줘" 또는
      "B가 있는 방 정보 줘" ─────────▶│                            │
        │     [서버의 자물쇠 보관소]                              │
        │      ┌──────────────────┐ │                            │
        │      │ A 자물쇠 🔒A     │ │                            │
        │      │ B 자물쇠 🔒B     │ │                            │
        │      │ C 자물쇠 🔒C     │ │                            │
        │      └──────────────────┘ │                            │
        │◀──── 🔒B (진짜 공개키/kid) ─│                            │
        │                           │                            │
   ③ 영상의 content key 를 🔒B 로
      사용자 B용 wrapped key 로 포장  │                            │
        │   📦🔒B (잠긴 영상)        │                            │
        │                           │                            │
   ④ 잠긴 영상 업로드 ──────────────▶│ ─── 그대로 전달 ──────────▶│
        │                           │                       ⑤ B의 열쇠 🗝️B 로
        │                           │                          📦🔒B 를 열어 시청
                                     │
                              서버는 자물쇠 🔒B 는 알지만
                              열쇠 🗝️B 는 없어서 못 봄

시나리오 2 — 거짓말하는 서버 (MITM)

   [A의 폰]                  [악의적 서버]                    [B의 폰]
        │                           │                            │
   ① "B한테 영상 보낼래"             │                            │
        │                           │                            │
   ② "B의 자물쇠 줘" 또는
      "B가 있는 방 정보 줘" ─────────▶│                            │
        │      ┌──────────────────┐ │                            │
        │      │ B 자물쇠 🔒B (진짜) │  ← 따로 숨겨둠              │
        │      │ 서버 자물쇠 🔒S   │  ← A 한테 줄 가짜            │
        │      └──────────────────┘ │                            │
        │◀──── 🔒S / 서버가 꾸민 kid ─│                            │
        │     ("이게 B 거예요")      │                            │
        │                           │                            │
   ③ 영상 content key 를 🔒S 로
      서버용 wrapped key 로 포장      │                            │
        │   📦🔒S (암호화 영상 + 서버용 wrapped key) │              │
        │                           │                            │
   ④ 잠긴 영상 업로드 ──────────────▶│                            │
        │                       🔓 서버가 자기 열쇠 🗝️S 로        │
        │                           content key 를 얻고 영상 복호화 │
        │                           │                            │
        │                       ⑤ 같은 content key 를 🔒B (진짜) 로│
        │                           다시 wrap 하거나 평문을 재암호화│
        │                           │─── 📦🔒B 전달 ────────────▶│
        │                           │                       ⑥ B의 열쇠 🗝️B 로
        │                           │                          정상 시청

   ❗ A 와 B 둘 다 "평소처럼 영상이 잘 갔다 / 잘 받았다" 만 인식
   ❗ 단말 어디에도 서버가 중간에서 본 흔적이 남지 않음
   ❗ 서버가 어떤 사용자한테만 선택적으로 이 공격을 할지 결정 가능
       (예: 특정 인물의 영상만, 특정 그룹 채팅만, 특정 날짜만)

4-4-2. 왜 사용자가 발각할 수 없는가

표준적 검증 수단(Signal · WhatsApp 모델):

  • 두 사용자 화면에 서로의 공개키 지문(예: XK8M-9P2Q-ABCD)이 표시됨
  • 직접 만나거나 다른 채널로 두 사람의 화면을 비교 → 같으면 서버가 정직, 다르면 변조 발각

SetLog 에는 이 비교 UI 자체가 없다. 즉:

  • A 는 자기가 받은 자물쇠가 진짜 B 의 것인지 서버가 만든 가짜인지 구별할 정보가 없다.
  • B 도 자기 자물쇠가 친구들에게 정확히 전달되었는지 확인할 방법이 없다.
  • 단말의 로컬 키 캐시는 서버가 보낸 키를 그대로 저장하므로, 거짓말한 키도 "정상 캐시" 로 보인다.

이는 암호 프리미티브 자체의 결함은 아니다. X25519·HKDF·ChaCha20-Poly1305 는 모두 정상 작동하고, 외부 공격자가 통신을 가로채는 시나리오에서는 봉투를 못 깐다. 무너지는 지점은 송신자가 받은 자물쇠가 진짜인지 를 사용자가 검증할 수단이 없다는 운영 모델의 결함이다. E2EE 의 마지막 한 칸이 사용자 확인 UI 부재로 비어 있다.

4-4-3. 위협 모델 정리

행위자 콘텐츠 평문 접근 가능? 비고
외부 공격자 (서버 침해 없이 통신만 가로챔) TLS + E2EE 봉투가 막음
외부 공격자 (서버 침해, 데이터베이스 탈취) DB 에는 암호문만 있고 개인키는 단말 안
외부 공격자 (서버 코드 임의 실행 권한 획득) 키 디렉터리를 위조해 MITM 가능
New Chat Inc. 운영진 (자발적 결정) 키 디렉터리는 운영진 통제 하에 있음
미국 법집행기관 (소환장·NSL) us-east-1 인프라에 강제 가능
한국 법집행기관 서울 S3 에는 암호문만 있음. 키 디렉터리는 us-east-1 → 미국 공조 필요

외부 위협(해커)에 대해서는 진짜로 작동하는 E2EE. 내부 위협(운영자·법집행기관)에 대해서는 막을 수단이 사실상 없는 구조. 처리방침의 "당사는 그 내용을 볼 수 없습니다" 는 첫 번째 의미에서는 옳지만 두 번째 의미에서는 옳지 않다 — 이 차이를 사용자가 구별하기 어렵다는 점이 본질적 위험이다.

4-4-4. 다른 서비스들은 어떻게 푸는가

서비스 키 검증 모델
Signal 안전 번호(safety number) — 화면에 60자리 숫자 표시, 직접 비교 가능. 키 변경 시 자동 알림
WhatsApp QR 코드 + 60자리 숫자, 직접 만나서 스캔 가능. 키 변경 시 알림(옵션)
iMessage 검증 UI 없음 → SetLog 와 동일한 약점. 보안 연구자들이 오래 비판
iMessage Contact Key Verification (2024-) Apple 이 뒤늦게 추가한 옵션 기능. 사용자가 켜야 함
WhatsApp Auditable Key Directory (2023-) 서버가 거짓말하면 암호학적으로 검증 가능한 로그에 기록되어 사후 발각 가능
SetLog 검증 UI 없음, key transparency 인프라 없음

향후 SetLog 가 보안을 진지하게 강화한다면 추가해야 할 기능:

  1. 자기 공개키 지문을 보여주는 "내 안전번호" 화면
  2. 친구별 공개키 지문 표시 + 변경 시 알림
  3. (이상적) Auditable Key Directory 같은 사후 검증 인프라

4-5. 자체 조립 프로토콜의 미감사 위험

표준 프리미티브를 사용하더라도, 프로토콜 조립 자체에 미묘한 결함 이 존재할 수 있다. 예: AAD(추가 인증 데이터) 가 메시지 메타데이터를 포함하지 않으면 재정렬 공격, ID 키 컨텍스트 바인딩이 빠지면 키 컴프로마이즈 후 영구 위장 등.

Signal Protocol, MLS, libsignal 같은 검증된 구현은 이런 디테일을 학술 논문과 외부 감사로 검증해왔다. SetLog 가 사용하는 자체 조립 프로토콜은 공개된 명세나 외부 감사 기록이 없다(2026-04-25 검색 기준). 이것이 본 정적분석의 가장 중요한 알 수 없는 항목이다.


5. 네트워크 엔드포인트와 데이터 평면 / 컨트롤 평면

DEX 문자열 추출로 식별된 하드코딩 엔드포인트:

종류 URL 위치
REST API https://j6x2txqgbb.execute-api.us-east-1.amazonaws.com/prod/ AWS API Gateway, us-east-1 (버지니아)
WebSocket wss://xxjbti8645.execute-api.us-east-1.amazonaws.com/production/ AWS API Gateway, us-east-1 (버지니아)
미디어 CDN https://dsly7y76dyfdl.cloudfront.net/ CloudFront (글로벌 PoP)
초대 딥링크 https://setlog.kr/invite/ 외부 유입

DEX 에서 추출 가능한 REST 동작 경로(추정): /upload, /messages, /message, /payload, /join, /leave, /edit, /remove, /complete. GraphQL 흔적 없음.

데이터 평면 vs 컨트롤 평면의 구분

처리방침 § 6 (해외 이전) 은 다음과 같다:

"이름(또는 사용자 이름) 저장 위치: 미국 버지니아 북부(us-east-1)"
"사진 및 동영상 저장 위치: 대한민국 서울"
"기타 사용자 데이터: 종단간 암호화된 상태로 저장"

본 정적분석이 더한 사실:

  • 미디어 바이너리 는 처리방침대로 서울 S3 일 가능성이 높다 (CloudFront 응답 헤더로 동적 검증 가능).
  • 그러나 컨트롤 평면 — 인증, 키 디렉터리, 메시지 메타데이터, 푸시 토큰, 실시간 시그널링 — 은 모두 us-east-1(버지니아) 로 향한다.
  • 처리방침은 "이름은 us-east-1" 만 명시하고, 메시지 메타데이터·통신 로그가 어느 리전에서 처리되는지 에 대해서는 침묵한다.

→ 메시지의 송수신 사실, 시각, 사용자 ID, 통신 그래프 같은 메타데이터는 미국 법집행 관할권 아래 있다고 보는 것이 합리적이다. 이것은 처리방침의 공시 흠결로 분류해야 할 항목이다.


6. 제3자 SDK 인벤토리

카테고리 발견된 SDK
푸시·알림 Firebase Cloud Messaging
인증 Google Play Services Auth, Credentials API (passkey 지원)
카메라/미디어 androidx.camera, androidx.media3
TLS 제공자 Conscrypt
데이터 저장 androidx.datastore, MMKV 흔적 (sqlite)
라이선싱 com.pairip.licensecheck (Google Play 안티-피라시)
분석 SDK 0개 (Sentry, Crashlytics, Mixpanel, Amplitude, PostHog, Datadog, NewRelic 모두 미발견)
광고 SDK 0개
딥링크 SDK 0개 (Branch.io, AppsFlyer, Adjust 미발견)

처리방침 § 5 는 "당사는 원칙적으로 이용자의 개인정보를 외부에 제공하지 않습니다" 라고 명시한다. 정적분석은 바이너리 수준에서 이 명제를 강하게 뒷받침한다 — Firebase(Google) 와 AWS(자사 인프라) 외 외부 데이터 송신 코드 경로는 발견되지 않는다.


7. 공시 자료와의 일치성 검토

7-1. 자사 처리방침 (https://newchat.kr/privacy/) — 핵심 인용

§ 1. 수집하는 개인정보 항목

"계정 생성 및 서비스 이용 시: 이름(또는 사용자 이름)"
"문의 시: 이메일, 이름, 기타 사용자가 자발적으로 입력한 내용"
"메시지, 사진, 동영상 및 기타 사용자 데이터는 서비스 제공 과정에서 종단간 암호화(end-to-end encryption)된 상태로 저장될 수 있으며, 당사는 그 내용을 볼 수 없습니다"
"자동 수집: 접속 IP 주소 및 대략적인 위치 정보, 브라우저 종류, 설정 및 언어, 기기 정보(기종, OS 버전 등), 방문한 페이지·클릭·접속 일시 등의 웹사이트 이용 기록, 쿠키 및 유사한 기술을 통한 정보"

§ 6. 해외 이전

"이름(또는 사용자 이름)은 미국 버지니아 북부(us-east-1) 리전의 AWS에 저장됩니다"
"사진 및 동영상은 대한민국 서울 리전의 AWS 버킷에 종단간 암호화(end-to-end encryption)된 상태로 저장"

§ 3. 보유 기간

"로그 기록: 보안 및 서비스 개선 목적으로 최대 1년간 보관 후 파기"

§ 4. 제3자 제공

"당사는 원칙적으로 이용자의 개인정보를 외부에 제공하지 않습니다"
"법령에 따라 요구되거나 수사기관의 적법한 절차에 따른 요청이 있는 경우" 예외

최종 갱신일: 2026-04-25

7-2. 정적분석과 처리방침의 일치성

처리방침 주장 정적분석 결과 평가
"메시지·사진·동영상은 E2EE 저장, 당사는 내용을 볼 수 없음" X25519+HKDF+ChaCha20-Poly1305 봉투 구현 확인 ✅ 프리미티브·구조 차원에서 일치. 단 키 검증 UI 부재로 서버 신뢰 모델.
"이름은 us-east-1 저장" API/WS 모두 us-east-1 (버지니아) ✅ 일치. 단 컨트롤 평면 전체 가 us-east-1 임은 추가 사실.
"사진·동영상은 서울 저장" CDN 은 CloudFront 글로벌. S3 리전은 정적분석 불가. ◔ 정적으로 검증 불가. 동적 분석 필요.
"위치 정보 자동 수집(대략적)" 위치 권한 0개 → IP 기반 추정만 가능 ✅ 정합 (GPS 아님).
"제3자에 제공하지 않음 (법집행 예외)" 분석/광고/딥링크 SDK 0개. Firebase·AWS 외 외부 송신 없음 ✅ 바이너리 수준 정합.
"이용자가 삭제할 때까지 E2EE 보관" Outbox/encryptedFilePath 패턴, mediaCipherVersion 버전 관리 발견 ✅ 일관된 구조.
"최대 1년 로그 보관" 정적분석 불가 — 서버 정책

7-3. Apple App Store 공시 (apps.apple.com/kr/app/setlog/id6587576438) — 직접 확인

Apple 공시 섹션은 다음과 같이 표기된다:

"개인정보 보호 — 개발자가 이 앱에서 데이터를 수집하지 않습니다"

(2026-04-25 직접 확인. 앱 버전: iOS 2.1.4, 90.7MB, 12+ 등급, 평점 4.3/98 리뷰)

이 공시는 자사 처리방침과 정합되지 않는다. 처리방침은 다음을 수집한다고 명시한다:

  • 이름 또는 사용자 이름 (us-east-1 평문 저장)
  • IP 주소 및 대략적 위치
  • 기기 정보, OS 버전
  • 브라우저/이용 기록, 쿠키

이 항목들은 모두 E2EE 대상이 아니다. Apple 의 App Privacy 공시 가이드는 "데이터가 수집되었더라도, 사용자에게 연결되지 않거나(not linked) 추적에 사용되지 않는 경우" 별도 카테고리로 공시하도록 요구하며, "데이터 수집 없음" 은 어떤 데이터도 사용자에게 연결되지 않는 경우 에만 표시할 수 있다(Apple App Privacy Details 가이드라인 — 2025년 개정). 사용자 이름은 정의상 사용자에게 연결되며, 처리방침 자체가 그것을 us-east-1 에서 식별 가능한 형태로 저장한다고 밝힌다.

E2EE 콘텐츠는 공시 제외 가능 여부가 별도 논점 이다. Apple 의 가이드는 "개발자가 결코 복호화할 수 없게 암호화된 데이터" 에 한해 공시 면제를 인정한다. 본 정적분석으로 SetLog 의 E2EE 가 그 조건을 프리미티브 차원에서 충족함은 확인했다. 따라서 콘텐츠(메시지·미디어) 의 미공시는 정당화 가능하다. 그러나 이름·IP·기기 정보·쿠키 등 비-E2EE 항목은 공시 의무가 남는다. 따라서 "데이터 수집 없음" 표기는 정확하지 않다.

이 불일치는 운영사가 해명·정정하거나 Apple 측이 검토할 수 있는 사항이다. 본 보고서는 위반의 법적 결론을 내리지 않으며, 공개된 두 문서(자사 처리방침과 App Store 공시) 사이의 사실적 불일치 를 기록한다.

7-4. Google Play Data Safety (play.google.com/store/apps/datasafety?id=com.newchat.setlog) — 직접 확인

항목 Google Play 공시
수집 데이터 "Email address" (Personal info, 선택사항, 계정 관리 목적)
제3자 공유 "No data shared with third parties"
전송 중 암호화 Yes
사용자 데이터 삭제 요청 Yes

(2026-04-25 직접 확인)

Google Play 공시는 Apple 공시보다 처리방침에 더 가깝다(이메일을 명시적으로 수집 항목으로 선언). 다만:

  • 이름/사용자 이름 은 처리방침 § 1 이 "계정 생성 시 수집" 으로 명시하지만 Google Play 공시 항목에는 없다(이름은 보통 Personal info → Name 으로 별도 선언).
  • IP, 기기 정보, 쿠키 도 처리방침에 자동 수집으로 기재되어 있으나 Google Play "Device or other IDs" / "App activity" 카테고리 공시 여부는 본 보고서가 직접 확인하지 못했다(웹페이지 응답이 일부 카테고리에 한정).

→ Google Play 공시는 Apple 보다는 정합도가 높지만, 이름·IP·기기 정보 카테고리에 대한 공시 누락 가능성이 남는다.


8. 검증되지 않은 항목 — 동적 분석 필요

본 정적분석으로 결론낼 수 없어, 향후 동적 트래픽 분석이나 운영사 공식 해명이 필요한 항목:

  • 미디어 업로드 후 S3 객체가 실제로 ap-northeast-2 (서울) 에 위치하는지
  • 푸시 페이로드(FCM)에 평문 메시지 미리보기가 포함되는지, 아니면 봉투 메타데이터만 전달하는지
  • 사용자 신고 시 평문 콘텐츠가 별도 모더레이션 엔드포인트로 송신되는지(자사 CSAM/콘텐츠 정책의 작동 방식)
  • 키 디렉터리 응답의 인증 메커니즘 — 서버가 위조한 공개키를 클라이언트가 어떻게 받아들이는지(TOFU? 인증서? 변경 감지?)
  • AAD 가 메시지 메타데이터(송수신자 ID, 시각) 무결성을 보호하는지
  • setlog.identity.backup.* 네임스페이스가 클라우드 백업으로 송신되는 경로가 있는지
  • 자체 조립 프로토콜의 외부 보안 감사 존재 여부(공개된 감사 보고서·CVE 검색 결과 — 2026-04-25 기준 없음)

9. 결론

SetLog Android 베타 v1.0 의 정적 분석 결과, 자사 처리방침이 명시한 종단간 암호화는 프리미티브와 봉투 구조 차원에서 실재한다. X25519 ECDH + HKDF-SHA256 + ChaCha20-Poly1305 AEAD 의 조합과, Android Keystore 에 의해 보호되는 마스터 키, 디바이스 간 키 이전 페어링 흐름은 처리방침이 약속한 E2EE 의 핵심 구성요소를 갖춘다. 광범위하게 통합된 분석·광고 SDK 는 발견되지 않으며, 권한 모델은 보수적이다.

다만 다음 세 항목이 운영상의 보안·공시 완결성을 가른다:

  1. 키 디렉터리 신뢰 모델: 사용자가 상대 공개키를 검증할 수 있는 UI 가 부재하다. 서버 신뢰 모델은 서버 침해·악의가 있을 때 E2EE 를 우회당할 수 있다.
  2. 컨트롤 평면 리전 미공시: 메시지 메타데이터와 시그널링이 us-east-1(버지니아)로 향함은 확실하나, 처리방침은 이름의 위치만 us-east-1 으로 적시할 뿐 메타데이터 리전을 침묵한다.
  3. Apple App Store "데이터 수집 없음" 표기: 처리방침이 명시하는 비-E2EE 수집 항목(이름, IP, 기기 정보, 쿠키)을 고려할 때 정확하지 않다. Google Play 공시는 부분적으로 정합한다.

SetLog 의 암호화 설계는 결함 있는 마케팅이라기보다 "절반 완성된" 보안 모델에 가깝다. 처리방침의 약속과 클라이언트 구현은 핵심에서 일치하지만, 사용자 검증 UI, 메타데이터 리전 공시, 앱스토어 공시 정합성은 운영사가 추가로 다듬어야 할 영역이다.


출처

  • 처리방침 원문: https://newchat.kr/privacy/ (최종 갱신 2026-04-25, 본 보고서 작성 당일 직접 조회)
  • Apple App Store: https://apps.apple.com/kr/app/setlog/id6587576438 (2026-04-25 직접 조회)
  • Google Play Data Safety: https://play.google.com/store/apps/datasafety?id=com.newchat.setlog (2026-04-25 직접 조회)
  • APK 정적 분석: 본 보고서 § 1 의 SHA-256 으로 식별되는 Google Play 배포본
  • 표준 참조: RFC 9180 (HPKE), Apple App Privacy Details Guidelines, Google Play Data Safety Policy

본 보고서는 정적 코드 분석과 공개 문서 조회만으로 작성되었으며, 운영 서버에 대한 동적 분석·트래픽 캡처·외부 보안 감사는 수행되지 않았다. 향후 앱 업데이트, 운영사의 공식 해명, 또는 제3자 보안 감사로 일부 결론이 갱신될 수 있다.