在當今的網路環境中,資料傳輸的安全性已成為不可妥協的基本要求。無論是線上購物的信用卡資訊、企業內部的機密文件,或是個人的社交通訊內容,都需要可靠的加密機制保護。SSL/TLS 協定作為網際網路安全通訊的基石,透過非對稱加密、數位憑證與密鑰交換等技術,為資料傳輸建立了端到端的安全通道。

從 1994 年 Netscape 發布 SSL 2.0 開始,這個協定歷經多次演進與改良。SSL 3.0 修正了前代的諸多安全缺陷,而 TLS 1.0 則在 1999 年成為正式的網際網路標準。隨著密碼學研究的進展與攻擊手法的演化,協定持續升級,TLS 1.2 與 1.3 相繼問世,提供了更強的安全保證與更佳的效能表現。如今,HTTPS 已成為網站的標準配置,瀏覽器也逐步淘汰對舊版協定的支援。

在台灣的資訊安全領域,SSL/TLS 的正確實作與配置是企業合規與資安防護的重要環節。從金融業的線上交易系統到醫療院所的病歷傳輸,從政府機關的公文系統到電商平台的會員資料,都仰賴 SSL/TLS 提供的安全保障。然而,僅僅啟用 HTTPS 並不足以確保安全,憑證管理、密碼套件選擇、協定版本控制等細節都可能成為安全缺口。

本文將從技術實務的角度深入探討 SSL/TLS 協定的運作機制,涵蓋握手流程、憑證體系、密鑰交換與加密通訊等核心主題。同時,我們也將探討安全測試工具的使用、代理伺服器的應用場景,以及常見的攻擊手法與防禦策略。透過理論分析與實戰案例的結合,讀者將能全面掌握 SSL/TLS 技術棧,並在實際專案中建構安全可靠的通訊系統。

SSL/TLS 協定架構與演進

SSL/TLS 協定是一個分層架構的安全協定棧,建構在 TCP 之上,為上層應用協定提供透明的加密服務。理解這個架構對於掌握協定的運作原理至關重要。整個協定棧主要包含四個子協定,分別是記錄協定、握手協定、警示協定與變更密碼規格協定。

記錄協定是整個架構的基礎層,負責將上層資料分割、壓縮、加密與認證。它為每個記錄加上訊息認證碼,確保資料的完整性與真實性。握手協定則運行在記錄協定之上,負責協商加密參數、驗證通訊雙方的身份,並建立會話金鑰。警示協定用於傳遞錯誤與警告訊息,當協定層偵測到異常狀況時,會透過警示協定通知對方。變更密碼規格協定是最簡單的子協定,只包含單一訊息,用於通知對方即將啟用新協商的安全參數。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "SSL/TLS 協定層次架構" {
  package "應用層協定" {
    component "HTTP" as http
    component "FTP" as ftp
    component "SMTP" as smtp
    component "其他協定" as other
  }
  
  package "SSL/TLS 協定層" {
    component "握手協定\n(Handshake)" as handshake
    component "警示協定\n(Alert)" as alert
    component "變更密碼規格\n(Change Cipher Spec)" as ccs
    component "應用資料協定\n(Application Data)" as appdata
    
    component "記錄協定\n(Record Protocol)" as record
  }
  
  component "TCP 傳輸層" as tcp
}

http --> record
ftp --> record
smtp --> record
other --> record

handshake --> record
alert --> record
ccs --> record
appdata --> record

record --> tcp

note right of handshake
  身份驗證
  密鑰交換
  參數協商
end note

note bottom of record
  資料分割
  壓縮處理
  加密與 MAC
end note

@enduml

TLS 1.3 在 2018 年發布,代表了協定演進的重要里程碑。相較於 TLS 1.2,新版本移除了多個過時且不安全的特性,包含 RSA 密鑰交換、CBC 模式密碼套件與 MD5/SHA1 雜湊演算法。握手流程也經過重新設計,從兩個往返簡化為一個往返,大幅降低了連線建立的延遲。前向保密成為強制要求,即使長期私鑰洩漏,過去的通訊內容仍無法被解密。

在實務部署中,協定版本的選擇需要平衡安全性與相容性。雖然 TLS 1.3 提供了最佳的安全保證,但某些舊版客戶端可能不支援。因此,許多伺服器配置為支援 TLS 1.2 與 1.3,但禁用 TLS 1.1 及更早的版本。這種策略在維持廣泛相容性的同時,也確保了基本的安全水準。

SSL/TLS 握手流程深度解析

SSL/TLS 握手是建立安全連線的關鍵過程,涉及身份驗證、密鑰交換與加密參數協商等多個步驟。理解握手流程對於診斷連線問題、優化效能與識別安全威脅都至關重要。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

participant "客戶端" as client
participant "伺服器" as server

== TLS 握手階段 ==

client -> server : ClientHello\n(支援的協定版本、密碼套件、隨機數)

note right of client
  包含客戶端隨機數
  支援的 TLS 版本
  支援的密碼套件列表
end note

server -> client : ServerHello\n(選定的協定版本、密碼套件、隨機數)

note left of server
  選擇協定版本
  選擇密碼套件
  生成伺服器隨機數
end note

server -> client : Certificate\n(伺服器憑證鏈)

note left of server
  包含 X.509 憑證
  CA 簽發的憑證鏈
  公鑰資訊
end note

server -> client : ServerKeyExchange\n(密鑰交換參數)

server -> client : ServerHelloDone

client -> client : 驗證憑證\n檢查有效期\n檢查簽發者

client -> server : ClientKeyExchange\n(加密的預主密鑰)

note right of client
  生成預主密鑰
  用伺服器公鑰加密
end note

client -> client : 計算主密鑰\n和會話金鑰

server -> server : 解密預主密鑰\n計算主密鑰\n和會話金鑰

client -> server : ChangeCipherSpec

client -> server : Finished\n(加密的握手訊息摘要)

server -> client : ChangeCipherSpec

server -> client : Finished\n(加密的握手訊息摘要)

== 加密通訊階段 ==

client <-> server : 加密應用資料傳輸

note over client, server
  使用對稱加密
  會話金鑰保護資料
end note

@enduml

握手過程以 ClientHello 訊息開始,客戶端向伺服器宣告其支援的 TLS 版本、密碼套件與壓縮方法,並提供一個隨機數。這個隨機數在後續的密鑰衍生過程中扮演重要角色,確保每次連線的會話金鑰都是唯一的。密碼套件列表按照客戶端的偏好順序排列,通常將最安全的套件放在前面。

伺服器收到 ClientHello 後,從客戶端提供的密碼套件列表中選擇一個支援的套件,並在 ServerHello 訊息中回覆。選擇的依據通常是伺服器的安全政策與效能考量。伺服器也會生成自己的隨機數,與客戶端隨機數一起用於後續的密鑰計算。接著,伺服器發送憑證鏈,證明其身份。憑證鏈從伺服器憑證開始,包含所有中繼 CA 憑證,直到受信任的根 CA 憑證。

憑證驗證是握手過程中的關鍵安全檢查。客戶端需要驗證憑證的有效期、檢查憑證是否被撤銷、確認憑證的主體名稱與目標網域相符,並驗證整個憑證鏈的簽名。任何一項檢查失敗都會導致連線中止。在現代瀏覽器中,憑證透明度日誌也成為驗證的一環,確保憑證的簽發過程是公開可審計的。

密鑰交換階段根據選擇的密碼套件而有所不同。傳統的 RSA 密鑰交換中,客戶端生成預主密鑰,用伺服器的公鑰加密後發送。在更安全的 ECDHE 密鑰交換中,雙方各自生成臨時的橢圓曲線密鑰對,交換公鑰後計算共享密鑰。ECDHE 提供了前向保密特性,即使伺服器的長期私鑰洩漏,過去的會話金鑰仍無法被推算出來。

握手的最後階段是 Finished 訊息的交換。這個訊息包含整個握手過程的雜湊摘要,用新協商的會話金鑰加密。雙方驗證 Finished 訊息,確認握手過程沒有被竄改,且雙方對於安全參數的理解是一致的。驗證通過後,握手完成,雙方開始使用會話金鑰進行加密通訊。

憑證體系與 PKI 基礎設施

公鑰基礎設施是 SSL/TLS 安全性的基石,它透過數位憑證與憑證授權機構建立了信任鏈。理解 PKI 的運作機制對於正確配置與管理 SSL/TLS 至關重要。

X.509 是最廣泛使用的數位憑證標準,定義了憑證的格式與內容。一個標準的 X.509 憑證包含版本號、序號、簽名演算法、簽發者名稱、有效期、主體名稱、主體公鑰、可選的擴展欄位與憑證授權機構的數位簽名。擴展欄位提供了額外的功能,例如主體別名可以讓單一憑證涵蓋多個網域,基本約束欄位則定義憑證是否可以作為 CA 憑證使用。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "PKI 憑證信任鏈" {
  component "根 CA\n(Root CA)" as root_ca
  component "中繼 CA 1\n(Intermediate CA)" as inter_ca1
  component "中繼 CA 2\n(Intermediate CA)" as inter_ca2
  component "伺服器憑證\n(example.com)" as server_cert
  
  database "根憑證儲存區\n(Trust Store)" as trust_store
  
  actor "客戶端" as client
}

trust_store --> root_ca : 內建信任

root_ca --> inter_ca1 : 簽發憑證
inter_ca1 --> inter_ca2 : 簽發憑證
inter_ca2 --> server_cert : 簽發憑證

client --> server_cert : 驗證憑證鏈
server_cert --> inter_ca2 : 驗證簽名
inter_ca2 --> inter_ca1 : 驗證簽名
inter_ca1 --> root_ca : 驗證簽名
root_ca --> trust_store : 檢查信任

note right of root_ca
  自簽名憑證
  預先安裝於系統
  極少變更
end note

note bottom of inter_ca1
  由根 CA 簽發
  實際簽發伺服器憑證
  可以撤銷與更新
end note

note left of server_cert
  包含網域資訊
  包含公鑰
  有效期限制
end note

@enduml

憑證信任鏈從受信任的根 CA 開始。根 CA 的憑證是自簽名的,預先安裝在作業系統或瀏覽器的信任儲存區中。根 CA 通常不直接簽發伺服器憑證,而是簽發中繼 CA 憑證,由中繼 CA 負責實際的憑證簽發工作。這種階層架構提供了更好的安全性與靈活性,當中繼 CA 的私鑰洩漏時,只需要撤銷該中繼 CA 憑證,而不影響整個根 CA。

憑證撤銷是 PKI 體系的重要安全機制。當憑證的私鑰洩漏或憑證資訊有誤時,需要在到期前撤銷憑證。傳統的撤銷機制包含憑證撤銷列表與線上憑證狀態協定。CRL 是 CA 定期發布的已撤銷憑證清單,但其檔案大小與更新延遲是顯著的缺點。OCSP 允許即時查詢憑證狀態,但增加了額外的網路請求。OCSP Stapling 透過讓伺服器在握手時附帶 OCSP 回應,解決了效能問題。

在實務應用中,憑證的申請與管理是一個複雜的流程。企業通常需要生成密鑰對、建立憑證簽發請求、提交給 CA、經過域名驗證或組織驗證程序,最後獲得簽發的憑證。Let’s Encrypt 的出現大幅簡化了這個過程,透過 ACME 協定實現了自動化的憑證申請與更新。

#!/bin/bash

# OpenSSL 憑證生成完整流程示範
# 展示從密鑰生成到自簽憑證的完整過程

echo "=== SSL/TLS 憑證生成實戰範例 ==="

# 步驟 1: 生成私鑰
# 使用 RSA 2048 位元金鑰(生產環境建議 4096 位元)
echo -e "\n[步驟 1] 生成 RSA 私鑰..."
openssl genrsa -out server.key 2048

# 驗證私鑰
echo "[驗證] 檢查私鑰格式..."
openssl rsa -in server.key -text -noout | head -n 5

# 步驟 2: 生成憑證簽發請求 (CSR)
# 包含伺服器的識別資訊
echo -e "\n[步驟 2] 生成憑證簽發請求..."
openssl req -new -key server.key -out server.csr \
  -subj "/C=TW/ST=Taiwan/L=Taipei/O=Example Corp/OU=IT/CN=example.com"

# 查看 CSR 內容
echo "[驗證] 檢查 CSR 內容..."
openssl req -in server.csr -text -noout | grep -A 5 "Subject:"

# 步驟 3: 生成自簽憑證(測試環境使用)
# 生產環境應提交 CSR 給受信任的 CA
echo -e "\n[步驟 3] 生成自簽憑證 (365 天有效期)..."
openssl x509 -req -days 365 \
  -in server.csr \
  -signkey server.key \
  -out server.crt

# 查看憑證詳細資訊
echo -e "\n[驗證] 憑證詳細資訊:"
openssl x509 -in server.crt -text -noout | head -n 20

# 步驟 4: 驗證憑證與私鑰配對
echo -e "\n[驗證] 確認憑證與私鑰配對..."
cert_modulus=$(openssl x509 -noout -modulus -in server.crt | openssl md5)
key_modulus=$(openssl rsa -noout -modulus -in server.key | openssl md5)

if [ "$cert_modulus" = "$key_modulus" ]; then
    echo "✓ 憑證與私鑰配對正確"
else
    echo "✗ 憑證與私鑰不配對"
fi

# 步驟 5: 生成包含 SAN 的憑證(支援多網域)
echo -e "\n[進階] 生成包含 SAN 擴展的憑證..."
cat > san.cnf <<EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
organizationName = Organization Name
commonName = Common Name

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
IP.1 = 192.168.1.100
EOF

# 使用配置檔生成憑證
openssl req -new -x509 -days 365 \
  -key server.key \
  -out server-san.crt \
  -config san.cnf \
  -extensions v3_req \
  -subj "/C=TW/ST=Taiwan/L=Taipei/O=Example Corp/CN=example.com"

echo "[驗證] SAN 憑證內容:"
openssl x509 -in server-san.crt -text -noout | grep -A 5 "Subject Alternative Name"

# 步驟 6: 生成 PKCS#12 格式(用於 Windows/Java)
echo -e "\n[格式轉換] 生成 PKCS#12 格式..."
openssl pkcs12 -export \
  -out server.p12 \
  -inkey server.key \
  -in server.crt \
  -password pass:changeit

echo -e "\n=== 憑證生成完成 ==="
echo "生成的檔案:"
ls -lh server.* san.cnf

# 安全建議
echo -e "\n=== 安全建議 ==="
echo "1. 私鑰檔案 (server.key) 應設定嚴格權限: chmod 600 server.key"
echo "2. 定期更新憑證,建議有效期不超過 90 天"
echo "3. 生產環境使用受信任 CA 簽發的憑證"
echo "4. 啟用 OCSP Stapling 提升效能與隱私"
echo "5. 使用 4096 位元或更高的 RSA 金鑰長度"

這個完整的範例展示了憑證生成的所有關鍵步驟,包含私鑰生成、CSR 建立、自簽憑證簽發與多域名支援。在實際部署中,私鑰的保護至關重要,應該設定嚴格的檔案權限,並考慮使用硬體安全模組儲存關鍵的私鑰。

密碼套件選擇與安全配置

密碼套件定義了 SSL/TLS 連線使用的加密演算法組合,包含密鑰交換演算法、身份驗證演算法、對稱加密演算法與訊息認證碼演算法。選擇適當的密碼套件對於確保通訊安全至關重要。

一個典型的密碼套件名稱如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,其中 ECDHE 表示使用橢圓曲線 Diffie-Hellman 臨時密鑰交換,RSA 表示使用 RSA 進行身份驗證,AES_128_GCM 表示使用 128 位元金鑰的 AES 加密搭配 GCM 模式,SHA256 則是用於偽隨機函數的雜湊演算法。

在選擇密碼套件時,需要考慮多個因素。前向保密是首要考量,因此應優先選擇使用 ECDHE 或 DHE 的套件。對稱加密演算法方面,AES-GCM 提供了優秀的安全性與效能,ChaCha20-Poly1305 則在行動裝置上表現更佳。應避免使用已知存在漏洞的演算法,如 RC4、3DES 與 CBC 模式的套件。

#!/usr/bin/env python3
"""
SSL/TLS 安全配置檢測工具

使用 Python 的 ssl 模組檢測目標伺服器的 SSL/TLS 配置
分析協定版本、密碼套件與憑證資訊
"""

import ssl
import socket
import sys
from datetime import datetime
from typing import Dict, List, Tuple

class SSLConfigAnalyzer:
    """SSL/TLS 配置分析器"""
    
    # 已知不安全的密碼套件關鍵字
    INSECURE_CIPHERS = [
        'NULL', 'EXPORT', 'DES', 'RC4', 'MD5',
        'PSK', 'SRP', 'CAMELLIA', 'IDEA', 'SEED'
    ]
    
    # 推薦的密碼套件(按優先級排序)
    RECOMMENDED_CIPHERS = [
        'TLS_AES_256_GCM_SHA384',
        'TLS_CHACHA20_POLY1305_SHA256',
        'TLS_AES_128_GCM_SHA256',
        'ECDHE-RSA-AES256-GCM-SHA384',
        'ECDHE-RSA-AES128-GCM-SHA256'
    ]
    
    def __init__(self, hostname: str, port: int = 443):
        self.hostname = hostname
        self.port = port
        self.results = {}
    
    def check_protocol_versions(self) -> Dict[str, bool]:
        """
        檢測支援的 SSL/TLS 協定版本
        
        Returns:
            包含各協定版本支援狀態的字典
        """
        protocols = {
            'SSLv2': ssl.PROTOCOL_SSLv23,  # 已棄用
            'SSLv3': ssl.PROTOCOL_SSLv23,  # 已棄用
            'TLSv1.0': ssl.PROTOCOL_TLSv1,
            'TLSv1.1': ssl.PROTOCOL_TLSv1_1,
            'TLSv1.2': ssl.PROTOCOL_TLSv1_2,
        }
        
        # TLS 1.3 在較新的 Python 版本中支援
        if hasattr(ssl, 'PROTOCOL_TLS'):
            protocols['TLSv1.3'] = ssl.PROTOCOL_TLS
        
        results = {}
        
        for name, protocol in protocols.items():
            try:
                context = ssl.SSLContext(protocol)
                context.check_hostname = False
                context.verify_mode = ssl.CERT_NONE
                
                with socket.create_connection((self.hostname, self.port), timeout=5) as sock:
                    with context.wrap_socket(sock, server_hostname=self.hostname) as ssock:
                        results[name] = True
                        
            except Exception as e:
                results[name] = False
        
        return results
    
    def get_cipher_suites(self) -> List[str]:
        """
        獲取伺服器支援的密碼套件
        
        Returns:
            密碼套件列表
        """
        try:
            context = ssl.create_default_context()
            
            with socket.create_connection((self.hostname, self.port), timeout=5) as sock:
                with context.wrap_socket(sock, server_hostname=self.hostname) as ssock:
                    return [cipher[0] for cipher in ssock.shared_ciphers() or []]
        
        except Exception as e:
            print(f"獲取密碼套件失敗: {e}")
            return []
    
    def analyze_cipher_security(self, ciphers: List[str]) -> Dict[str, List[str]]:
        """
        分析密碼套件的安全性
        
        Args:
            ciphers: 密碼套件列表
            
        Returns:
            分類後的密碼套件字典
        """
        secure = []
        weak = []
        insecure = []
        
        for cipher in ciphers:
            # 檢查是否為不安全的套件
            if any(keyword in cipher.upper() for keyword in self.INSECURE_CIPHERS):
                insecure.append(cipher)
            # 檢查是否為推薦的套件
            elif any(rec in cipher for rec in self.RECOMMENDED_CIPHERS):
                secure.append(cipher)
            else:
                weak.append(cipher)
        
        return {
            'secure': secure,
            'weak': weak,
            'insecure': insecure
        }
    
    def get_certificate_info(self) -> Dict:
        """
        獲取伺服器憑證資訊
        
        Returns:
            憑證資訊字典
        """
        try:
            context = ssl.create_default_context()
            
            with socket.create_connection((self.hostname, self.port), timeout=5) as sock:
                with context.wrap_socket(sock, server_hostname=self.hostname) as ssock:
                    cert = ssock.getpeercert()
                    
                    # 解析憑證資訊
                    info = {
                        'subject': dict(x[0] for x in cert['subject']),
                        'issuer': dict(x[0] for x in cert['issuer']),
                        'version': cert['version'],
                        'serial_number': cert['serialNumber'],
                        'not_before': cert['notBefore'],
                        'not_after': cert['notAfter'],
                        'san': cert.get('subjectAltName', [])
                    }
                    
                    # 檢查憑證有效期
                    not_after = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
                    days_to_expiry = (not_after - datetime.now()).days
                    info['days_to_expiry'] = days_to_expiry
                    
                    return info
        
        except Exception as e:
            print(f"獲取憑證資訊失敗: {e}")
            return {}
    
    def run_full_analysis(self) -> None:
        """執行完整的 SSL/TLS 配置分析"""
        print(f"\n{'='*60}")
        print(f"SSL/TLS 安全配置分析報告")
        print(f"目標: {self.hostname}:{self.port}")
        print(f"{'='*60}")
        
        # 檢測協定版本
        print("\n[協定版本支援]")
        protocols = self.check_protocol_versions()
        for name, supported in protocols.items():
            status = "✓ 支援" if supported else "✗ 不支援"
            security = ""
            
            if name in ['SSLv2', 'SSLv3', 'TLSv1.0', 'TLSv1.1']:
                security = " [不安全,應禁用]"
            elif name == 'TLSv1.3':
                security = " [推薦]"
            
            print(f"  {name}: {status}{security}")
        
        # 分析密碼套件
        print("\n[密碼套件分析]")
        ciphers = self.get_cipher_suites()
        
        if ciphers:
            analysis = self.analyze_cipher_security(ciphers)
            
            print(f"\n  安全套件 ({len(analysis['secure'])} 個):")
            for cipher in analysis['secure'][:5]:
                print(f"    ✓ {cipher}")
            
            if analysis['weak']:
                print(f"\n  弱安全套件 ({len(analysis['weak'])} 個):")
                for cipher in analysis['weak'][:3]:
                    print(f"    ⚠ {cipher}")
            
            if analysis['insecure']:
                print(f"\n  不安全套件 ({len(analysis['insecure'])} 個):")
                for cipher in analysis['insecure']:
                    print(f"    ✗ {cipher}")
        
        # 憑證資訊
        print("\n[憑證資訊]")
        cert_info = self.get_certificate_info()
        
        if cert_info:
            print(f"  主體: {cert_info['subject'].get('commonName', 'N/A')}")
            print(f"  簽發者: {cert_info['issuer'].get('commonName', 'N/A')}")
            print(f"  有效期: {cert_info['not_before']}{cert_info['not_after']}")
            
            days = cert_info['days_to_expiry']
            if days < 0:
                print(f"  到期狀態: ✗ 已過期 {abs(days)} 天")
            elif days < 30:
                print(f"  到期狀態: ⚠ 即將到期 ({days} 天)")
            else:
                print(f"  到期狀態: ✓ 有效 (剩餘 {days} 天)")
            
            if cert_info['san']:
                print(f"  SAN: {', '.join(x[1] for x in cert_info['san'][:3])}")
        
        # 安全建議
        print("\n[安全建議]")
        if protocols.get('SSLv2') or protocols.get('SSLv3'):
            print("  ✗ 禁用 SSLv2/SSLv3 協定")
        if protocols.get('TLSv1.0') or protocols.get('TLSv1.1'):
            print("  ⚠ 考慮禁用 TLSv1.0/1.1")
        if not protocols.get('TLSv1.3'):
            print("  ⚠ 啟用 TLSv1.3 以獲得最佳安全性")
        
        if ciphers:
            analysis = self.analyze_cipher_security(ciphers)
            if analysis['insecure']:
                print("  ✗ 移除不安全的密碼套件")
            if not analysis['secure']:
                print("  ⚠ 配置安全的密碼套件")
        
        print(f"\n{'='*60}\n")

# 使用範例
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"使用方式: {sys.argv[0]} <hostname> [port]")
        sys.exit(1)
    
    hostname = sys.argv[1]
    port = int(sys.argv[2]) if len(sys.argv) > 2 else 443
    
    analyzer = SSLConfigAnalyzer(hostname, port)
    analyzer.run_full_analysis()

這個工具提供了完整的 SSL/TLS 配置分析功能,能夠檢測協定版本支援、分析密碼套件安全性與驗證憑證資訊。在實務應用中,定期執行這類安全掃描能及早發現配置問題。

代理伺服器與中間人分析

代理伺服器在網路架構中扮演多重角色,從內容快取到存取控制,從匿名瀏覽到流量分析。在 SSL/TLS 環境中,代理伺服器的應用更為複雜,涉及憑證信任與加密解密等敏感操作。

透明 SSL 代理透過動態生成憑證的方式,攔截並解密 HTTPS 流量。當客戶端嘗試連線到 HTTPS 網站時,代理伺服器以目標網站的名義生成一張憑證,用代理伺服器的 CA 私鑰簽署。如果客戶端信任這個 CA,連線就能建立。代理伺服器實際上建立了兩條 SSL 連線,分別與客戶端和目標伺服器通訊,在中間解密並檢查流量。

#!/usr/bin/env python3
"""
mitmproxy 基礎 SSL/TLS 流量分析工具

展示如何使用 mitmproxy 攔截與分析 HTTPS 流量
包含請求/回應記錄、標頭分析與內容檢查
"""

from mitmproxy import http, ctx
from typing import Optional
import json
import re

class HTTPSTrafficAnalyzer:
    """HTTPS 流量分析器"""
    
    def __init__(self):
        self.request_count = 0
        self.sensitive_patterns = {
            'email': r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}',
            'credit_card': r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',
            'phone': r'\b09\d{8}\b',
            'id_number': r'\b[A-Z]\d{9}\b'
        }
    
    def request(self, flow: http.HTTPFlow) -> None:
        """
        處理 HTTP/HTTPS 請求
        
        Args:
            flow: HTTP 流物件
        """
        self.request_count += 1
        request = flow.request
        
        # 記錄基本請求資訊
        ctx.log.info(f"\n{'='*60}")
        ctx.log.info(f"請求 #{self.request_count}")
        ctx.log.info(f"{'='*60}")
        ctx.log.info(f"方法: {request.method}")
        ctx.log.info(f"URL: {request.pretty_url}")
        ctx.log.info(f"Host: {request.host}")
        
        # 分析請求標頭
        ctx.log.info("\n[請求標頭]")
        for name, value in request.headers.items():
            # 標記敏感標頭
            is_sensitive = name.lower() in [
                'authorization', 'cookie', 'x-csrf-token'
            ]
            marker = "🔒" if is_sensitive else "  "
            ctx.log.info(f"{marker} {name}: {value}")
        
        # 檢查 Cookie
        if 'Cookie' in request.headers:
            self._analyze_cookies(request.headers['Cookie'])
        
        # 分析請求內容
        if request.content:
            self._analyze_request_content(request)
    
    def response(self, flow: http.HTTPFlow) -> None:
        """
        處理 HTTP/HTTPS 回應
        
        Args:
            flow: HTTP 流物件
        """
        response = flow.response
        
        ctx.log.info(f"\n[回應資訊]")
        ctx.log.info(f"狀態碼: {response.status_code}")
        ctx.log.info(f"內容類型: {response.headers.get('Content-Type', 'N/A')}")
        ctx.log.info(f"內容長度: {len(response.content)} bytes")
        
        # 檢查安全標頭
        self._check_security_headers(response)
        
        # 分析回應內容
        if response.content:
            self._analyze_response_content(response)
        
        ctx.log.info(f"{'='*60}\n")
    
    def _analyze_cookies(self, cookie_header: str) -> None:
        """
        分析 Cookie 標頭
        
        Args:
            cookie_header: Cookie 標頭值
        """
        ctx.log.info("\n[Cookie 分析]")
        cookies = cookie_header.split('; ')
        
        for cookie in cookies:
            if '=' in cookie:
                name, value = cookie.split('=', 1)
                # 檢查 Cookie 屬性
                is_secure = 'Secure' in cookie
                is_httponly = 'HttpOnly' in cookie
                is_samesite = 'SameSite' in cookie
                
                security_flags = []
                if is_secure:
                    security_flags.append("Secure")
                if is_httponly:
                    security_flags.append("HttpOnly")
                if is_samesite:
                    security_flags.append("SameSite")
                
                flags_str = f" [{', '.join(security_flags)}]" if security_flags else " [無安全屬性]"
                ctx.log.info(f"  {name}: {value[:20]}...{flags_str}")
    
    def _analyze_request_content(self, request: http.Request) -> None:
        """
        分析請求內容
        
        Args:
            request: HTTP 請求物件
        """
        content_type = request.headers.get('Content-Type', '')
        
        if 'application/json' in content_type:
            try:
                data = json.loads(request.content)
                ctx.log.info("\n[JSON 請求資料]")
                ctx.log.info(json.dumps(data, indent=2, ensure_ascii=False))
                
                # 檢查敏感資料
                self._detect_sensitive_data(json.dumps(data))
                
            except json.JSONDecodeError:
                pass
        
        elif 'application/x-www-form-urlencoded' in content_type:
            ctx.log.info("\n[表單資料]")
            try:
                body = request.content.decode('utf-8')
                params = body.split('&')
                for param in params:
                    if '=' in param:
                        key, value = param.split('=', 1)
                        ctx.log.info(f"  {key}: {value}")
            except UnicodeDecodeError:
                pass
    
    def _analyze_response_content(self, response: http.Response) -> None:
        """
        分析回應內容
        
        Args:
            response: HTTP 回應物件
        """
        content_type = response.headers.get('Content-Type', '')
        
        if 'application/json' in content_type:
            try:
                data = json.loads(response.content)
                ctx.log.info("\n[JSON 回應資料]")
                
                # 只顯示前幾個欄位以避免輸出過長
                if isinstance(data, dict):
                    preview = {k: v for k, v in list(data.items())[:3]}
                    ctx.log.info(json.dumps(preview, indent=2, ensure_ascii=False))
                    if len(data) > 3:
                        ctx.log.info(f"  ... (還有 {len(data) - 3} 個欄位)")
                
            except json.JSONDecodeError:
                pass
    
    def _check_security_headers(self, response: http.Response) -> None:
        """
        檢查安全相關的 HTTP 標頭
        
        Args:
            response: HTTP 回應物件
        """
        security_headers = {
            'Strict-Transport-Security': 'HSTS',
            'Content-Security-Policy': 'CSP',
            'X-Frame-Options': 'Clickjacking 防護',
            'X-Content-Type-Options': 'MIME 嗅探防護',
            'X-XSS-Protection': 'XSS 防護',
            'Referrer-Policy': 'Referrer 政策'
        }
        
        ctx.log.info("\n[安全標頭檢查]")
        for header, description in security_headers.items():
            if header in response.headers:
                ctx.log.info(f"  ✓ {description}: {response.headers[header]}")
            else:
                ctx.log.info(f"  ✗ 缺少 {description}")
    
    def _detect_sensitive_data(self, content: str) -> None:
        """
        檢測敏感資料
        
        Args:
            content: 要檢測的內容
        """
        ctx.log.info("\n[敏感資料檢測]")
        found_any = False
        
        for data_type, pattern in self.sensitive_patterns.items():
            matches = re.findall(pattern, content)
            if matches:
                found_any = True
                ctx.log.warn(f"  ⚠ 偵測到 {data_type}: {len(matches)} 處")
                for match in matches[:2]:  # 只顯示前兩個
                    masked = match[:3] + '*' * (len(match) - 6) + match[-3:] if len(match) > 6 else '***'
                    ctx.log.warn(f"    - {masked}")
        
        if not found_any:
            ctx.log.info("  ✓ 未偵測到已知敏感資料模式")

# mitmproxy 插件註冊
addons = [
    HTTPSTrafficAnalyzer()
]

使用 mitmproxy 進行流量分析時,需要在客戶端安裝 mitmproxy 的 CA 憑證。這個過程模擬了企業環境中部署透明 SSL 代理的場景,管理員需要在所有客戶端設備上安裝企業 CA 憑證。

這種中間人分析技術在安全測試與合規稽核中有其價值,但也帶來了隱私與信任的問題。在台灣的企業環境中,部署 SSL 攔截代理時需要明確告知員工,並確保符合個資法與勞動法規的要求。

總結與安全最佳實務

SSL/TLS 協定為網際網路通訊提供了基礎的安全保障,但正確的實作與配置同樣重要。從協定版本選擇到密碼套件配置,從憑證管理到金鑰保護,每個環節都可能影響整體的安全性。

在台灣的資安環境中,隨著資安法的實施與產業合規要求的提升,SSL/TLS 的正確部署已成為企業的基本責任。定期進行安全掃描、及時更新憑證、禁用過時的協定版本與密碼套件,這些最佳實務應該成為標準作業流程的一部分。

展望未來,後量子密碼學的發展將為 SSL/TLS 帶來新的挑戰與機遇。美國國家標準技術研究院已公布了後量子加密標準,業界正積極研究如何將這些演算法整合到現有的 TLS 框架中。在量子計算威脅成為現實之前,提前規劃與準備將是明智之舉。持續學習與實踐,掌握 SSL/TLS 的最新發展與最佳實務,將有助於建構更安全可靠的網路環境。