Go TLS 완전 가이드: 전체 과정 설명
May 23, 2025
# golang
Olivia Novak
Dev Intern · Leapcell

TLS 핸드셰이크 프로세스 설명
TLS (Transport Layer Security) 핸드셰이크는 클라이언트 (웹 브라우저 등)와 서버 (웹 서버 등) 간의 보안 통신을 가능하게 하는 중요한 절차입니다. 다음은 전체 TLS 핸드셰이크 프로세스에 대한 자세한 분석입니다.
-
Client Hello
- 클라이언트는 "Client Hello" 메시지를 서버로 보내 핸드셰이크를 시작합니다.
- 이 메시지는 다음을 포함합니다.
- 클라이언트가 지원하는 TLS 버전.
- 지원하는 암호 스위트 (암호화 알고리즘) 목록.
- 임의의 바이트 문자열 (Client Random이라고 함).
-
Server Hello
- 서버는 "Server Hello" 메시지로 응답합니다.
- 이 메시지는 다음을 포함합니다.
- 선택된 TLS 버전.
- 선택된 암호 스위트.
- 임의의 바이트 문자열 (Server Random이라고 함).
- 서버의 디지털 인증서 (신뢰할 수 있는 인증 기관 (CA)에서 발급).
-
인증서 확인
- 클라이언트는 인증 기관 (CA) 체인을 통해 서버의 인증서를 확인합니다.
- 인증서가 유효하고 만료되지 않았으며 올바른 도메인으로 발급되었는지 확인합니다.
-
Pre - Master Secret 생성
- 클라이언트는 서버의 공개 키 (인증서에서 추출)를 사용하여 "Pre - Master Secret"을 생성합니다.
- 이 비밀은 암호화되어 서버로 전송됩니다.
-
Master Secret 파생
- 클라이언트와 서버는 다음을 사용하여 "Master Secret"을 생성합니다.
- Client Random.
- Server Random.
- Pre - Master Secret.
- Master Secret은 암호화 및 무결성 검사를 위한 세션 키를 파생시키는 데 사용됩니다.
- 클라이언트와 서버는 다음을 사용하여 "Master Secret"을 생성합니다.
-
세션 키 생성
- Master Secret을 사용하여 양측은 다음을 생성합니다.
- 대칭 암호화를 위한 암호화 키.
- 무결성 검사를 위한 MAC (Message Authentication Code) 키.
- Master Secret을 사용하여 양측은 다음을 생성합니다.
-
Client Finished
- 클라이언트는 세션 키로 암호화된 "Finished" 메시지를 보냅니다.
- 이는 핸드셰이크가 성공적이었으며 향후 메시지가 암호화될 것임을 확인합니다.
-
Server Finished
- 서버는 세션 키로 암호화된 자체 "Finished" 메시지를 보냅니다.
- 이는 핸드셰이크의 종료와 암호화된 통신의 시작을 나타냅니다.
-
데이터 전송
- 이후의 모든 통신은 파생된 세션 키를 사용하여 암호화됩니다.
- 데이터는 무결성 검사와 함께 암호화된 패킷으로 전송됩니다.
TLS 핸드셰이크 프로세스 다이어그램
+----------------------------------------+ +----------------------------------------+
| 클라이언트 | | 서버 |
+----------------------------------------+ +----------------------------------------+
| | | |
| ClientHello |----->| |
| [TLS 버전, 암호 스위트, Random] | | |
| | | |
| | | ServerHello |
| |<-----| [TLS 버전, 암호 스위트, Random] |
| | | |
| |<-----| 인증서 |
| | | [서버의 공개 키] |
| | | |
| |<-----| ServerHelloDone |
| | | |
| CertificateVerify | | |
| [서버의 인증서 확인] | | |
| | | |
| ClientKeyExchange |----->| |
| [암호화된 Pre-Master Secret] | | |
| | | |
| ChangeCipherSpec |----->| |
| [암호화 사용 시작] | | |
| | | |
| Finished |----->| |
| [핸드셰이크 무결성 확인] | | |
| | | |
| |<-----| ChangeCipherSpec |
| | | [암호화 사용 시작] |
| | | |
| |<-----| Finished |
| | | [핸드셰이크 무결성 확인] |
| | | |
| 보안 통신 |<--->| 보안 통신 |
| [암호화된 데이터 전송] | | [암호화된 데이터 전송] |
+----------------------------------------+ +----------------------------------------+
GoLang으로 TLS Client Hello 메시지 얻기
다음은 GoLang을 사용하여 모든 ClientHello 메시지를 캡처하는 서버를 구현하는 방법입니다.
인증서 생성
먼저 필요한 SSL 인증서를 생성합니다.
# 개인 키 생성 openssl genrsa -out server.key 2048 # 공개 키 (인증서) 생성 openssl req -new -x509 -key server.key -out server.pem -days 3650
서버 구현
다음은 ClientHello 정보를 캡처하기 위한 전체 서버 코드입니다.
package main import ( "bufio" "crypto/tls" "encoding/json" "fmt" "io/ioutil" "log" "net" "os" "sync" "time" ) type CollectInfos struct { ClientHellos []*tls.ClientHelloInfo sync.Mutex } var collectInfos CollectInfos var currentClientHello *tls.ClientHelloInfo func (c *CollectInfos) collectClientHello(clientHello *tls.ClientHelloInfo) { c.Lock() defer c.Unlock() c.ClientHellos = append(c.ClientHellos, clientHello) } func (c *CollectInfos) DumpInfo() { c.Lock() defer c.Unlock() data, err := json.Marshal(c.ClientHellos) if err != nil { log.Fatal(err) } ioutil.WriteFile("hello.json", data, os.ModePerm) } func getCert() *tls.Certificate { cert, err := tls.LoadX509KeyPair("server.pem", "server.key") if err != nil { log.Println(err) return nil } return &cert } func buildTlsConfig(cert *tls.Certificate) *tls.Config { cfg := &tls.Config{ Certificates: []tls.Certificate{*cert}, GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) { collectInfos.collectClientHello(clientHello) currentClientHello = clientHello return nil, nil }, } return cfg } func serve(cfg *tls.Config) { ln, err := tls.Listen("tcp", ":443", cfg) if err != nil { log.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() if err != nil { log.Println(err) continue } go handler(conn) } } func handler(conn net.Conn) { defer conn.Close() r := bufio.NewReader(conn) for { msg, err := r.ReadString('\n') if err != nil { log.Println(err) return } fmt.Println(msg) data, err := json.Marshal(currentClientHello) if err != nil { log.Fatal(err) } _, err = conn.Write(data) if err != nil { log.Println(err) return } } } func main() { go func() { for { collectInfos.DumpInfo() time.Sleep(10 * time.Second) } }() cert := getCert() if cert != nil { serve(buildTlsConfig(cert)) } }
클라이언트 구현
해당 클라이언트 코드는 다음과 같습니다.
func main() { conn, err := tls.Dial("tcp", "localhost:443", &tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } defer conn.Close() _, err = conn.Write([]byte("hello\n")) if err != nil { log.Fatal(err) } buf := make([]byte, 1000) n, err := conn.Read(buf) if err != nil { log.Fatal(err) } fmt.Println(string(buf[:n])) }
이 구현을 통해 TLS 핸드셰이크 프로세스 중에 ClientHello 메시지에서 자세한 정보를 캡처할 수 있습니다. 서버는 이 정보를 분석을 위해 주기적으로 JSON 파일로 내보냅니다.
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 Go 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 간편하게 개발하십시오.
🌍 무료로 무제한 프로젝트 배포
사용한 만큼만 지불하십시오. 요청이 없으면 요금이 부과되지 않습니다.
⚡ 사용량에 따라 지불, 숨겨진 비용 없음
유휴 요금 없이 원활한 확장성만 제공합니다.
🔹 트위터에서 팔로우하십시오: @LeapcellHQ