TLS 핑거프린팅과 JA4 해시 완벽 해설
TLS Client Hello 메시지가 기기 식별의 보고인 이유, 그리고 JA4 해시가 브라우저 업데이트에도 안정적으로 유지되는 핑거프린트를 제공하는 방법.
모든 HTTPS 연결은 TLS 핸드셰이크로 시작되며, 모든 TLS 핸드셰이크는 Client Hello 메시지로 시작된다. 이 메시지에는 연결하는 클라이언트에 관한 풍부한 정보 — cipher suite, extension, 지원하는 curve, signature algorithm — 가 담겨 있으며, 이는 브라우저·버전·운영체제에 따라 크게 달라진다. TLS 핑거프린팅은 이 정보를 포착하여 식별 신호로 활용한다.
Client Hello에는 무엇이 들어 있나?
브라우저가 HTTPS 서버에 연결할 때, 브라우저는 다음을 담은 Client Hello 메시지를 보낸다: 지원하는 TLS 버전, 사용할 의향이 있는 cipher suite 목록, 포함하는 TLS extension(SNI, ALPN, key share 등), 지원하는 타원곡선(elliptic curve), 수용하는 signature algorithm, 그리고 제공하는 compression 방식.
각 브라우저 계열은 저마다 뚜렷한 핑거프린트를 가진다. Chrome, Firefox, Safari는 모두 서로 다른 cipher suite 순서, 서로 다른 extension 집합, 서로 다른 curve 선호도를 보낸다. 같은 브라우저 계열 안에서도 cipher suite가 추가되거나 폐기됨에 따라 버전마다 약간씩 다른 Client Hello 메시지를 보낼 수 있다.
JA3에서 JA4로
JA3는 2017년 Salesforce가 도입한 최초의 TLS 핑거프린팅 해시다. TLS 버전, cipher suite, extension, 타원곡선, EC point format을 하나의 문자열로 이어 붙인 뒤 MD5 해시를 계산한다. 획기적이었지만 JA3에는 한계가 있다: 분석하기 어려운 불투명한 단일 해시를 만들어내며, 어느 한 필드의 사소한 변화만으로도 완전히 다른 해시가 생성된다.
2023년 FoxIO가 도입한 JA4는 여러 면에서 JA3를 개선한다. JA4는 세 가지 구성 요소로 이루어진 구조화된 핑거프린트를 만든다: 사람이 읽을 수 있는 접두사("t13d1715h2" — TLS 1.3, 17개 cipher suite, 15개 extension, HTTP/2 등), cipher suite의 순서 기반 해시, 그리고 extension의 순서 기반 해시. 이 구조 덕분에 JA4 핑거프린트는 한눈에 분석 가능하면서도 식별에 필요한 정밀도를 유지한다.
TLS 핑거프린트가 device intelligence에 중요한 이유
TLS 핑거프린트는 JavaScript가 실행되기 전에 수집되기 때문에 가치가 있다. user agent를 위조하고, canvas 렌더링을 가짜로 꾸미고, navigator 속성을 패치한 봇이라 하더라도, 실제로 사용하는 TLS 라이브러리에서 나온 진짜 Client Hello 메시지는 여전히 그대로 보낸다. Client Hello가 "Go의 crypto/tls 라이브러리"라고 말하는데 user agent는 "Chrome 124"라고 말한다면, 무언가가 위조되고 있음을 알 수 있다.
이 교차 검증은 봇 탐지에 매우 강력하다. 대부분의 자동화 프레임워크 — Selenium, Puppeteer, Playwright — 는 브라우저의 네이티브 TLS 스택을 사용하므로, 그 TLS 핑거프린트는 자신이 제어하는 브라우저와 일치한다. 그러나 커스텀 HTTP 클라이언트, Go 기반 스크레이퍼, requests 라이브러리를 쓰는 Python 스크립트는 모두 뚜렷한 TLS 핑거프린트를 가지며, 이는 즉시 그것들이 브라우저가 아닌 클라이언트임을 식별해 준다.
TLS 핑거프린트의 안정성
TLS 핑거프린팅의 한 가지 우려는 브라우저 업데이트 전반에 걸친 안정성이다. Chrome이 cipher suite를 추가하거나 제거하면 TLS 핑거프린트가 바뀐다. 실제로 이런 일은 예상보다 드물게 일어난다. Chrome의 cipher suite 목록은 비교적 안정적이며 — 주요 변경은 모든 버전마다가 아니라 1년에 한두 번 일어난다.
JA4의 구조화된 형식이 여기서 도움이 된다. 사람이 읽을 수 있는 접두사는 마이너 버전 변경 전반에 걸쳐 안정적으로 유지되므로(cipher suite와 extension의 개수는 자주 바뀌지 않는다), 상세 해시가 바뀌더라도 접두사가 연속성을 제공한다. 우리의 다단계(multi-tier) 식별 시스템에서 TLS 핑거프린트 데이터는 Tier 2에 놓인다 — 식별에 기여할 만큼 충분히 안정적이면서도, 예상되는 드리프트를 처리하기 위해 세션 간(cross-session) 매칭을 거쳐 처리된다.
서버 측 수집
JavaScript 실행이 필요한 클라이언트 측 신호와 달리, TLS 핑거프린트는 전적으로 서버 측에서 수집된다. 우리의 엣지 서버는 연결이 수립되기 전에 원시 TLS 핸드셰이크를 검사하여 Client Hello를 추출한다. 이는 JavaScript가 차단되어 있을 때, 브라우저에 프라이버시 확장 프로그램이 설치되어 있을 때, 또는 클라이언트가 아예 브라우저가 아닐 때조차 TLS 핑거프린팅이 작동함을 의미한다.
이러한 서버 측 특성은 또한 TLS 핑거프린트를 스푸핑에 강하게 만든다. 특정 브라우저를 흉내 내는 커스텀 TLS Client Hello를 만드는 것이 이론적으로 가능하긴 하지만, 그렇게 하려면 낮은 수준에서 TLS를 구현해야 한다 — user agent 문자열을 바꾸는 것보다 훨씬 더 많은 노력이 든다. 대부분의 스푸핑 도구는 이를 시도하지 않는다.
기기 식별과의 통합
우리의 기기 식별 엔진에서 TLS 핑거프린트는 신호이자 검증자 역할을 한다. 신호로서는 자체 식별 가중치를 가지고 전체 기기 핑거프린트에 기여한다. 검증자로서는 주장된 브라우저 정체성에 대한 교차 확인을 제공한다. JavaScript 신호는 "macOS의 Chrome"이라고 하는데 TLS 핑거프린트가 "Linux의 Firefox"라고 한다면, 이 불일치는 우리의 Smart Signals 분석에서 변조(tampering) 플래그를 발동시킨다.