이 글은 계간 잡지 마이크로소프트웨어 394호에 기고된 글입니다.

TLS

우리가 개발하는 서비스 중에서 인터넷에 연결이 필요 없는 서비스는 거의 없다고 봐야한다. 이런 서비스는 우리가 만든 앱 혹은 웹을 통해서 데이터를 받아 서버에 저장한다. 오프라인 모드로 즐기는 게임마저도 장애 로그 또는 통계 데이터를 인터넷 연결로 받는 사례가 늘고있다. 다만 이 데이터에는 굉장히 민감한 정보가 포함돼 있다는 것이 요점이다. 카드 번호나 주소는 기본이고, 심지어 우리 집 거실에 있는 공유기의 맥어드레스까지도 데이터화 돼 전송된다. 그래서 중간에 데이터가 조작되거나 변조되지 않도록 암호화해 데이터를 보내야한다.

우리는 이런 민감한 정보를 보낼 때 HTTP보다는 HTTPS(‘Secure’를 의미하는 ‘S’)를 사용하라고 배웠다. HTTPS의 안전성은 TLS라는 프로토콜을 통해 보장된다. TLS는 과거 우리가 SSL이라 부르던 프로토콜에서 시작했다. SSL은 1990년대에 브라우저를 개발했던 넷스케이프(Netscape)에서 보안성이 높은 HTTP 통신을 지원하기 위해 만든 프로토콜이다. 넷스케이프가 SSL을 IETF(Internet Engineering Task Force, 국제 인터넷 표준화 기구)에게 양도하면서 바뀐 이름이 TLS 1.0이다. (TLS 1.0은 단순히 이름만 바뀐 것이기 때문에, SSL 3.0과 동일한 프로토콜로 본다.)

우리가 사용하는 웹브라우저 대부분은 HTTP/2를 사용하기 위해 TLS 사용을 강제하고 있다. 특히 최고의 브라우저 점유율을 갖고 있는 구글 크롬(Google Chrome) 브라우저의 최신 버전에서는 HTTP로 접속하면 ‘안전하지 않은 사이트’라는 경고를 띄운다.

Blue House without TLS

[그림1] 안전하지 않은 청와대

과거만 해도 TLS/SSL를 사용하기 위해서는 인증서를 구매 해야 했고, 이 구매 비용이 (생각보다) 부담스럽기 때문에 TLS를 사용하지 않는다는 사람들도 있었다. 하지만 이제는 인증서 마저도 렛츠인크립트(Let’s Encrypt) 같은 서비스를 이용하면 무료로 발급받을 수 있다. 이런 추세와 시도로 많은 사이트가 TLS를 사용해 조금 더 안전한 인터넷 세상을 만들 수 있게 변하고 있다.

TLS 1.2의 문제점

TLS의 전신인 SSL은 1990년대에 등장한 프로토콜이다. 초기 SSL을 디자인할 당시에는 설계상 허점으로 인해 구현체가 실제 구현 결과와 다른 부분(Heuristic)이 있었다. 또한 보안 프로토콜에서 발생하는 문제가 어떤 방식인지 자료나 학습이 부족했다. 허점이 발견된(Heuristric) 부분이나 문제가 되는 부분은 구현체가 만들어지는 과정에서 많이 고쳐지면서 현재의 TLS 1.2가 탄생했다. 하지만 TLS의 설계적인 허점과는 별개로 TLS와 관련된 직간접적인 보안 이슈가 많이 발생했다.

가장 먼저 떠오르는 보안 관련 이슈로 하트블리드(Heartbleed, CVE-2014-0160)가 있다. 하트블리드는 많은 언어, 프레임워크, 서버 프로그램에서 TLS연결을 위해 사용하던 오픈소스 라이브러리인 OpenSSL에서 발생한 버퍼 오버플로우(BOF) 버그다. 엄밀하게는 TLS 구현체 자체에서 해결해야하는 문제점이므로 TLS와는 상관 없다고 할 수 있다. (OpenSSL은 ‘BERserk’, ‘goto fail;’ 등 구현체 수준의 다른 문제도 제기됐다.)

하지만 TLS 프로토콜 자체에도 문제점이 있다. 이론적인 레벨도 있지만 ‘LogJam(CVE-2015-4000)’, ‘FREAK(CVE-2015-0204, CVE-2015-1637, CVE-2015-1067), ‘SWEET32(CVE-2016-2183)’ 같은 위협적인 문제도 있고, 실제 적용할 수 있는 ‘POODLE(CVE-2014-8730)’과 ‘ROBOT’과 같은 문제점들도 있다.

TLS 1.2는 느리다

TLS의 가장 최신 버전이었던 ‘1.2’는 2008년에 나왔다. 2008년 당시 네이버를 떠올려보자. 그 시절 네이버는 로그인 서비스 같은 보안이 중요한 부분을 제외하고는 평문(Plaintext)으로 서버와 클라이언트가 통신했다. 하지만 최근 추세는 조금 다르다. 지금은 모든 페이지를 TLS로 서비스하고 있다.

당연히 이런 노력은 긍정적인 변화로 봐야 한다. 중간에서 통신을 도청당해도 안전하고, 인젝션(Injection)과 같은 공격도 걱정할 필요가 없다. 다만 단점은 새로운 연결을 맺기까지 조금 느려진다. TLS는 처음 커넥션을 핸드셰이크(Handshake)라 부르는 과정을 통해서 암호화 방식이나 암호화 키를 교환한다. 이를 위해 클라이언트와 서버 간 라운드 트립(Round Trip)이 2회 정도 추가된다.

      Client                                               Server

      ClientHello                  -------->
                                                      ServerHello
                                                     Certificate*
                                               ServerKeyExchange*
                                              CertificateRequest*
                                   <--------      ServerHelloDone
      Certificate*
      ClientKeyExchange
      CertificateVerify*
      [ChangeCipherSpec]
      Finished                     -------->
                                               [ChangeCipherSpec]
                                   <--------             Finished
      Application Data             <------->     Application Data

[그림2] TLS 1.2(RFC5246)의 전체 핸드셰이크 과정

엄청난 차이라고 느끼지 않을 수도 있다. 하지만 서버와 클라이언트가 서로 지구 반대편에 있는 최악의 상황을 떠올려 보자. 데이터가 빛의 속도로 움직인다고 가정하면, 빛이 지구 반대편에 도달하고 다시 돌아오는데 걸리는 시간은 약 133ms다. 이를 2번 시행하면 매번 커넥션을 열 때마다 약 270ms가 지연(Delay)된다. 당연히 이 지연 현상은 비용으로 추가된다.

TLS 1.3은 다른가?

TLS 1.3 표준은 IETF의 RFC 8446에서 맡고 있다. 더 빠르고 안전한 인터넷을 만들기 위해 4년간 논의하고 28회의 초안을 거쳤다. 문제점이 제기된 암호화 방식을 버리고, 핸드셰이크 과정을 최소화해 암호화 통신하는 방법을 추가했다. TLS 1.3의 큰 차이점은 다음과 같다.

TLS 1.3의 특징

  1. 핸드셰이크에 ‘0-RTT’ 모드 추가.
  2. 정적인 RSA와 디피-헬먼 암호화 스위트(Diffie-Hellman Cipher Suite) 제거.
  3. 핸드셰이크를 가능한 최대한 암호화.
  4. 타원 곡선 알고리즘을 기본으로 지원.
  5. 키 교환과 암호화 방식을 암호화 스위트(Cipher Suite)방식이 아니라, 개별적으로 결정.

TLS 1.2와 1.3의 성능과 안전성 차이는 뒤에서 자세히 알아보자.

더 이상 묶음 판매 안 합니다.

이전의 TLS에서는 핸드셰이크 과정에서 암호화 스위트(Cipher Suite)라는 묶음으로 인증과 키 교환 방법을 정했다. 핸드셰이크 과정에는 서로 합의해야 하는 알고리즘이 4가지가 있다. 대칭키 교환 방식, 인증서 서명 방식, 대칭키 알고리즘, HMAC 알고리즘이다. TLS에서는 서버와 클라이언트가 이 4가지 알고리즘을 묶음으로 합의하고, 합의된 알고리즘으로 애플리케이션 계층(Application Layer)의 내용을 암호화해서 전송한다.

보통 암호화 스위트는 [그림3]의 ‘TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384’와 같이 대문자로 표기한다. [그림3] 예시는 다음과 같은 내용이다.

  • 대칭키 교환 방식은 ECDHE(Elliptic Curve Diffie Hellman Ephemeral)을 사용.
  • 인증서 서명 방식은 RSA로 서명된 인증서로 상호 간의 신원을 확인.
  • 대칭키 암호화 알고리즘은 AES 256bit와 GCM을 사용.
  • HMAC 알고리즘으로는 SHA-384를 사용해서 메시지의 무결성을 확인.

Tranditional cipher suite [그림3] 암호화 스위트 표기 예시

암호화 스위트는 여러 종류가 있다. 그 중에서 클라이언트(Client)가 지원하면서 취약하지 않은 암호화 스위트의 리스트를 서버에게 알려주면, 서버는 그 중에 지원하는 암호화 스위트를 선택해 합의한다. 실제 구현에서는 <그림5> 예시 같은 문자열이 아니라, 각 암호화 스위트마다 IANA(Internet Assigned Numbers Authority, 인터넷 할당 번호 관리기관)에서 지정한 2바이트(Bytes)를 사용한다. 문제는 키 교환, HMAC 알고리즘, 암호화 알고리즘 등 각 부분이 다양해지기 시작했다. HMAC 함수만 해도 SHA-1, SHA-128, SHA-384, SHA-256, MD5와 같은 방법이 있고 암호화 방식도 AES, DES, RC4와 같은 방법이 있다. 조합마다 코드를 만들기에는 너무 다양하고, 이미 존재하는 구성요소라 해도 새로운 조합법이 나올 때마다 업데이트해야 하므로 구현체에도 부담이 된다.

New cipher suite [그림4] TLS 1.3의 암호화 스위트 합의 예시

TLS 1.3에서는 이런 문제를 해결하기 위해, 암호화 스위트의 각 부분을 나눠서 합의한다.

  • 암호화 알고리즘
  • 키 교환 방법
  • 인증서 서명 알고리즘

암호화 알고리즘, 키 교환 방법, 인증서 서명 알고리즘을 각각 합의함으로써 앞으로 등장할 수 있는 방식에 유연하게 대처할 수 있다. 게다가 핸드셰이크는 2번 발생하는 라운드 트립을 1번으로 줄일 수 있게 만들었다.

1-RTT Handshake

TLS 1.2는 커넥션을 하나 생성하면 라운드 트립을 2번 거쳐서 핸드셰이크해야 한다. TLS 1.3에서는 암호화 스위트 선택 방법을 최대한 단순화했다. 그리고 키 교환 방식으로 RSA를 배제해서 옵션 또한 최대한 줄였다. 그래서 클라이언트는 시작부터 키 교환 방식으로 DH를 사용한다고 가정할 수 있게 됐다. DH도 여러 가지 방식(X25519, P256 등)이 있지만, RSA나 그 외의 방식까지 고려해서 서버에게 여러 가지 키 교환 방식의 지원 여부를 물어봐야 했던 과거에 비해 훨씬 간단하게 추측할 수 있다.

RFC 문서에 나와 있는 TLS 1.3의 전체 핸드셰이크를 1.2와 비교하면 쉽게 이해할 수 있다. TLS 1.2에서는 커넥션이 맺어지면, 클라이언트는 ‘ClientHello’를 보내서 클라이언트가 지원하는 암호화 스위트 목록을 보낸다. 서버는 그중 하나 선택해서 ‘ServerHello’ 메시지를 보낸다. 만약 디피-헬먼 방식을 이용해 키를 교환한다면 서버의 공개키(Public Key)도 동시에 보낸다.

앞에서 이야기한 것처럼 TLS 1.3에서는 암호화 방식을 각각 합의한다. 그리고 키 교환 방식도 ECDHE 혹은 DH만을 지원한다. 따라서 클라이언트는 시작할 때부터 ‘ClientHello’를 보낼 때 지원하는 키 교환 방식에 해당하는 모든 방법을 TLS 핸드셰이크 확장기능(TLS Handshake extension)인 ‘key_share’와 함께 동시에 보낸다.

        Client                                               Server

        ClientHello
        + key_share             -------->
                                                  HelloRetryRequest
                                <--------               + key_share
        ClientHello
        + key_share             -------->
                                                        ServerHello
                                                        + key_share
                                              {EncryptedExtensions}
                                              {CertificateRequest*}
                                                     {Certificate*}
                                               {CertificateVerify*}
                                                         {Finished}
                                <--------       [Application Data*]
        {Certificate*}
        {CertificateVerify*}
        {Finished}              -------->
        [Application Data]      <------->        [Application Data]

[그림5] TLS 1.3 (RFC8446) 전체 핸드셰이크 과정

예를 들어, 크롬 브라우저에서 TLS 1.3을 사용한다면 ‘ECDHE X25519’ 키 교환 방식을 서버가 지원한다고 가정하고 보낸다.

0-RTT Resumption

HTTP/1.1만 지원하는 서버에 브라우저가 접속하면, 동시에 리소스를 받아오기 위해서 TCP 커넥션(TCP Connection)을 만든다. (크롬 브라우저의 경우 6개를 만든다.)만약 해당 서버가 TLS를 사용 중이라면, TCP 커넥션을 만들 때마다 TLS 핸드셰이크 과정을 거쳐야 해, 굉장히 많은 리소스를 소모해야 한다. 그래서 TLS 1.2에서는 처음 핸드셰이크 과정을 거칠 때 ‘Session id’를 받아둔 후, 다시 핸드셰이크할 때 ‘Session id’와 서로 교환했던 키를 이용해 핸드셰이크를 마무리한다. 하지만 ‘Session id’를 이용해 TLS 핸드셰이크를 다시 진행할 때는 라운드 트립이 1번 발생해야 한다. TLS 1.3에서는 이 부분을 개선해 라운드 트립이 발생하지 않도록 만들었다.

      Client                                                Server

      ClientHello                   -------->
                                                       ServerHello
                                                [ChangeCipherSpec]
                                    <--------             Finished
      [ChangeCipherSpec]
      Finished                      -------->
      Application Data              <------->     Application Data

TLS 1.2 (RFC5246) 약식 핸드셰이크

         Client                                               Server

         ClientHello
         + early_data
         + key_share*
         + psk_key_exchange_modes
         + pre_shared_key
         (Application Data*)     -------->
                                                         ServerHello
                                                    + pre_shared_key
                                                        + key_share*
                                               {EncryptedExtensions}
                                                       + early_data*
                                                          {Finished}
                                 <--------       [Application Data*]
         (EndOfEarlyData)
         {Finished}              -------->
         [Application Data]      <------->        [Application Data]

TLS 1.3(RFC8446) 0-RTT 핸드셰이크

[그림6] TLS 1.2와 TLS 1.3 비교

TLS 1.3의 핸드셰이크는 키 하나를 만들어서 공유한다. 이 키를 PSK(Pre-Shared Key)라 부른다. 이를 암호화된 통신으로 주고받으면, 다음 커넥션이 맺어질 때 진행하는 핸드셰이크에서는 클라이언트가 PSK와 애플리케이션 데이터(Application Data)를 포함 시켜서 보낸다. (애플리케이션 계층이 HTTP라면 HTTP로 요청한다.) TLS 1.3에서 0-RTT 재개(0-RTT Resumption) 방식으로 TLS 핸드셰이크 과정을 거친다면, 핸드셰이크를 위한 클라이언트-서버 간 통신 없이도 새로운 TCP 커넥션을 맺을 수 있다.

강력한 안전성 추구

RSA를 사용하면 클라이언트가 핸드셰이크 초반에 서버로 RSA 공개키를 요청해야 하고, 공개키를 받은 후 대칭키를 서로 공유하는 커넥션을 다시 한번 맺어야 한다. 즉, 앞에서 이야기한 방법처럼 라운드 트립 1번으로 핸드셰이크를 마칠 수 없게 된다. 그래서 TLS 1.3은 더는 RSA를 이용한 키 교환 방식을 지원하지 않는다.

RSA를 더는 지원하지 않는 이유에는 보안 취약성도 있다. RSA 키 교환 방식은 서버가 본인의 인증서를 CA(Certificate Authority)로부터 받는 공개키-비밀키 쌍을 가지고 키를 교환한다. 이는 현재 서버 어딘가에 RSA의 고정 된 비밀키가 저장돼 있다는 이야기다. 만약 누군가가 모든 통신 과정을 감청하고 있고, 이를 저장해뒀다가 서버의 치명적인 취약점을 발견해서 비밀키가 탈취된다면, 감청한 데이터를 해독할 수 있다. 현실성이 떨어져 보이지만, 국가 단위에서는 충분히 가능한 방법이다. 이런 문제점을 RSA가 PFS(perfect forward secrecy)가 아니라고 이야기한다.

TLS 1.3에서 사용하는 디피-헬먼(Diffie-Hellman)은 정적인 디피-헬먼은 지원하지 않고, 일시적인 디피-헬먼(Ephemeral Diffie-Hellman)만 지원한다. 이는 서버와 클라이언트가 키를 교환하기 위한 비밀키를 매번 바꿔서 사용한다는 의미다. 비밀키를 매번 바꾸기 때문에, 서버와 클라이언트가 감청당하더라도 서버가 나중에 해킹당했을 때 이전의 통신한 내용을 해독할 수 없다.

TLS 1.2에서 문제가 됐던 많은 키 교환 방식이나 암호화 방식을 1.3에서는 없애버렸다. 그 중에는 오래된 RC4, DES, 3DES 같은 것이 있고, MD5나 SHA-1과 같이 취약한 MAC 함수도 더 이상은 지원하지 않는다. 1.2에서는 취약한 암호화 스위트를 지원하지 않도록 설정해야 했다. 그렇지 않으면 중간자가(Man in the Middle) 클라이언트에서 취약한 암호화 스위트만 지원하는 것처럼 행동해 공격하는 경우가 있다. 하지만 1.3에서는 이미 취약하다고 알려진 암호화를 삭제함으로써 서버 관리자의 관리 이슈가 줄어들었다.

TLS 1.3 지원

TLS 1.3 Can I use [그림7] 브라우저별 TLS 1.3 지원 현황

크롬 브라우저는 63버전부터 TLS 1.3을 지원한다. 그 외에도 안드로이드용 크롬 브라우저와 파이어폭스(Firefox)에서도 TLS 1.3을 지원한다. 현재까지 TLS 1.3을 지원하지 않는 브라우저는 MS 엣지(MS Edge)와 인터넷 익스플로러(Internet Explorer), 오페라(Opera) 등이 있다. (MS 엣지는 개발 중이다.)

애플 사파리는 기본적으로 지원하지 않지만, macOS High Sierra의 Safari 11.1버전에서는 <코드1>의 명령어를 입력하면 TLS 1.3을 지원하도록 설정할 수 있다.

$ sudo defaults write /Library/Preferences/com.apple.networkd tcp_connect_enable_tls13 1

[코드1] Safari 11.1에서 TLS 1.3을 활성화 하는 명령어

서버 프로그램 중 엔진엑스(Nginx)는 1.13버전 부터 TLS 1.3을 지원하고 있으므로 ‘ssl_protocols’ 설정에 1.3만 추가해주면 된다. 아파치(Apache)에서는 TLS 1.3을 지원하는 OpenSSL이 이제 막 나온 단계라 아직 지원하지 않는다. 그리고 MS의 IIS도 아직 TLS 1.3을 지원하지 않는다.

그래서 지금 당장 써도 될까?

TLS같이 인터넷 연결 과정에서 중추가 되는 프로토콜은 많은 장비가 관여하고 있어서 함부로 버전을 올리기에는 부담이 클 수 있다. TLS 1.3에서는 이런 부분을 고려해 완벽하게 하위 호환될 수 있도록 제작했다. TLS 1.3은 드래프트(Draft) 과정을 거치면서 많은 테스트 과정을 거쳤고, 점유율이 가장 높은 크롬 브라우저가 이를 지원하고 있다. CDN/DNS 제공 업체인 클라우드플레어(Cloudflare) 외 페이스북(Facebook)과 인스타그램(Instagram)도 TLS 1.3을 지원하고 있다.

TLS 1.3은 이전 버전에서 가지고 있던 많은 레거시(Legacy)를 없애면서 더 안전하고 빠른 프로토콜이 됐다. TLS 1.2에서 노출된 취약점과 위협요소가 언제 우리를 위협할지 알 수 없다. TLS 1.3을 조금이라도 빨리 적용해서, 발생할 수 있는 보안사고를 미리 예방하는게 좋지 않을까?

[참고1] TLS 1.3 - CloudFlare London Tech Talk

References

  1. blog.cloudflare.com/rfc-8446-aka-tls-1-3
  2. kinsta.com/blog/tls-1-3
  3. tools.ietf.org/html/rfc8446#section-2.2
  4. tools.ietf.org/html/rfc5246#section-7.4.1.2