rein's world

TLS 연결 디버깅: Forward Secrecy 재확인하기

앞선 글과 같이 쓰려고 했던 내용 정리. 글이 길어지고 쓸 시간도 모자라서 쪼개서 옮긴다.

SSL/TLS 연결로 메시지를 주고 받는 서버/클라이언트를 디버깅하는 작업이라 우선 wireshark 를 써서 덤프를 떴다. 아래 내용은 해당 디버깅 세션의 덤프는 아니고, 임의로 비슷한 TLS 설정을 쓰고,  거기서 HTTP GET을 보내고 응답을 받는 과정을 실행해서 해당 덤프를 얻었다. 이 메시지 덤프를 분석해서 “왜 이 메시지를 wireshark 에서 디코딩 할 수 없는지” 그리고 “TLS의 이런 기능 덕분에 우리의 사생활이 더 잘 키져지는지” 를 정리해보겠다. TLS 메시지를 wireshark 에서 분석하려면 서버의 비밀키 (private key) 를 등록해야한다. Wireshark UI 상에서 추가하거나 (설정의 프로토콜 ssl 이하) ~/.config/wireshark/ssl_keys 파일에 디코딩할 서버 주소 (IP:port) 와 키에 해당하는 .pem 파일 위치를 지정하면 끝.

TLS 패킷 덤프 열어보기

이 설정으로 wireshark 에서 열어보면 아래처럼 분석이 불가능한 내용이 나온다.

비밀키를 알고 있는데 왜 분석을 할 수 없을까? 이는 TLS의 특정 키 교환 방식 때문이다. 실제로 어떤 메시지를 주고 받았는지 확인해보면 아래와 같다.

서버가 보낸 TLS 설정: Ephemeral ECDH 키교환 / RSA 로 키 교환 서명 / AES-256-GCM 으로 블럭 암호화 (AEAD) / SHA2 – 384 메시지 체크섬

서버와 클라이언트 사이에 사용할 암호화 키(=세션 키)를 서버가 바로 정해서 보내는게 아니라 타원곡선 (elliptic curve; EC) 을 사용한 디피-헬만 (DH) 키 교환 알고리즘을 이용하고, 이를 완전히 랜덤하고 이 세션에서만 쓰고 버릴 데이터를 이용하는 (ephemeral) 방식을 썼다. 즉, 서버의 비밀키는 세션 키와는 관련성이 없다. 서버의 비밀키는 서버가 ECDH 를 위한 메시지를 보낼 때, 해당 메시지를 서버가 보냈다고 전자 서명하는데 썼다. 이 메시지 내용은 다음과 같다.

서버가 보낸 ECDH 키 교환 과정에 해당하는 메시지

서버 인증서가 서명한 공개키가 RSA 알고리즘 기반이라, 서버가 보낸 메시지의 서명부분(Signature Algorithm 및 그 데이터)도 이 RSA 알고리즘을 써서 만든걸 확인할 수 있다. 디코딩할 수 없는 이유를 정리하면 다음과 같다.

  • 서버와 클라이언트 사이에 공유할 세션 키 (=둘만 알면되는 랜덤한 값) 를 생성하는데 서버 인증서의 비밀키 값을 쓰지 않는다.
  • 서버 인증서에 해당하는 비밀키는 이 공유 세션 키의 서버 부분을 보낼 때, 이 데이터가 “서버가 보냈음을 증명” 하는 용도로 전자서명할 때만 쓴다.
  • Server Key Exchange 에 해당하는 메시지는 서버 비밀키를 이용해서 이를 해석(=디코딩)할 수 없다. 비밀 키를 모르는 상태와 이 문제를 푸는 난이도가 동일하다. 그래서 서버에서 생성한 랜덤 값을 추측할 수 없다.
  • 클라이언트는 키 교환 메시지(Client Key Exchange) 를 보내고, 중간에 이걸 해석할 수 없다. 이 이후부터 서버와 클라이언트는 공유 비밀 정보를 만들고 이를 써서 세션 키를 초기화한다.

복호화 가능한 TLS 설정 시험하기

이런 이유로 오고 가는 메시지를 제대로 디코딩 할 수 없었던 것. 그럼 반대로(??) 서버의 비밀 키를 알고 있는 경우엔 디코딩 가능하게 하려면 어떻게 해야할까? 서버의 TLS 알고리즘에서 키를 생성할 때 임시 키를 안전하게 생성하는 방식을 쓰지 않도록 강제하면 된다. 예를 들어서 원래 사용하던 ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384;대신에, ssl_ciphers AES128-GCM-SHA256; 같은 걸로 설정하란 소리. 이렇게 바꾸면 아래처럼 암호화 푼 내용을 볼 수 있다.

위에서 말한 것처럼 TLS 설정이 RSA 기반의 세션 키 생성으로 바꿨다. 그리고 이 약한 알고리즘을 쓰는 경우에는 오고가는 HTTP 메시지를 원래 상태 그대로 확인할 수 있다. 알고리즘을 바꾸면 어떻게 서버 키로 암호화를 풀 수 있는지는, 키 교환 과정을 살펴보면 이해할 수 있다.

RSA 로 암호화한 PreMaster Secret 을 클라이언트에서 서버로 전송한다. 이 정보를 가지고 세션 키를 생성하게 된다. 그런데 이 RSA 키는 서버의 비밀 키에 해당한다. 즉, 키 교환 시점 이전부터 메시지를 덤프해두고 있다면 나중에 다시 이걸 복호화해서 볼 수 있다. 인터넷 상에서 사생활에 민감한 이들에겐 다행인 소식. 지난 8월 (2018년 8월) 에 표준화 작업을 마무리한 TLS 1.3 버전부터는 이런 방식을 쓸 수 없다. 앞서 디코딩할 수 없었던 ECDH처럼 “나중에 비밀키를 탈취해도 해석할 수 없는” 방법만 사용하게 바뀌었다. (이렇게 미래에 비밀키를 탈취당해도 문제 없는 방식이라 forward secrecy 라고 부른다) 다만 이런 FS 방식을 지원하는 경우에도, a. 서버 비밀키를 탈취하고 b. 서버 클라이언트 연결 사이에 끼어들어가서 능동적으로 중간자 공격 (man-in-the-middle attack) 을 하는 경우에는 복호화가 가능하다는 점은 참고하시길.

디버깅 방법 요약