메인 콘텐츠로 건너뛰기
Deep Thought
← 목록으로
Security

TLS 핸드셰이크: 안전한 웹 통신의 시작

신중선-- views
tlshttpsweb-securitycryptographybrowser

TLS 핸드셰이크란?

TLS(Transport Layer Security) 핸드셰이크는 브라우저와 서버가 HTTPS 통신을 시작하기 전 안전한 연결을 설정하는 과정입니다. 이 과정에서 서로의 신원을 확인하고, 데이터 암호화에 사용할 키를 교환하여 도청이나 중간자 공격으로부터 통신을 보호합니다.

일반적으로 사용자가 https://example.com을 브라우저에 입력하면, 실제 웹페이지 데이터를 주고받기 전에 TLS 핸드셰이크가 먼저 수행됩니다. 이 과정은 보통 100-200ms 정도 소요되며, 한 번 완료되면 해당 세션 동안 암호화된 통신이 가능해집니다.

핵심 개념

1. Client Hello - 연결 요청

브라우저는 서버에 연결을 요청하며 다음 정보를 전송합니다.

interface ClientHello {
  tlsVersion: string;           // 지원하는 TLS 버전 (예: TLS 1.3)
  cipherSuites: string[];       // 지원하는 암호화 알고리즘 목록
  sessionId?: string;           // 기존 세션 재사용을 위한 ID
  clientRandom: ArrayBuffer;    // 32바이트 난수
  extensions: Extension[];      // SNI, ALPN 등 확장 기능
}

// 브라우저가 보내는 정보 예시
const clientHello = {
  tlsVersion: "TLS 1.3",
  cipherSuites: [
    "TLS_AES_256_GCM_SHA384",
    "TLS_AES_128_GCM_SHA256",
    "TLS_CHACHA20_POLY1305_SHA256"
  ],
  clientRandom: new Uint8Array(32).map(() => Math.floor(Math.random() * 256)),
  extensions: [
    { type: "server_name", value: "example.com" },
    { type: "application_layer_protocol_negotiation", value: ["h2", "http/1.1"] }
  ]
};

2. Server Hello - 서버 응답

서버는 클라이언트의 요청을 검토하고 다음과 같이 응답합니다.

interface ServerHello {
  selectedTlsVersion: string;
  selectedCipherSuite: string;
  serverRandom: ArrayBuffer;
  sessionId: string;
  certificate: X509Certificate;
  extensions: Extension[];
}

// 서버 응답 예시
const serverHello = {
  selectedTlsVersion: "TLS 1.3",
  selectedCipherSuite: "TLS_AES_256_GCM_SHA384",
  serverRandom: new Uint8Array(32).map(() => Math.floor(Math.random() * 256)),
  certificate: {
    subject: "CN=example.com",
    issuer: "CN=Let's Encrypt Authority X3",
    publicKey: "-----BEGIN PUBLIC KEY-----...",
    validFrom: "2024-01-01",
    validTo: "2024-12-31"
  }
};

3. 인증서 검증과 키 교환

브라우저는 서버의 디지털 인증서를 검증하고 암호화 키를 생성합니다.

// 인증서 검증 과정
async function verifyCertificate(certificate: X509Certificate): Promise<boolean> {
  // 1. 인증서 유효기간 확인
  const now = new Date();
  if (now < certificate.validFrom || now > certificate.validTo) {
    return false;
  }
  
  // 2. CA(Certificate Authority) 서명 검증
  const isSignatureValid = await crypto.subtle.verify(
    "RSASSA-PKCS1-v1_5",
    certificate.issuerPublicKey,
    certificate.signature,
    certificate.tbsCertificate
  );
  
  // 3. 도메인 이름 일치 확인
  const isHostnameValid = certificate.subject.includes(location.hostname);
  
  return isSignatureValid && isHostnameValid;
}

// Pre-Master Secret 생성 및 전송
async function generatePreMasterSecret(serverPublicKey: CryptoKey): Promise<ArrayBuffer> {
  // 48바이트 랜덤 값 생성
  const preMasterSecret = crypto.getRandomValues(new Uint8Array(48));
  
  // 서버 공개키로 암호화
  const encryptedSecret = await crypto.subtle.encrypt(
    "RSA-OAEP",
    serverPublicKey,
    preMasterSecret
  );
  
  return encryptedSecret;
}

4. 대칭키 생성과 통신 시작

양측이 동일한 대칭키를 생성하여 실제 데이터 암호화에 사용합니다.

// Master Secret 생성 (PRF: Pseudo-Random Function 사용)
async function generateMasterSecret(
  preMasterSecret: ArrayBuffer,
  clientRandom: ArrayBuffer,
  serverRandom: ArrayBuffer
): Promise<ArrayBuffer> {
  const seed = new Uint8Array([...clientRandom, ...serverRandom]);
  
  // HMAC-SHA256을 사용한 키 확장
  const masterSecret = await crypto.subtle.importKey(
    "raw",
    preMasterSecret,
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  
  return crypto.subtle.sign("HMAC", masterSecret, seed);
}

// 세션 키 생성
async function generateSessionKeys(
  masterSecret: ArrayBuffer,
  clientRandom: ArrayBuffer,
  serverRandom: ArrayBuffer
): Promise<{
  clientWriteKey: CryptoKey;
  serverWriteKey: CryptoKey;
  clientWriteIV: ArrayBuffer;
  serverWriteIV: ArrayBuffer;
}> {
  const keyMaterial = await expandKey(masterSecret, clientRandom, serverRandom, 128);
  
  return {
    clientWriteKey: await crypto.subtle.importKey("raw", keyMaterial.slice(0, 32), "AES-GCM", false, ["encrypt"]),
    serverWriteKey: await crypto.subtle.importKey("raw", keyMaterial.slice(32, 64), "AES-GCM", false, ["decrypt"]),
    clientWriteIV: keyMaterial.slice(64, 80),
    serverWriteIV: keyMaterial.slice(80, 96)
  };
}

정리

단계 주요 작업 목적
Client Hello TLS 버전, 암호화 알고리즘, 클라이언트 난수 전송 지원 기능 협상 시작
Server Hello 선택된 암호화 방식, 서버 난수, 인증서 전송 서버 신원 증명 및 설정 확정
Certificate Verify CA 서명 검증, 도메인 확인 서버 신뢰성 검증
Key Exchange Pre-Master Secret 암호화 전송 안전한 키 교환
Session Key Generation 공유된 값들로 대칭키 생성 실제 통신 암호화 준비
Finished 암호화된 핸드셰이크 완료 메시지 교환 연결 성공 확인

TLS 핸드셰이크가 완료되면 브라우저와 서버는 생성된 대칭키로 모든 HTTP 데이터를 암호화하여 주고받습니다. 이를 통해 사용자의 민감한 정보가 네트워크상에서 안전하게 보호됩니다.

References