사이드프로젝트 slecs

APK 디컴파일 대비 안드로이드 앱 보안 라인 정비

목차

v1.0.0 릴리즈 — 보안 라인 정비

릴리즈 직전에 가장 신경 쓰인 건 기능이 아니라 APK 디컴파일 내성이었음. 베타 기간 동안 외부에서 받은 빌드를 jadx로 까봤는데, 문자열 상수에 박혀있던 키들이 그대로 노출되더라. 그날 바로 작업 큐에 "키 분리" 박아놓고 v1.0.0 게이팅으로 잡음.

NDK로 키를 옮긴 이유

처음엔 BuildConfig 상수 + 빌드타임 주입으로 끝내려 했는데, dex 레벨에서는 결국 평문 문자열로 떨어짐. 그래서 네이티브 라이브러리(.so) 안쪽으로 옮김. 디컴파일 도구가 자바 영역만 잘 까고 네이티브는 IDA 같은 도구가 따로 필요해서 진입장벽이 한 단계 더 생김. 완벽한 보안은 아니지만 공격자 비용을 올리는 것 자체가 목적이었음.

  • JNI 함수에서 키를 런타임에 조립해서 반환
  • 반환된 문자열은 즉시 사용 후 참조 끊기
  • 빌드 variant 별로 다른 키가 들어가도록 cmake 인자 분기

ProGuard / R8 규칙 정리

R8 켜는 순간 리플렉션 쓰는 직렬화 라이브러리가 죽어서, keep 규칙을 항목별로 다시 짰음. 무지성 -keep class ** 같은 거 박아두면 난독화가 거의 무의미해져서 필요한 클래스만 남기는 방향으로 갈았음.

영역 규칙 방향 이유
모델 DTO keep class members 직렬화 필드 깨짐 방지
JNI 바인딩 keep names native 메서드 시그니처 매칭
로그 호출 assumenosideeffects 릴리즈 빌드에서 통째 제거
그 외 기본 난독화 클래스/메서드명 단축

릴리즈 빌드 mapping.txt 따로 보관해서 크래시 리포트 디스맵핑 가능하게 챙겨둠.

네트워크 보안 설정

평문 트래픽 차단하고, 도메인별 인증서 핀닝 적용:

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

핀은 운영 + 백업 2개 필수. 한 개만 박으면 인증서 갱신될 때 앱 자체가 통신 불가 상태로 빠짐. 이거 베타 때 한 번 당해봤음.

회고 포인트

  • 순수 자바/코틀린 영역에 비밀을 두지 않는다가 v1.0.0의 가장 큰 학습. 대신 NDK도 만능 아니라는 점은 잊지 말 것.
  • ProGuard 규칙은 처음 짤 때 시간 들여서 항목 단위로 잡는 게 결국 이득. 통짜 keep은 미래의 나에게 빚 떠넘기는 짓.
  • 인증서 핀닝은 갱신 운영 시나리오까지 포함해서 설계해야지, 박고 끝낼 수 있는 게 아님.

다음 마일스톤은 루팅/디버거 탐지랑 무결성 체크 쪽으로 확장할 예정.

댓글 0

첫 댓글 달아줘.