Android System & AOSP Engineering/AOSP Framework & Custom Services

안드로이드 SSL/TLS 구조 분석: Conscrypt에서 BoringSSL 네이티브 핸드셰이크까지

임베디드 친구 2025. 4. 3. 09:53
반응형

스마트폰 앱을 통해 금융 거래를 하고, 비밀번호를 변경하며, 개인적인 메시지를 전송할 때 데이터가 중간에 해커에게 탈취되지 않는 이유는 무엇일까요? 비밀은 네트워크 패킷 전체를 강력한 암호화 알고리즘으로 감싸버리는 SSL/TLS(Secure Sockets Layer / Transport Layer Security) 프로토콜에 있습니다. 안드로이드 OS는 전 세계 수십억 명의 유저 데이터를 보호하기 위해 네트워크 레이어 전반에 걸쳐 철통같은 보안 라이브러리 체계를 구축해 두었습니다.

대다수 앱 개발자는 자바 레이어에서 SSLSocket이나 OkHttp 같은 HTTP 클라이언트를 호출하는 수준에서 네트워크 코딩을 끝마치지만, 안드로이드 프레임워크 밑단에서는 성능 최적화와 저전력 암호화 연산을 위해 자바와 네이티브 C/C++ 영역이 톱니바퀴처럼 유기적으로 맞물려 돌아갑니다. 이번 포스팅에서는 안드로이드 보안의 두 축인 ConscryptBoringSSL의 정체를 알아보고, AOSP(Android Open Source Project) 코드를 통해 가상 머신의 핸드셰이크 요청이 실제 네이티브 암호화 코어로 이어지는 실 구동 매커니즘을 자세히 뜯어보겠습니다.

Generated by Gemini AI.

📌 핵심 요약 3줄

  1. 안드로이드 SSL/TLS 시스템은 자바 표준 API를 구현한 Conscrypt와 구글이 자체 경량화한 C/C++ 암호화 코어인 BoringSSL의 이중 레이어로 결합되어 작동합니다.
  2. 최신 AOSP 체제에서 Conscrypt는 가상 머신 코어(libcore)를 벗어나 시스템 독립 업데이트가 가능한 Mainline APEX 모듈 구조로 완전히 분리 진화했습니다.
  3. 네트워크 보안 신뢰성의 마침표인 CA 인증서 검증은 /system/etc/security/cacerts에 봉인된 시스템 신뢰 저장소와 TrustManager 인터락을 통해 이루어집니다.

1. 안드로이드 보안 서브시스템 핵심 SSL 라이브러리 비교

안드로이드 네트워크 암호화 스택을 분담하여 지탱하는 핵심 라이브러리들의 역할과 소스 트리 내부의 주소지를 매칭했습니다.

라이브러리 명칭 주동 구동 레이어 AOSP 소스 트리 핵심 경로 시스템 보안 관점의 핵심 설계 철학 및 역할
Conscrypt Java / JNI 브릿지 external/conscrypt/ Java 표준 JCE(보안 프로바이더) 인프라 구현 및 네이티브 BoringSSL 엔진 중재
BoringSSL C/C++ 네이티브 external/boringssl/ OpenSSL에서 불필요한 레거시 코드를 쳐내고 크롬/안드로이드 최적화 표준으로 포크한 고속 암호화 코어
OpenSSL C/C++ 네이티브 external/openssl/ 안드로이드 6.0 이전의 표준 엔진. 현대 아키텍처에서는 보안 취약점 예방을 위해 호환성 도메인 외 완벽 탈피

2. AOSP 자바-네이티브 링킹 메커니즘 및 핸드셰이크 추적

안드로이드 앱이 특정 보안 서버에 커넥션을 맺고 암호화 통로를 개설하는 과정은 자바 프레임워크의 호출이 JNI 장벽을 깨고 C++ 영역으로 침투하는 연속적인 다운스트림 시퀀스입니다.

2.1 Java 레이어: ConscryptEngine의 핸드셰이크 트리거

앱이 sslSocket.startHandshake()를 선언하면, 안드로이드 고유의 가상 머신 보안 프로바이더인 Conscrypt가 인터셉트하여 상태 머신을 기동합니다.

Java
 
// external/conscrypt/common/src/main/java/org/conscrypt/ConscryptEngine.java (원리 요약)
public final class ConscryptEngine extends SSLEngine {
    private final Object handshakeLock = new Object();
    
    @Override
    public void beginHandshake() throws SSLException {
        synchronized (handshakeLock) {
            if (handshakeState != HandshakeState.NOT_STARTED) {
                throw new IllegalStateException("이미 핸드셰이크가 진행 중인 소켓입니다.");
            }
            handshakeState = HandshakeState.STARTED;
            
            // 실제 JNI 하부 바인딩 엔진을 통해 C/C++ 네이티브 영역으로 진입합니다.
            engine.beginHandshake();
        }
    }
}

2.2 Native 레이어: BoringSSL의 실제 핸드셰이크 바인딩

자바 영역의 바인딩 신호는 JNI 링크를 타고 네이티브 BoringSSL 저장소 내부의 C 언어 코어 함수인 SSL_do_handshake를 깨우게 됩니다. 여기서 하드웨어 가속(AES-GCM 암호화 등) 지시어가 함께 로드됩니다.

C
 
// external/boringssl/src/ssl/ssl_lib.cc
int SSL_do_handshake(SSL *ssl) {
    // 하부 드라이버 및 네트워크 소켓 상태가 정상인지 유효성 검증을 수행합니다.
    if (ssl->handshake_func == NULL) {
        OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);
        return -1;
    }
    
    // 벤더사 AP 아키텍처 가속 파이프라인 함수 포인터를 타고 실제 TLS 패킷 교환을 집행합니다.
    return ssl->handshake_func(ssl);
}

3. 인증서 검증(Certificate Validation) 및 신뢰 저장소 구조

네트워크 암호화 통로가 아무리 단단해도 연결된 대상 서버가 피싱 사이트라면 아무런 의미가 없습니다. 안드로이드 시스템은 공개키 기반구조(PKI) 신뢰성을 확보하기 위해 엄격한 2단계 인증서 검증 구조를 유지합니다.

3.1 CA 인증서 저장소 레이아웃

안드로이드 OS가 전 세계 오퍼레이팅 시스템 기준 완벽히 신뢰한다고 공인한 루트 CA 인증서들은 파일 시스템 내부 읽기 전용 영역에 엄격한 해시 파일명 규칙으로 관리됩니다.

  • 시스템 저장소 경로: /system/etc/security/cacerts/ (오직 구글 OS 빌드 시점에만 탑재 및 수정 가능)
  • 유저 저장소 경로: /data/misc/user/0/cacerts-added/ (사용자가 수동 인증서를 신뢰 등록할 때 사상되는 공간)

3.2 Java TrustManager 인프라의 연동

네이티브 암호화 스택이 서버로부터 인증서 체인(Certificate Chain)을 전송받으면, 자바의 TrustManagerFactory 인프라가 시스템 디렉터리의 루트 인증서 크레덴셜을 로드하여 체인의 위변조 여부를 해시 대조 방식으로 최종 판별합니다.


💡 안드로이드 네트워크 보안 개발을 위한 실전 팁

  1. Network Security Config를 통한 안전한 클리어텍스트(Cleartext) 통제: 안드로이드 9(Pie) 버전부터는 암호화되지 않은 일반 http:// 통신이 시스템 기본 방침으로 원천 차단됩니다. 만약 사내 테스트 서버나 임베디드 장비 제어를 위해 특정 도메인만 예외적으로 HTTP 통신을 허용해야 한다면, 자바 코드를 더럽히지 말고 res/xml/network_security_config.xml 설정을 도입해 보세요. 아래와 같이 명시적인 도메인 화이트리스트 아키텍처를 구성하면 다른 상용 네트워크 트래픽의 보안성을 완벽하게 지키면서 특정 테스트 환경만 안전하게 개방할 수 있습니다.
XML
 
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <!-- 상용 트래픽은 강력한 TLS 통신을 강제 집행합니다 -->
    <base-config cleartextTrafficPermitted="false" />
    <!-- 예외 도메인 세션 구역 정의 -->
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">test.internal.dev</domain>
    </domain-config>
</network-security-config>
  1. Conscrypt 프로바이더의 명시적 선행 로드를 통한 네트워크 성능 최적화: 안드로이드의 암호화 성능을 극대화하려면 상용 통신 모듈(OkHttp 등)이 가동되기 전에 시스템 보안 프로바이더 런타임의 최상단 스택에 Conscrypt를 주입해 두는 것이 좋습니다. 앱의 Application 클래스 초기화 시점에 Security.insertProviderAt(Conscrypt.newProvider(), 1); 규격을 선언해 두면, 시스템 표준 자바 암호화 엔진이 최적화된 BoringSSL 네이티브 가속 코어를 가장 먼저 참조하게 되어 SSL 헨드셰이크 및 데이터 암/복호화 속도가 비약적으로 향상됩니다.

⚠️ 흔히 하는 실수

  1. 테스트 편의를 위한 X509TrustManager 무조건 승인(checkServerTrusted 공백) 방치: 개발 도중 사내 사설 인증서 환경에서 SSLHandshakeException 에러가 터지면, 많은 초급 개발자들이 인터넷 블로그의 잘못된 예제를 복사하여 인증서 검증을 아예 패스해 버리는 가짜 TrustManager 객체를 생성하곤 합니다. checkServerTrusted() 메서드 내부를 아무런 예외 처리 없이 공백으로 비워두는 실수는 하이브리드 앱 전체를 중간자 공격(MITM, Man-in-the-Middle) 위험에 완전히 발가벗겨 노출시키는 최악의 보안 취약점입니다. 해커가 가짜 와이파이 공유기 등을 통해 트래픽을 가로채면 금융 데이터와 세션 토큰이 그대로 털리게 되므로, 사설 인증서가 필요하다면 앞서 설명한 Network Security Config에 <pin-set> 또는 사설 <certificates> 자원을 바인딩하여 안전하게 신뢰를 확장해야 합니다.
  2. TLS 핸드셰이크 타임아웃 미설정으로 인한 백그라운드 스레드 영구 블로킹: 일반 TCP 소켓 통신과 달리 TLS 통신은 소켓 연결 이후 암호화 키를 교환하는 '핸드셰이크' 단계에서 추가적인 패킷 왕복이 일어납니다. 만약 네트워크 음영 지역이나 해외 열악한 망 환경에서 sslSocket.connect()의 타임아웃 옵션만 믿고 핸드셰이크 전용 타임아웃 설정을 누락하면, 네이티브 BoringSSL 엔진이 상대방 서버의 키 응답을 무한히 기다리며 백그라운드 워커 스레드 핸들러를 영구히 붙잡고 놔주지 않는 병목 현상이 발생합니다. 결국 전반적인 앱 네트워킹 리소스가 고갈되므로 통신 프레임워크 구축 시 헨드셰이크 타임아웃 규격을 명시적으로 제어해야 합니다.

4. 결론

안드로이드의 SSL/TLS 서브시스템은 무겁고 파편화되어 있던 오픈소스 OpenSSL의 그림자를 지워내고, 모바일 환경에 극도로 군더더기를 쳐낸 BoringSSL 엔진과 플러그인 형태로 진화한 Conscrypt 아키텍처의 결합을 통해 완성된 모던 웹 보안의 결정체입니다. 자바의 가상 머신 인터페이스 이면에 숨겨진 JNI 핸드셰이크 라우팅 메커니즘과 시스템 루트 CA 저장소의 엄격한 유효성 검증 체계를 정확히 명시하고 네티워크 아키텍처를 설계할 때, 우리는 수많은 해킹 위협 속에서도 유저 데이터를 안전하게 수호하는 무결점의 엔터프라이즈 보안 시스템을 구축할 수 있습니다.

반응형