안드로이드 시스템·Compose 용어 사전
보안network security config · 인증서 고정 · certificate pinning

네트워크 보안 구성

평문 트래픽 차단과 인증서 고정을 XML로 선언하는 설정.

평문 차단과 인증서 신뢰 범위를 XML로 선언하는 설정.

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config>
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2027-01-01">
            <pin digest="SHA-256">AAAA...=</pin>
            <pin digest="SHA-256">BBBB...=</pin>   <!-- 백업 핀 필수 -->
        </pin-set>
    </domain-config>
</network-security-config>

Android 9(P) 부터 평문 HTTP 는 기본 차단이다

  • http:// 를 부르면 CleartextNotPermittedException

인증서 고정이 막는 것

일반 HTTPS 는 '신뢰할 수 있는 CA 가 서명했으면' 통과한다

문제

  • 사용자가 기기에 CA 를 설치할 수 있다 (Charles · mitmproxy)
  • 기업 MDM 이 CA 를 심을 수 있다
  • CA 자체가 뚫릴 수 있다

핀을 걸면 '내가 아는 그 인증서(의 공개키 해시)' 여야만 통과한다

// OkHttp 로도 가능하다
val pinner = CertificatePinner.Builder()
    .add("api.example.com", "sha256/AAAA...=")
    .add("api.example.com", "sha256/BBBB...=")
    .build()
OkHttpClient.Builder().certificatePinner(pinner).build()

고정의 위험 — 앱이 통째로 죽는다

서버 인증서를 갱신했는데 앱의 핀을 안 바꿨다

  • 설치된 모든 앱이 네트워크에 연결하지 못한다
  • 앱 업데이트를 받아야 고쳐지는데, 업데이트도 네트워크가 필요할 수 있다

반드시

  • 백업 핀을 함께 넣는다 (차기 인증서의 핀)
  • expiration 을 설정해 만료 후에는 고정을 해제한다
  • 인증서 갱신 절차에 '앱 핀 교체' 를 넣는다

금융·의료가 아니면 고정을 안 하는 편이 나을 수 있다 — 운영 비용이 실재한다

디버그 빌드에서만 프록시 허용

<debug-overrides>
    <trust-anchors>
        <certificates src="user" />    <!-- 사용자 설치 CA 를 디버그에서만 신뢰 -->
    </trust-anchors>
</debug-overrides>
  • debug-overrides 는 android:debuggable="true" 일 때만 적용된다
  • 릴리스에 새어 나갈 수 없다

면접 함정

  • "핀을 하나만 넣어도 된다" → 인증서 갱신 순간 앱이 전부 죽는다.
  • "인증서 고정이면 리버싱도 막는다" → 앱을 뜯어 핀 검사를 제거할 수 있다. 중간자 공격을 어렵게 할 뿐이다.

핀을 어떻게 뽑나

# 서버 인증서의 공개키 해시
openssl s_client -servername api.example.com -connect api.example.com:443 \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary | base64

OkHttp 는 더 간단한 방법도 준다 — 일부러 틀린 핀으로 요청하면 예외 메시지에 실제 핀들이 찍힌다 (개발 중에만)

어느 인증서를 고정하나

  • 리프(서버) 인증서 — 가장 엄격. 갱신 주기가 짧아 위험하다 (Let's Encrypt 는 90일)

  • 중간 CA — 갱신 주기가 길다. 실무에서 가장 흔한 선택

  • 루트 CA — 가장 느슨하다. 그 CA 로 발급된 아무 인증서나 통과한다

  • 중간 CA 를 고정하고 백업으로 다른 중간 CA 를 넣는 것이 균형점이다

평문이 필요한 예외

<domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="false">localhost</domain>
    <domain includeSubdomains="false">10.0.2.2</domain>   <!-- 에뮬레이터의 호스트 -->
</domain-config>

전체를 여는 것(base-config cleartextTrafficPermitted="true") 이 아니라 필요한 도메인만 연다. 그리고 debug 소스셋에만 둔다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 권한·보안 — 런타임 권한·저장소·암호화·네트워크