Mọi trang web giờ đều là HTTPS, và điều đó tốt cho bảo mật. Nhưng "S" trong HTTPS không miễn phí: trên mỗi kết nối, sau khi bắt tay TCP (bài 1) xong, còn một bắt tay TLS nữa — thỏa thuận thuật toán mã hóa, trao khóa, xác thực chứng chỉ. Bắt tay này tốn thêm thời gian (một RTT hoặc hơn) và tốn CPU đáng kể cho toán khóa công khai. Với hệ mở nhiều kết nối TLS, chi phí này cộng dồn nhanh. Bài này (phần 7 loạt Mạng) chạy thật để đo chính xác TLS đắt hơn TCP bao nhiêu, và session resumption cứu vãn thế nào.

Cơ chế: một bắt tay nữa trên TCP

Quy trình một kết nối HTTPS: bắt tay TCP → bắt tay TLS → mới gửi dữ liệu (đã mã hóa). Bắt tay TLS 1.2 tốn ~2 RTT (nhiều bước qua lại); TLS 1.3 gộp bước xuống còn ~1 RTT. Phần đắt nhất về CPU là toán khóa công khai (bất đối xứng): trao đổi khóa ECDHE và ký/kiểm chứng chỉ — nặng hơn nhiều so với mã hóa đối xứng dùng cho dữ liệu sau đó.

Session resumption cứu vãn cho các kết nối sau: lần đầu làm full handshake, server cấp một session ticket; lần sau client trình ticket, hai bên dùng lại khóa cũ (PSK) — bỏ được toán khóa công khai và bớt RTT (TLS 1.3 còn hỗ trợ 0-RTT: gửi dữ liệu ngay).

// server TLS với self-signed cert
ln, _ := tls.Listen("tcp", addr, &tls.Config{Certificates: cert})
// client đo bắt tay, bật cache để resumption
cfg := &tls.Config{ClientSessionCache: tls.NewLRUClientSessionCache(64)}
c, _ := tls.Dial("tcp", addr, cfg)
c.ConnectionState().Version    // TLS 1.2 / 1.3
c.ConnectionState().DidResume  // có phải resumed không

Ảnh chụp đoạn mã Go nền tối minh hoạ TLS handshake, cơ chế TLS thêm một bắt tay nữa lên trên TCP TCP bắt tay bài 1 TLS bắt tay mới gửi dữ liệu mã hóa TLS 1.2 khoảng 2 RTT thỏa thuận cipher trao khóa xác thực cert TLS 1.3 khoảng 1 RTT gộp bước nhanh hơn tốn thêm vòng khứ hồi RTT cộng CPU mã hóa bất đối xứng ECDHE ký cert đây là phần đắt nhất toán khóa công khai tốn CPU, session resumption kết nối sau bỏ qua phần đắt lần đầu full handshake server cấp session ticket lần sau client trình ticket dùng lại khóa cũ PSK bỏ toán khóa công khai cộng bớt RTT TLS 1.3 0-RTT gửi ngay cfg tls Config ClientSessionCache tls NewLRUClientSessionCache 64, đo bằng Go self-signed cert server client localhost ln tls Listen tcp addr tls Config Certificates cert c tls Dial tcp addr cfg đo thời gian bắt tay c ConnectionState Version TLS 1.2 1.3 c ConnectionState DidResume có phải resumed không lưu ý TLS 1.3 session ticket gửi sau handshake phải Read mới nhận

Hình 1: HTTPS = TCP + TLS; bắt tay TLS thỏa thuận cipher/khóa/cert (TLS 1.3 ~1 RTT), tốn CPU toán khóa công khai; session resumption dùng ticket để bỏ phần đắt cho kết nối sau.

Đo thật: TLS đắt hơn TCP bao nhiêu

Mình dựng HTTPS server self-signed và đo thời gian bắt tay ba trường hợp: TCP thường, TLS full, TLS resumption:

Ảnh chụp bảng kết quả chạy thật TLS handshake output thật, thời gian bắt tay mỗi kết nối TLS version TLS 1.3 một TCP thường không TLS 23 µs mỗi kết nối hai TLS full handshake mỗi lần 391 µs mỗi kết nối ba TLS resumption session cache 353 µs mỗi kết nối resumed 500 trên 500, chi phí thêm và tiết kiệm chi phí TLS thêm so TCP 368 µs full 330 µs resumed resumption nhanh hơn full 1.1 lần trên localhost TLS full đắt gấp 17x TCP thường phần lớn là CPU toán khóa công khai ECDHE cộng ký kiểm chứng chỉ thấy rõ cả trên localhost, lưu ý trung thực về localhost resumption chỉ nhanh hơn 1.1x vì localhost RTT 0 phần tiết kiệm chính của resumption là bỏ bớt RTT mà localhost gần như không có RTT lợi ích ẩn đi trên mạng thực RTT cao resumption bỏ 1 vòng khứ hồi 0-RTT tiết kiệm rõ rệt hơn nhiều DidResume 500 trên 500 chứng minh cơ chế resumption hoạt động đúng, kết luận HTTPS bằng TCP cộng TLS TLS thêm chi phí CPU khóa công khai cộng RTT session resumption cộng keep-alive pool giảm mạnh chi phí TLS lặp

Hình 2: Chạy thật (TLS 1.3) — TCP thường 23µs, TLS full handshake 391µs, TLS resumption 353µs (resumed 500/500); TLS thêm ~368µs so với TCP (đắt gấp 17 lần, phần lớn là CPU); resumption chỉ nhanh 1,1 lần trên localhost vì RTT0.

Đọc kết quả đo được:

  • TLS thêm ~368µs so với TCP thường: TCP thường chỉ 23µs/kết nối, còn TLS full handshake tốn 391µs — đắt gấp 17 lần. Chênh lệch này không phải do RTT (localhost RTT0) mà do CPU: toán khóa công khai ECDHE và kiểm chứng chỉ. Đây là chi phí thật của TLS, hiện rõ ngay cả trên loopback.
  • Resumption hoạt động: 500/500 resumed: với ClientSessionCache, cả 500 kết nối sau đều DidResume=true — chúng dùng lại session cũ thay vì full handshake. Cơ chế đúng như thiết kế.
  • Nhưng trên localhost resumption chỉ nhanh 1,1 lần: 353µs vs 391µs. Đây là chỗ cần trung thực: phần tiết kiệm chính của resumption là bỏ bớt RTT (full = 1-RTT, resumed/0-RTT = 0-RTT). Localhost gần như không có RTT nên lợi ích đó ẩn đi; con số 1,1x chỉ phản ánh phần CPU tiết kiệm được. Trên mạng thực với RTT 50ms, bỏ một RTT là tiết kiệm ~50ms mỗi kết nối — lớn hơn nhiều lần con số localhost.

Đánh đổi cần cân nhắc

Cách rẻ nhất để giảm chi phí TLS là bắt tay TLS ít lần hơn. Giống bài 1 (bắt tay TCP), lời giải không phải làm bắt tay TLS nhanh hơn mà là tránh lặp lại nó. Kết hợp keep-alive + connection pool (bài 5, 6): một kết nối TLS đã bắt tay được tái dùng cho nhiều request, nên chi phí 368µs chỉ trả một lần cho cả loạt. Đây là lý do một client HTTPS cấu hình pool tốt gần như không cảm nhận chi phí TLS trong thực tế.

TLS 1.3 và session ticket nên là mặc định. TLS 1.3 giảm full handshake xuống 1 RTT (so với 2 của 1.2) và bảo mật tốt hơn. Session ticket cho phép resumption. Cả hai đều bật sẵn trong thư viện hiện đại (Go crypto/tls mặc định hỗ trợ). Đừng tắt session ticket vì lý do "bảo mật" mà không hiểu rõ — nó có đánh đổi về forward secrecy nhưng thường được cân bằng tốt trong cài đặt mặc định.

CPU của TLS đáng kể ở quy mô lớn — đó là lý do có TLS offload. 368µs CPU mỗi full handshake nghe nhỏ, nhưng một server nhận hàng nghìn kết nối HTTPS mới mỗi giây sẽ tốn CPU đáng kể chỉ cho bắt tay. Đây là lý do các hệ lớn dùng TLS termination ở load balancer / reverse proxy (nginx, CDN) — gánh chi phí TLS ở một tầng chuyên dụng (đôi khi có phần cứng tăng tốc), để server ứng dụng phía sau chỉ nói HTTP thường.

Ba ý mang về

  1. HTTPS = TCP + TLS, và TLS tốn CPU thật: đo thật TLS full handshake 391µs so với TCP thường 23µs ( 17 lần) — chênh lệch chủ yếu là CPU toán khóa công khai (ECDHE + chứng chỉ), hiện rõ cả trên localhost nơi RTT0.
  2. Session resumption bỏ phần đắt cho kết nối sau: đo thật 500/500 kết nối resumed; nhưng trên localhost chỉ nhanh 1,1 lần vì lợi ích chính là bỏ RTT mà localhost không có — trên mạng thực RTT cao, resumption tiết kiệm cả một vòng khứ hồi.
  3. Giảm chi phí TLS = bắt tay TLS ít lần hơn + offload: tái dùng kết nối (keep-alive/pool) trả chi phí handshake một lần cho cả loạt; dùng TLS 1.3 + session ticket; và ở quy mô lớn, đưa TLS termination lên load balancer/CDN.

Nguồn

Phần sau ta lùi về bước xảy ra trước cả bắt tay TCP: DNS resolution — phân giải tên miền thành IP là một chuyến đi mạng ẩn trước mỗi kết nối tới host mới; đo thật thời gian phân giải và tác dụng của cache.