사이드프로젝트 slecs

API URL을 NDK로 숨겨 안드로이드 앱 보안 강화

목차

보안 강화하면서 NDK까지 끌어들인 날

앱 보안 점검하다가 결국 빌드 설정 전체를 갈아엎었음. 처음엔 ProGuard 규칙만 다듬으려고 했는데, API URL이 평문으로 코드에 박혀있는 걸 보고 결심함. 디컴파일 한 번이면 다 노출되는 구조였음.

API URL을 NDK로 옮긴 이유

문자열 상수로 두면 APK 디컴파일 도구 하나면 추출됨. 그래서 C++ 레이어로 내려보냄.

extern "C" JNIEXPORT jstring JNICALL
Java_app_security_ApiConfig_getEndpoint(JNIEnv* env, jobject) {
    const char* parts[] = { "https://", "api.", "example", ".com" };
    // 런타임 조립 후 반환
}

문자열 그대로 박지 않고 분할해서 런타임에 합치도록 했음. 정적 분석에서 한 번에 안 잡히게 함. 완벽한 방어는 아니지만 진입장벽은 확실히 올라감.

위치 Before After
API URL Kotlin 상수 NDK + 분할 조립
빌드 설정 minifyEnabled false release만 true
네트워크 cleartext 허용 network_security_config
키스토어 gradle.properties 환경변수

ProGuard 규칙 정리하면서 만난 함정

릴리즈 빌드 켰더니 앱이 즉사함. 직렬화 클래스가 난독화되면서 JSON 파싱이 다 깨짐. 결국 아래처럼 케이스별로 keep 규칙 추가.

  • 모델 클래스: -keep class app.domain.model.** { *; }
  • 리플렉션 쓰는 라이브러리: -keepattributes Signature, *Annotation*
  • 콜백 인터페이스: -keepclassmembers 로 메서드만 보존
  • Kotlin Coroutines: 공식 가이드 규칙 그대로 복붙

-printusage, -printseams 옵션 켜고 빌드 결과물 확인하면서 한 줄씩 검증함. 이거 안 하고 무작정 keep만 늘리면 난독화 의미 없어짐.

network_security_config 적용

매니페스트에 usesCleartextTraffic="false" 박고 별도 config XML 만듦. 개발 빌드만 로컬 IP 허용하도록 분기.

<domain-config cleartextTrafficPermitted="false">
    <domain includeSubdomains="true">api.example.com</domain>
    <pin-set expiration="2027-01-01">
        <pin digest="SHA-256">...</pin>
    </pin-set>
</domain-config>

여기서 핀 만료일 까먹으면 어느 날 갑자기 통신 다 끊김. 캘린더에 미리 알림 박아둠.

배운 것

  • 보안은 한 곳만 막으면 의미 없음. 평문 URL, 키스토어, 네트워크 정책 전부 같이 봐야 함
  • ProGuard는 "켜면 끝"이 아니라 "켜고 검증해야 끝". 첫 릴리즈 크래시는 거의 다 난독화 때문
  • NDK 도입은 빌드 시간 늘어나는 비용 있음. abiFilters로 필요한 아키텍처만 남기는 게 필수

다음

댓글 0

첫 댓글 달아줘.