안드로이드 프레임워크·시스템 학습 노트 목차

권한·보안 — 런타임 권한·저장소·암호화·네트워크

왜 권한이라는 게 있는가

휴대폰에는 사람의 가장 민감한 것이 다 들어 있다 — 위치, 연락처, 카메라, 마이크, 사진, 메시지. 앱이 이걸 마음대로 가져갈 수 있다면 아무도 앱을 믿고 설치하지 못한다. 그래서 안드로이드는 기기 기능과 사용자 데이터를 권한(permission)이라는 문으로 잠그고, 그 문을 여는 열쇠를 사용자에게 준다 — 앱이 카메라를 쓰려면 사용자가 직접 "허용"을 눌러야 한다. 권한은 앱과 사용자 사이의 신뢰 계약이고, 보안은 그 계약을 지키는 기술적 뒷받침이다. 이 장은 권한 요청·저장소 접근·민감 데이터 암호화·네트워크 보안을 다룬다. (출처: Android — Permissions / Security best practices.)

권한 — normal vs dangerous

권한은 위험도에 따라 둘로 나뉜다. 일반(normal) 권한(인터넷 접근 등)은 위험이 낮아매니페스트에 선언만 하면 설치 시 자동 부여된다(사용자에게 묻지 않음). 반면 위험(dangerous) 권한(카메라·위치·연락처·마이크)은 사용자 프라이버시에 직결되므로 — 앱이 실제로 쓰는 순간 런타임에 사용자에게 명시적으로 물어야 한다.

런타임 권한 요청은 세 갈래 분기로 처리한다 — 이 흐름을 정확히 짜는 게 핵심이다.

// 권한 결과를 받을 launcher 등록 (Activity Result API)
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
    if (granted) openCamera() else showDeniedUi()      // 거부돼도 크래시 없이 기능 저하
}

when {
    // ① 이미 허용 — 바로 사용
    checkSelfPermission(CAMERA) == PERMISSION_GRANTED -> openCamera()
    // ② 전에 거부한 적 있음 — "왜 필요한지" 먼저 설명하고 다시 요청
    shouldShowRequestPermissionRationale(CAMERA) -> showRationale { launcher.launch(CAMERA) }
    // ③ 처음 요청 — 시스템 다이얼로그
    else -> launcher.launch(Manifest.permission.CAMERA)
}
다이어그램 로딩 중…

shouldShowRequestPermissionRationale가 *true면 "전에 한 번 거부했다"*는 신호다 — 이때 왜 이 권한이 필요한지 사용자에게 설명한 뒤 다시 요청해야 동의를 얻기 쉽다. 사용자가 "다시 묻지 않음"으로 영구 거부하면 시스템 다이얼로그가 더는 안 뜨므로 설정 화면으로 안내한다.

관통하는 원칙은 최소 권한(least privilege)꼭 필요한 권한만, 필요한 순간에 요청한다. 그리고 거부를 우아하게 다룬다(권한이 없으면 그 기능만 빠지고 앱은 정상 — 크래시 금지). 더 나아가 권한이 아예 필요 없는 대안을 우선한다 — 예컨대 사진을 고를 때 저장소 권한 대신 권한 없는 Photo Picker를 쓰는 식이다.

저장소 — Scoped Storage

예전 안드로이드는 앱이 기기의 파일 시스템을 거의 자유롭게 돌아다녔다 — 그래서 악성 앱이 다른 앱의 데이터를 훔치거나, 앱을 지워도 쓰레기 파일이 남는 문제가 컸다. 현대 안드로이드는 **범위 지정 저장소(scoped storage)**로 이를 막는다 — 각 앱은 자기 전용 디렉터리만 자유롭게 쓰고, 그 밖은 통제된 통로로만 접근한다. 공유 미디어MediaStore나 권한 없는 Photo Picker로, 문서는 *Storage Access Framework(SAF)*로 — 결과적으로 광범위한 저장소 권한이 거의 불필요해졌다. "내 앱 데이터는 내 전용 공간에, 공유할 것만 통제된 통로로"가 사고법이다.

민감 데이터 저장 — 왜 평문은 위험한가

로그인 토큰·개인정보를 평문 SharedPreferences나 파일에 두면 — 루팅된 기기·백업·물리적 탈취로 그대로 노출된다. 핵심 도구는 Android Keystore다 — 암호화 키를 하드웨어 보안 영역(TEE/StrongBox)에 보관해, 앱조차 키 원문을 못 보게 한다. 그래서 디스크를 통째로 훔쳐도 키가 없어 복호화 불가다.

  • 민감 값은 Keystore 기반 암호화로 저장한다(Jetpack Security의 EncryptedSharedPreferences 등 — 일부 API는 변천이 있으니 현재 권장 라이브러리를 확인).
  • 결제·로그인 같은 민감 작업은 **생체 인증(BiometricPrompt)**으로 한 겹 더 보호한다.

네트워크 보안

데이터는 저장될 때만이 아니라 오갈 때도 노출될 수 있다(공용 와이파이의 도청).

  • HTTPS 강제 — 현대 안드로이드는 평문 HTTP를 기본 차단(cleartext 비허용)한다. 도메인별 예외·정책은 Network Security Config로 선언한다.
  • 인증서 고정(certificate pinning)신뢰할 인증서를 앱에 박아 두어, 중간자가 가짜 인증서로 가로채는 MITM 공격을 막는다. 단 핀이 만료되면 앱이 서버에 못 붙으니 만료 관리가 중요하다.
  • 클라이언트 검증만 믿지 않는다 — 보안 검증의 최종 권위는 서버다(클라이언트 코드는 역공학으로 우회될 수 있다).

코드·비밀 보호

  • 비밀(secret)을 코드에 하드코딩 금지 — API 키·토큰을 코드에 박으면 APK를 역공학해 추출된다. 서버 측에 두거나 안전한 채널로 받는다.
  • R8 난독화([11])로 코드 분석을 어렵게 한다(완전한 보호는 아니지만 분석 비용을 올린다).
  • 컴포넌트 노출 최소화 — 매니페스트의 exported=false불필요한 외부 접근을 차단하고, ContentProvider·딥링크로 들어오는 입력은 반드시 검증한다(외부가 보내는 값은 신뢰하지 않는다).

정리 — 최소 권한·암호화·검증

보안의 핵심 — 위험 권한은 런타임에 최소·근거와 함께 요청(거부돼도 기능 저하지 크래시 금지), scoped storage로 저장소 접근 최소화(Photo Picker/SAF), 민감 데이터는 Keystore 기반 암호화·전송은 HTTPS(+ 필요 시 인증서 고정), 비밀 하드코딩 금지·exported=false·외부 입력 검증. 모든 결정의 기준은 *"꼭 필요한 만큼만, 안전하게"*이고, 최종 신뢰는 서버에 둔다.

한 줄 요약 — 권한=사용자와의 신뢰 계약. normal(자동) vs dangerous(런타임 요청·registerForActivityResult·shouldShowRationale 분기)·최소 권한·거부 우아하게. 저장소: scoped storage(전용 공간+Photo Picker/SAF). 민감 데이터: Keystore(하드웨어 키)·EncryptedSharedPreferences·BiometricPrompt. 네트워크: HTTPS 강제·Network Security Config·인증서 고정. 비밀 하드코딩 금지·exported=false·외부 입력 검증·최종 검증은 서버.

(출처: Android — Permissions / App security best practices / Data and file storage / Photo Picker / Security guidelines.)

코루틴 on Android — lifecycleScope·repeatOnLifecycle빌드 — Gradle·빌드 변형·R8/난독화·App Bundle