在網路應用程式開發中,密碼安全和憑證管理是不可或缺的一環。本文將探討如何使用 Python 的 Passlib 函式庫進行安全的密碼儲存,並深入研究如何建立和管理 TLS 憑證以及使用 Paramiko 進行 SSH 連線。首先,我們將介紹 Passlib 的密碼雜湊機制,包括鹽值的使用和 CryptContext 的應用。接著,將逐步說明如何建立本地憑證授權機構和自簽憑證,並探討如何使用 Python 程式碼實作這些操作。最後,我們將介紹 Paramiko 函式庫,並示範如何使用它進行 SSH 連線和金鑰管理,確保伺服器和使用者端之間的安全通訊。

安全儲存密碼:Passlib 函式庫的使用

安全儲存密碼是一件非常微妙的事情,因為它需要處理那些不遵循密碼最佳實踐的使用者。如果所有密碼都很強壯,並且使用者不會在不同網站重複使用相同的密碼,那麼密碼儲存就會很簡單。然而,人們通常會選擇熵值很低的密碼(例如 “123456” 和 “password” 仍然非常流行),並且他們經常會在多個網站使用相同的密碼。此外,他們還容易受到網路釣魚攻擊和社交工程攻擊,從而將密碼洩露給未經授權的第三方。

正確儲存密碼並不能阻止所有的威脅,但可以減輕很多威脅。Passlib 函式庫是由軟體安全專家編寫的,旨在消除儲存密碼時最明顯的錯誤。密碼永遠不會以明文形式儲存,而是會被雜湊。

雜湊與鹽值

雜湊是指將使用者的密碼透過一個計算相對容易但難以逆向推導的函式進行處理。這意味著即使攻擊者獲得了密碼資料函式庫的存取許可權,也無法還原使用者的密碼並假冒他們。攻擊者可能會嘗試透過嘗試所有可能的密碼組合、對它們進行雜湊處理,並檢查是否與某個密碼相等來獲得原始密碼。為了避免這種情況,會使用特殊的演算法,這些演算法在計算上很難處理。這意味著攻擊者需要使用大量的資源來嘗試許多密碼,因此即使只嘗試了幾百萬個密碼,也需要很長的時間來比較。

此外,攻擊者還可以使用彩虹表預先計算許多常見密碼的雜湊值,並將它們與密碼資料函式庫進行比較。為了避免這種情況,密碼在進行雜湊之前會新增一個隨機的字首(鹽值),然後將鹽值新增到雜湊值之前。當使用者輸入密碼時,會從雜湊值的開頭檢索鹽值,然後對其進行雜湊處理以進行比較。

使用 Passlib

Passlib 函式庫與儲存方式無關,不關心密碼儲存在哪裡。但是,它關心的是能夠更新雜湊密碼,以便在需要時將雜湊密碼更新為新的雜湊方案。雖然 Passlib 支援各種低階介面,但最好使用 CryptContext 的高階介面。

首先,需要決定支援哪些雜湊演算法。並非所有演算法都需要是好的雜湊演算法;如果過去曾經支援過不好的雜湊演算法,那麼它們仍然需要被支援。在這個例子中,選擇 Argon2 作為首選的雜湊演算法,但允許其他幾個選項。

hashes = ["argon2", "pbkdf2_sha256", "md5_crypt", "des_crypt"]
deprecated = ["md5_crypt", "des_crypt"]

內容解密:

  • hashes 列表包含了支援的雜湊演算法。
  • deprecated 列表標記了不建議使用的雜湊演算法。
from passlib.context import CryptContext
ctx = CryptContext(schemes=hashes, deprecated=deprecated)

內容解密:

  • CryptContext 是 Passlib 中的一個重要類別,用於管理密碼雜湊。
  • schemes 引數指定了支援的雜湊演算法。
  • deprecated 引數標記了不建議使用的雜湊演算法。

當使用者建立或更改密碼時,需要使用 hash 方法對密碼進行雜湊處理後再儲存。

res = ctx.hash("good password")

內容解密:

  • ctx.hash 方法用於對密碼進行雜湊處理。
  • 這裡使用了一個示例密碼 “good password”。

在使用者登入時,需要檢索儲存在資料函式庫中的雜湊值,並檢查使用者輸入的密碼是否匹配。同時,也需要檢查是否需要更新雜湊值。

ctx.verify_and_update("good password", res)

內容解密:

  • verify_and_update 方法用於驗證使用者輸入的密碼是否與儲存在資料函式庫中的雜湊值匹配。
  • 如果匹配,並且使用的雜湊演算法已經被標記為不建議使用,那麼該方法還會傳回新的雜湊值。

建立本地憑證授權機構與TLS憑證管理

在現代微服務架構中,確保服務間的通訊安全至關重要。Transport Layer Security(TLS)提供了一種加密方式來保護傳輸中的資料,而憑證授權機構(Certificate Authority, CA)則負責簽署公鑰以驗證端點的正確性。

為何需要本地憑證授權機構?

  1. 微服務架構:在微服務架構中,驗證每個服務的真實性可以提高整體的安全性。
  2. 內部測試環境:在內部測試環境中,使用本地CA可以避免使用真實CA的麻煩,同時確保測試環境的真實性。

手動建立憑證

建立憑證需要使用到cryptography函式庫中的hazmat層,這是一個較底層的介面,需要謹慎選擇加密演算法和引數。

步驟1:匯入必要的模組

首先,需要匯入必要的模組,包括default_backendrsax509等。

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
import datetime

步驟2:產生私鑰和公鑰

使用rsa.generate_private_key函式產生一個私鑰,並從中計算出公鑰。

private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
public_key = private_key.public_key()

#### 內容解密:

此段程式碼的作用是產生一個2048位的RSA私鑰,並計算出對應的公鑰。其中,public_exponent引數通常設為65537,這是一個常用的值;key_size引數決定了金鑰的長度,2048位目前被認為是安全的。

建立憑證

使用x509.CertificateBuilder來建立一個憑證,並新增相關的屬性。

builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),
]))
builder = builder.issuer_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'Simple Test CA'),
]))

#### 內容解密:

這段程式碼建立了一個憑證建構器,並設定了憑證的主體名稱和發行者名稱。在這個例子中,兩者相同,因為這是一個自簽憑證。

設定憑證的有效期

one_day = datetime.timedelta(days=1)
today = datetime.datetime.now()
yesterday = today - one_day
next_month = today + (30 * one_day)

builder = builder.not_valid_before(yesterday)
builder = builder.not_valid_after(next_month)

#### 內容解密:

這裡設定了憑證的有效期,從昨天開始到三十天後結束。這樣可以確保憑證在大多數系統上都是有效的。

新增公鑰和擴充套件屬性

builder = builder.serial_number(x509.random_serial_number())
builder = builder.public_key(public_key)
builder = builder.add_extension(
    x509.BasicConstraints(ca=True, path_length=None),
    critical=True)

#### 內容解密:

這段程式碼為憑證新增了一個隨機的序號、公鑰和一個基本限制擴充套件,標記這個憑證為CA憑證。

憑證管理與Paramiko簡介

在前面的章節中,我們探討了使用Python進行密碼學操作的方法,包括如何生成私鑰和自簽憑證。在本章節中,我們將繼續討論憑證管理的相關內容,並介紹Paramiko,一個用於實作SSH協定的Python函式庫。

生成私鑰與自簽憑證的回顧

首先,讓我們快速回顧一下如何使用Python的cryptography函式庫生成私鑰和自簽憑證。我們使用rsa.generate_private_key函式生成私鑰,然後使用x509.CertificateBuilder建立自簽憑證。

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes

# 生成私鑰
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)

# 建立自簽憑證
builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'ca.test.local')
]))
builder = builder.issuer_name(x509.Name([
    x509.NameAttribute(NameOID.COMMON_NAME, 'ca.test.local')
]))
builder = builder.not_valid_before(yesterday)
builder = builder.not_valid_after(next_month)
builder = builder.public_key(private_key.public_key())
builder = builder.serial_number(x509.random_serial_number())
builder = builder.add_extension(
    x509.SubjectAlternativeName([x509.DNSName('ca.test.local')]),
    critical=False,
)
certificate = builder.sign(
    private_key=private_key, algorithm=hashes.SHA256(),
    backend=default_backend()
)

內容解密:

  1. 使用rsa.generate_private_key生成2048位元的RSA私鑰。
  2. 使用x509.CertificateBuilder建立X.509憑證。
  3. 設定憑證的主體名稱和發行者名稱為ca.test.local
  4. 設定憑證的有效期從昨天到下個月。
  5. 將私鑰對應的公鑰加入憑證中。
  6. 使用私鑰對憑證進行簽署。

儲存私鑰與憑證

生成的私鑰和憑證需要儲存到檔案中。我們使用PEM格式,因為它支援簡單的串接。

private_bytes = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.TraditionalOpenSSL,
    encryption_algorithm=serialization.NoEncryption()
)
public_bytes = certificate.public_bytes(
    encoding=serialization.Encoding.PEM
)
with open("ca.pem", "wb") as fout:
    fout.write(private_bytes + public_bytes)
with open("ca.crt", "wb") as fout:
    fout.write(public_bytes)

內容解密:

  1. 將私鑰和憑證分別轉換為PEM格式的位元組串。
  2. 將私鑰和憑證的PEM格式位元組串寫入ca.pem檔案中。
  3. 將憑證的PEM格式位元組串寫入ca.crt檔案中。

使用Paramiko進行SSH操作

Paramiko是一個實作SSHv2協定的Python函式庫,提供了客戶端和伺服器功能。它允許Python程式安全地存取遠端伺服器。

安裝Paramiko

您可以使用pip安裝Paramiko:

pip install paramiko

使用Paramiko連線SSH伺服器

import paramiko

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

# 自動新增主機金鑰
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 連線到SSH伺服器
ssh_client.connect(hostname='example.com', username='username', password='password')

# 執行命令
stdin, stdout, stderr = ssh_client.exec_command('ls -l')

# 輸出結果
print(stdout.read().decode())

# 關閉連線
ssh_client.close()

內容解密:

  1. 建立一個SSH客戶端物件。
  2. 設定自動新增主機金鑰的策略。
  3. 使用主機名、使用者名稱和密碼連線到SSH伺服器。
  4. 執行遠端命令並取得輸出結果。
  5. 關閉SSH連線。

SSH 安全連線與金鑰管理

SSH 是一種安全遠端控制和組態主機的協定,但其安全性取決於正確的使用方式。瞭解 SSH 的安全機制對於確保連線的安全至關重要。SSH 協定建立了客戶端和伺服器之間的相互信任關係,主要透過公鑰方法實作。

伺服器身份驗證

伺服器的公鑰由指紋(fingerprint)識別,該指紋透過安全通道事先傳達或在首次連線時儲存於本地。指紋是伺服器公鑰的雜湊值,若指紋匹配,證明公鑰相同。伺服器可證明其擁有與公鑰對應的私鑰,從而確認其身份。

指紋驗證機制

  1. 預先建立的安全通道:例如,AWS EC2 伺服器啟動時會將指紋列印到虛擬控制檯,可透過 AWS API 呼叫檢索。
  2. Trust On First Use (TOFU) 模型:首次連線時假設指紋真實並儲存,後續連線時檢查指紋是否匹配。

使用者端金鑰

使用者端私鑰和公鑰通常儲存在相鄰的檔案中。可以使用 Paramiko 產生金鑰,例如使用 Elliptic Curve Digital Signature Algorithm (ECDSA) 金鑰。

from paramiko import ecdsakey
k = ecdsakey.ECDSAKey.generate()
public_key = k.get_base64()

with open("key.pub", "w") as fp:
    fp.write(public_key)

import os
with open("key.priv", "w") as fp:
    os.chmod("key.priv", 0o600)
    k.write_private_key(fp)

內容解密:

  • 使用 ECDSAKey.generate() 方法生成 ECDSA 金鑰對。
  • get_base64() 方法取得公鑰的 Base64 編碼。
  • 將公鑰寫入 key.pub 檔案,無需保護。
  • 將私鑰寫入 key.priv 檔案,並設定檔案許可權為 0o600,確保只有擁有者可讀寫。

主機身份

TOFU 原則是 SSH 防禦中間人攻擊的第一道防線。連線到主機後,其指紋需儲存在快取中。現代環境中,快取位置可能不固定,因此 Paramiko 提供了一些工具來處理。

使用 MissingHostKeyPolicy

客戶端可設定 MissingHostKeyPolicy 例項,以支援特定介面,實作記錄金鑰或查詢外部資料函式庫的邏輯。

# 範例:自定義 MissingHostKeyPolicy
class CustomMissingHostKeyPolicy(paramiko.MissingHostKeyPolicy):
    def missing_host_key(self, client, hostname, key):
        # 自定義邏輯,例如記錄或查詢外部資料函式庫
        pass

內容解密:

  • 自定義 MissingHostKeyPolicy 以處理未知主機金鑰。
  • 可實作記錄、查詢或拒絕等邏輯。

連線建立

Paramiko 推薦使用 SSHClient 高階介面建立連線。由於其例項需要關閉,建議使用 contextlib.closing 作為上下文管理器。

import paramiko
from contextlib import closing

with closing(paramiko.SSHClient()) as client:
    # 建立連線
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    client.connect('hostname', username='username', key_filename='path/to/key')
    # 執行命令或傳輸檔案

內容解密:

  • 使用 SSHClient 建立 SSH 連線。
  • 設定 MissingHostKeyPolicy 以處理主機金鑰驗證。
  • 使用 connect 方法連線到遠端主機,並指定使用者名稱和金鑰檔案。