07-2. SFTP 協定:安全的檔案傳輸

基於 SSH 的加密檔案傳輸協定

🔐 SFTP 協定:安全的檔案傳輸

SFTP 在網路模型中的位置

┌──────────────────────────────────────────────────────────┐
│            OSI 七層模型          TCP/IP 四層模型          │
├──────────────────────────────────────────────────────────┤
│  7. 應用層 (Application)                                 │
│     ├─ SFTP ───────────────┐    應用層 (Application)     │
│                             │    (SFTP, HTTP, FTP...)     │
├─────────────────────────────┤                             │
│  6. 表現層 (Presentation)   │                             │
│     ├─ SSH 加密             │                             │
├─────────────────────────────┤                             │
│  5. 會話層 (Session)        │                             │
│     ├─ SSH Session          │                             │
├─────────────────────────────┼─────────────────────────────┤
│  4. 傳輸層 (Transport)      │    傳輸層 (Transport)       │
│     └─ TCP ─────────────────┘    (TCP)                    │
├─────────────────────────────┼─────────────────────────────┤
│  3. 網路層 (Network)        │    網際網路層 (Internet)    │
│     └─ IP                   │    (IP, ICMP, ARP)          │
├─────────────────────────────┼─────────────────────────────┤
│  2. 資料連結層 (Data Link)  │    網路存取層               │
│  1. 實體層 (Physical)       │    (Network Access)         │
└─────────────────────────────┴─────────────────────────────┘

📍 位置:OSI Layer 7(應用層)/ TCP/IP Layer 4(應用層)
🔌 Port:22(SSH)
🚛 傳輸協定:TCP(透過 SSH 通道)

為什麼 SFTP 用 TCP?

原因說明
檔案完整性 📄檔案傳輸不能有任何遺失或錯誤
順序保證 🔢檔案內容必須按順序接收
可靠傳輸TCP 提供錯誤檢測和重傳機制
SSH 要求 🔐SSH 協定建立在 TCP 之上
流量控制 🎚️大檔案傳輸需要 TCP 的流量控制

💡 重點:SFTP 是透過 SSH 通道傳輸,所有資料都經過加密,確保安全性


🎯 什麼是 SFTP?

💡 比喻:加密的快遞服務

FTP = 明信片(任何人都能看到內容)
SFTP = 密封信件(只有收件人能解開)

FTP:帳號、密碼、檔案內容都是明文
SFTP:全程加密,無法被竊聽

SFTP(SSH File Transfer Protocol) 是一種透過 SSH 協定提供安全檔案傳輸的網路協定。與 FTP 完全不同,SFTP 提供加密的檔案存取、傳輸和管理功能。

為什麼需要 SFTP?

FTP 的安全問題:

# 使用 Wireshark 監聽 FTP
tcpdump -i eth0 port 21 -A

# 可以看到:
USER alice          ← 使用者名稱明文
PASS secret123      ← 密碼明文!
STOR confidential.pdf  ← 檔案名稱明文
(檔案內容也是明文)     ← 可以被竊聽

SFTP 的解決方案:

# 使用 Wireshark 監聽 SFTP
tcpdump -i eth0 port 22 -A

# 只能看到:
&*#@!%^...亂碼...    ← 全部加密
無法知道:
- 使用者是誰
- 密碼是什麼
- 傳輸了什麼檔案

🆚 SFTP vs FTP vs FTPS

特性FTPFTPSSFTP
全名File Transfer ProtocolFTP over SSL/TLSSSH File Transfer Protocol
Port21(控制)+ 20(數據)990 或 2122(單一)
連線數2 個(控制+數據)2 個1 個
加密❌ 無✅ SSL/TLS✅ SSH
認證方式密碼密碼 + 憑證密碼 + 公鑰
防火牆複雜(雙連線)複雜簡單(單連線)
安全性❌ 不安全✅ 安全✅ 安全
命令加密❌ 否✅ 是✅ 是
標準化RFC 959RFC 4217RFC 4251-4254

重要:SFTP ≠ FTPS

SFTP (SSH File Transfer Protocol):
- 完全基於 SSH
- 與 FTP 毫無關係
- 設計理念完全不同
- 推薦使用 ✅

FTPS (FTP over SSL/TLS):
- FTP + SSL/TLS 加密
- 仍是雙連線架構
- 相容性較好但較複雜
- 較少使用 ⚠️

🏗️ SFTP 架構

單一連線設計

客戶端                    SSH Server (Port 22)
  │                              │
  ├──── SSH 連線建立 ───────────>│
  │     1. 金鑰交換               │
  │     2. 加密協商               │
  │     3. 使用者認證             │
  │                              │
  ├──── SSH 通道 (加密) ─────────>│
  │     SFTP 命令 & 資料          │
  │     • ls, get, put           │
  │     • 檔案內容               │
  │     • 所有資料都加密           │
  │                              │
  │<──── 回應 (加密) ─────────────┤

優點:

  • ✅ 單一 Port(22),防火牆設定簡單
  • ✅ 所有資料加密(命令+內容)
  • ✅ 支援多種認證(密碼+公鑰)
  • ✅ 可復用 SSH 連線(效率高)

🔑 SFTP 認證方式

1️⃣ 密碼認證

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 使用密碼登入
ssh.connect(
    hostname='sftp.example.com',
    port=22,
    username='alice',
    password='secret123'
)

sftp = ssh.open_sftp()

問題:

  • ⚠️ 密碼可能被猜測
  • ⚠️ 需要在程式碼中儲存密碼

2️⃣ 公鑰認證(推薦)

💡 比喻:門鎖和鑰匙

私鑰 = 鑰匙(自己保管,不可外洩)
公鑰 = 鎖(可以給別人,放在伺服器上)

登入時:
1. 伺服器用「公鑰」加密一段資料
2. 客戶端用「私鑰」解密
3. 解密成功 = 證明你有私鑰 = 允許登入

生成金鑰對:

# 生成 RSA 金鑰(4096 bits)
ssh-keygen -t rsa -b 4096 -C "alice@example.com"

# 會產生:
# ~/.ssh/id_rsa        ← 私鑰(絕對不可外洩)
# ~/.ssh/id_rsa.pub    ← 公鑰(可以給伺服器)

# 或使用更安全的 Ed25519
ssh-keygen -t ed25519 -C "alice@example.com"
# ~/.ssh/id_ed25519
# ~/.ssh/id_ed25519.pub

安裝公鑰到伺服器:

# 方法 1:使用 ssh-copy-id(推薦)
ssh-copy-id alice@sftp.example.com

# 方法 2:手動複製
cat ~/.ssh/id_rsa.pub | ssh alice@sftp.example.com "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

# 方法 3:直接編輯
# 登入伺服器後:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
# 貼上公鑰內容
chmod 600 ~/.ssh/authorized_keys

Python 使用公鑰:

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 載入私鑰
private_key = paramiko.RSAKey.from_private_key_file(
    '/home/alice/.ssh/id_rsa',
    password='金鑰密碼'  # 如果金鑰有設定密碼
)

# 使用公鑰認證
ssh.connect(
    hostname='sftp.example.com',
    username='alice',
    pkey=private_key
)

sftp = ssh.open_sftp()

💻 Python 使用 SFTP

安裝套件

pip install paramiko

基本連線

import paramiko

# 建立 SSH 客戶端
ssh = paramiko.SSHClient()

# 自動新增主機金鑰(生產環境應驗證)
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 連線
ssh.connect(
    hostname='sftp.example.com',
    port=22,
    username='alice',
    password='password'
)

# 開啟 SFTP 會話
sftp = ssh.open_sftp()

# 列出檔案
files = sftp.listdir('.')
for file in files:
    print(file)

# 關閉連線
sftp.close()
ssh.close()

下載檔案

import paramiko

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('sftp.example.com', username='alice', password='password')

sftp = ssh.open_sftp()

# 簡單下載
sftp.get('/remote/path/file.txt', 'local_file.txt')
print("下載完成")

# 帶進度條
def progress_callback(transferred, total):
    percent = (transferred / total) * 100
    print(f"\r下載進度:{percent:.1f}% ({transferred}/{total} bytes)", end='')

sftp.get(
    '/remote/path/large_file.zip',
    'local_file.zip',
    callback=progress_callback
)

print("\n下載完成")

sftp.close()
ssh.close()

上傳檔案

sftp = ssh.open_sftp()

# 簡單上傳
sftp.put('local_file.txt', '/remote/path/file.txt')

# 帶進度條
def progress_callback(transferred, total):
    percent = (transferred / total) * 100
    bar_length = 50
    filled = int(bar_length * transferred // total)
    bar = '█' * filled + '-' * (bar_length - filled)
    print(f"\r上傳進度:[{bar}] {percent:.1f}%", end='')

sftp.put(
    'large_file.zip',
    '/remote/path/file.zip',
    callback=progress_callback
)

print("\n上傳完成")
sftp.close()

目錄操作

sftp = ssh.open_sftp()

# 列出目錄(詳細資訊)
for attr in sftp.listdir_attr('.'):
    # 檔案類型判斷
    if stat.S_ISDIR(attr.st_mode):
        file_type = "DIR"
    else:
        file_type = "FILE"

    # 格式化大小
    size_mb = attr.st_size / (1024 * 1024)

    print(f"{file_type:4} {attr.filename:30} {size_mb:10.2f} MB")

# 切換目錄
sftp.chdir('/var/www/html')

# 取得當前目錄
current_dir = sftp.getcwd()
print(f"當前目錄:{current_dir}")

# 建立目錄
sftp.mkdir('new_folder')
sftp.mkdir('new_folder', mode=0o755)  # 設定權限

# 刪除目錄(必須為空)
sftp.rmdir('old_folder')

# 刪除檔案
sftp.remove('file.txt')

# 重新命名/移動
sftp.rename('old.txt', 'new.txt')

# 檢查檔案/目錄是否存在
try:
    sftp.stat('file.txt')
    print("檔案存在")
except FileNotFoundError:
    print("檔案不存在")

# 取得檔案資訊
file_stat = sftp.stat('document.pdf')
print(f"大小:{file_stat.st_size} bytes")
print(f"修改時間:{file_stat.st_mtime}")

sftp.close()

斷點續傳

import paramiko
import os

def sftp_resume_download(sftp, remote_file, local_file):
    """SFTP 斷點續傳下載"""

    # 取得遠端檔案大小
    remote_size = sftp.stat(remote_file).st_size

    # 檢查本地檔案
    if os.path.exists(local_file):
        local_size = os.path.getsize(local_file)
    else:
        local_size = 0

    # 已下載完成
    if local_size == remote_size:
        print("檔案已下載完成")
        return

    print(f"已下載:{local_size:,} / {remote_size:,} bytes")
    print(f"剩餘:{remote_size - local_size:,} bytes")

    # 從中斷處繼續
    with open(local_file, 'ab') as local_f:
        with sftp.file(remote_file, 'rb') as remote_f:
            # 跳到中斷位置
            remote_f.seek(local_size)

            # 分塊讀取
            chunk_size = 1024 * 1024  # 1 MB
            while True:
                chunk = remote_f.read(chunk_size)
                if not chunk:
                    break

                local_f.write(chunk)

                # 顯示進度
                current = local_size + local_f.tell()
                percent = (current / remote_size) * 100
                print(f"\r進度:{percent:.1f}%", end='')

    print("\n下載完成")

# 使用
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('sftp.example.com', username='alice', password='password')

sftp = ssh.open_sftp()

# 第一次下載(可能中斷)
try:
    sftp_resume_download(sftp, '/remote/large_file.zip', 'local.zip')
except KeyboardInterrupt:
    print("\n下載中斷")

# 重新執行,自動續傳
sftp_resume_download(sftp, '/remote/large_file.zip', 'local.zip')

sftp.close()
ssh.close()

批次上傳目錄

import paramiko
import os
import stat as stat_module

class SFTPDeployer:
    def __init__(self, hostname, username, password=None, private_key_path=None):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        # 使用私鑰或密碼
        if private_key_path:
            private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
            self.ssh.connect(hostname, username=username, pkey=private_key)
        else:
            self.ssh.connect(hostname, username=username, password=password)

        self.sftp = self.ssh.open_sftp()

    def upload_directory(self, local_dir, remote_dir, exclude=None):
        """遞迴上傳整個目錄"""

        if exclude is None:
            exclude = ['.git', 'node_modules', '__pycache__', '.DS_Store']

        # 確保遠端目錄存在
        try:
            self.sftp.mkdir(remote_dir)
        except IOError:
            pass  # 目錄已存在

        for item in os.listdir(local_dir):
            # 跳過排除的項目
            if item in exclude:
                continue

            local_path = os.path.join(local_dir, item)
            remote_path = f"{remote_dir}/{item}".replace('\\', '/')

            if os.path.isfile(local_path):
                # 上傳檔案
                print(f"上傳:{local_path}{remote_path}")
                self.sftp.put(local_path, remote_path)

            elif os.path.isdir(local_path):
                # 遞迴上傳子目錄
                self.upload_directory(local_path, remote_path, exclude)

    def download_directory(self, remote_dir, local_dir):
        """遞迴下載整個目錄"""

        # 建立本地目錄
        os.makedirs(local_dir, exist_ok=True)

        for item in self.sftp.listdir_attr(remote_dir):
            remote_path = f"{remote_dir}/{item.filename}".replace('\\', '/')
            local_path = os.path.join(local_dir, item.filename)

            if stat_module.S_ISDIR(item.st_mode):
                # 遞迴下載子目錄
                self.download_directory(remote_path, local_path)
            else:
                # 下載檔案
                print(f"下載:{remote_path}{local_path}")
                self.sftp.get(remote_path, local_path)

    def sync_directory(self, local_dir, remote_dir):
        """同步目錄(只上傳修改過的檔案)"""

        for root, dirs, files in os.walk(local_dir):
            # 計算相對路徑
            rel_path = os.path.relpath(root, local_dir)
            if rel_path == '.':
                remote_root = remote_dir
            else:
                remote_root = f"{remote_dir}/{rel_path}".replace('\\', '/')

            # 確保遠端目錄存在
            try:
                self.sftp.mkdir(remote_root)
            except IOError:
                pass

            # 上傳檔案
            for file in files:
                local_file = os.path.join(root, file)
                remote_file = f"{remote_root}/{file}".replace('\\', '/')

                # 檢查是否需要上傳
                upload_needed = False

                try:
                    remote_stat = self.sftp.stat(remote_file)
                    local_mtime = os.path.getmtime(local_file)

                    # 比較修改時間
                    if local_mtime > remote_stat.st_mtime:
                        upload_needed = True
                        reason = "檔案已更新"
                except FileNotFoundError:
                    upload_needed = True
                    reason = "新檔案"

                if upload_needed:
                    print(f"上傳 ({reason}):{local_file}{remote_file}")
                    self.sftp.put(local_file, remote_file)

    def close(self):
        self.sftp.close()
        self.ssh.close()

# 使用範例
deployer = SFTPDeployer(
    hostname='server.example.com',
    username='deploy_user',
    private_key_path='/home/alice/.ssh/id_rsa'
)

# 部署網站
deployer.upload_directory('./dist', '/var/www/html', exclude=['.git', 'node_modules'])

# 同步(只上傳變更的檔案)
deployer.sync_directory('./src', '/var/www/html/src')

deployer.close()

🎓 常見面試題

Q1:SFTP 和 FTP 的核心差異是什麼?

答案:

核心差異:安全性與架構

特性FTPSFTP
基礎協定獨立協定基於 SSH
Port21 + 20(雙連線)22(單連線)
加密❌ 全部明文✅ 全部加密
認證只有密碼密碼 + 公鑰
防火牆複雜簡單

安全性比較:

# FTP(可被竊聽)
# 使用 Wireshark 可以看到:
USER alice         ← 明文
PASS secret123     ← 明文密碼!
STOR file.pdf      ← 檔案名明文
(檔案內容)          ← 內容也是明文

# SFTP(加密)
# 使用 Wireshark 只能看到:
&*#@!%^...         ← 全部亂碼
無法知道任何資訊

什麼時候用哪個?

✅ 使用 SFTP:
- 公網環境(預設選擇)
- 傳輸敏感資料
- 需要強認證
- 現代系統

⚠️ 使用 FTP:
- 內網環境(確保安全)
- 極度要求速度
- 舊系統相容性
- 公開下載站台

Q2:如何設定 SFTP 公鑰認證?

答案:

完整流程:

# 1. 客戶端生成金鑰對
ssh-keygen -t ed25519 -C "alice@example.com"

# 會產生:
# ~/.ssh/id_ed25519      ← 私鑰(不可外洩!)
# ~/.ssh/id_ed25519.pub  ← 公鑰(給伺服器)

# 2. 安裝公鑰到伺服器
ssh-copy-id alice@server.example.com

# 或手動:
cat ~/.ssh/id_ed25519.pub | ssh alice@server.example.com "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

# 3. 測試連線
sftp alice@server.example.com
# 不需要密碼,直接登入成功 ✅

伺服器端設定:

# 編輯 SSH 設定
sudo nano /etc/ssh/sshd_config

# 啟用公鑰認證
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

# (可選)禁用密碼認證(更安全)
PasswordAuthentication no

# 重啟 SSH
sudo systemctl restart sshd

Python 使用公鑰:

private_key = paramiko.RSAKey.from_private_key_file('~/.ssh/id_rsa')
ssh.connect(hostname='server.com', username='alice', pkey=private_key)

為什麼公鑰更安全?

密碼認證:
❌ 可能被暴力破解
❌ 密碼可能外洩
❌ 需要在多處儲存密碼

公鑰認證:
✅ 私鑰不離開本機
✅ 無法暴力破解(數學上不可行)
✅ 可設定不同機器使用不同金鑰
✅ 可隨時撤銷(刪除 authorized_keys)

Q3:如何優化 SFTP 傳輸速度?

答案:

1. 啟用壓縮:

ssh.connect(
    hostname='sftp.example.com',
    username='alice',
    password='password',
    compress=True  # SSH 壓縮
)

2. 調整緩衝區大小:

# 增大讀取緩衝(預設 32KB)
with sftp.file(remote_file, 'rb', bufsize=1024*1024) as f:  # 1 MB
    data = f.read()

3. 平行傳輸多個檔案:

from concurrent.futures import ThreadPoolExecutor
import paramiko

def upload_file(filename):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('server.com', username='alice', password='password')

    sftp = ssh.open_sftp()
    sftp.put(filename, f'/remote/{filename}')
    sftp.close()
    ssh.close()

# 平行上傳
files = ['file1.zip', 'file2.zip', 'file3.zip']
with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(upload_file, files)

4. 使用更快的加密演算法:

# 編輯 SSH 設定,使用 ChaCha20
sudo nano /etc/ssh/sshd_config

Ciphers chacha20-poly1305@openssh.com,aes128-gcm@openssh.com

5. 打包後傳輸:

import tarfile

# 打包
with tarfile.open('archive.tar.gz', 'w:gz') as tar:
    tar.add('large_directory')

# 上傳單一檔案(比傳輸多個小檔案快)
sftp.put('archive.tar.gz', '/remote/archive.tar.gz')

速度比較:

場景:上傳 1000 個小檔案(總共 100 MB)

方法 1:逐一上傳
時間:~5 分鐘 ⏱️

方法 2:打包後上傳
時間:~30 秒 ⚡

節省:90% 時間

Q4:如何限制 SFTP 使用者只能存取特定目錄?

答案:

使用 chroot 限制使用者在特定目錄。

設定步驟:

# 1. 編輯 SSH 設定
sudo nano /etc/ssh/sshd_config

# 2. 在檔案最後加入
Match User sftpuser
    ChrootDirectory /home/sftpuser
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no

# 3. 設定目錄權限(重要!)
sudo chown root:root /home/sftpuser
sudo chmod 755 /home/sftpuser

# 4. 建立可寫入的子目錄
sudo mkdir /home/sftpuser/uploads
sudo chown sftpuser:sftpuser /home/sftpuser/uploads
sudo chmod 755 /home/sftpuser/uploads

# 5. 重啟 SSH
sudo systemctl restart sshd

測試:

# 使用者登入後
sftp sftpuser@server.com

sftp> pwd
/                         ← 使用者看到的「根目錄」
                          ← 實際是 /home/sftpuser

sftp> cd /etc
Couldn't canonicalize: No such file  ← 無法存取其他目錄

sftp> cd uploads
sftp> put file.txt        ← 只能在 uploads 寫入

群組批次設定:

# 建立 SFTP 群組
sudo groupadd sftpusers

# 批次設定
Match Group sftpusers
    ChrootDirectory /var/sftp/%u    # %u = 使用者名稱
    ForceCommand internal-sftp

Q5:SFTP 如何處理大檔案傳輸中斷?

答案:

SFTP 本身不支援自動續傳,需要手動實作。

實作方式:

import paramiko
import os

def sftp_resume_upload(sftp, local_file, remote_file):
    """SFTP 斷點續傳上傳"""

    local_size = os.path.getsize(local_file)

    # 檢查遠端檔案
    try:
        remote_size = sftp.stat(remote_file).st_size
    except FileNotFoundError:
        remote_size = 0

    if remote_size == local_size:
        print("檔案已上傳完成")
        return

    print(f"已上傳:{remote_size:,} / {local_size:,} bytes")

    # 從中斷處繼續
    with open(local_file, 'rb') as local_f:
        # 跳過已上傳的部分
        local_f.seek(remote_size)

        # 開啟遠端檔案(追加模式)
        with sftp.file(remote_file, 'ab') as remote_f:
            chunk_size = 1024 * 1024  # 1 MB

            while True:
                chunk = local_f.read(chunk_size)
                if not chunk:
                    break

                remote_f.write(chunk)

                # 顯示進度
                current = remote_size + remote_f.tell()
                percent = (current / local_size) * 100
                print(f"\r進度:{percent:.1f}%", end='')

    print("\n上傳完成")

# 使用
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('server.com', username='alice', password='password')

sftp = ssh.open_sftp()

# 第一次上傳(可能中斷)
try:
    sftp_resume_upload(sftp, 'large_file.zip', '/remote/file.zip')
except KeyboardInterrupt:
    print("\n上傳中斷")

# 重新執行,自動續傳
sftp_resume_upload(sftp, 'large_file.zip', '/remote/file.zip')

sftp.close()
ssh.close()

另一種方式:使用 rsync over SSH

# rsync 自動支援續傳
rsync -avz -P -e ssh large_file.zip alice@server.com:/remote/

# -a: 保留屬性
# -v: 顯示詳細資訊
# -z: 壓縮傳輸
# -P: 顯示進度 + 續傳

📝 總結

SFTP 是現代檔案傳輸的標準選擇:

  • 安全 🔐:全程 SSH 加密
  • 簡單 🎯:單一 Port(22),防火牆友善
  • 靈活 🔑:支援密碼 + 公鑰認證
  • 可靠 ✅:基於 TCP,檔案完整性保證

記憶口訣:「一安二密三目錄」

  • 一安:單一連線,安全加密
  • 二密:雙認證(密碼 + 公鑰)
  • 三目錄:完整目錄操作(ls, mkdir, rm)

最佳實踐:

✅ 使用公鑰認證(不用密碼)
✅ 限制使用者目錄(chroot)
✅ 記錄所有操作(日誌)
✅ 定期更新 SSH 版本
✅ 禁用 root 登入

🔗 延伸閱讀

  • 上一篇:07-1. FTP 協定
  • 下一篇:07-3. SCP 協定
  • RFC 4251(SSH Protocol Architecture):https://tools.ietf.org/html/rfc4251
  • RFC 4253(SSH Transport Layer):https://tools.ietf.org/html/rfc4253
  • Paramiko 官方文件:https://www.paramiko.org/
0%