在 Python 中,cryptography 套件提供了 Fernet 模組,方便地實作對稱式加密。Fernet 使用 AES-128-CBC 演算法加密資料,並自動處理初始向量(IV)和訊息驗證碼(HMAC),確保資料的機密性和完整性。而 MultiFernet 則更進一步,允許使用多個 Fernet 金鑰,實作金鑰輪替,提升安全性。當需要更新金鑰時,MultiFernet 可以使用新金鑰重新加密資料,同時仍然能夠解密使用舊金鑰加密的資料,直到所有資料都完成金鑰更新。這個機制可以避免在金鑰輪替過程中服務中斷,並確保資料在過渡期間的安全。

Fernet 的工作原理

Fernet 的 encrypt 方法不僅加密明文,也對密鑰進行雜湊處理,使用HMAC-SHA256演算法。這樣產生的密鑰和雜湊值一起被稱為Fernet令牌。當需要解密時,Fernet 的 decrypt 方法會先驗證密鑰的雜湊值,如果驗證失敗,則丟擲異常;如果驗證成功,則進行解密並傳回明文。

金鑰輪替

金鑰輪替是指退役舊金鑰並用新金鑰取代的過程。為了退役舊金鑰,所有使用舊金鑰加密的密鑰都需要被解密並重新加密使用新金鑰。金鑰可能需要因為多種原因而被輪替,例如金鑰被洩露或是人員變動。

Fernet 類別提供了與 MultiFernet 類別結合的金鑰輪替功能。首先,建立兩個 Fernet 例項,一個使用舊金鑰,另一個使用新金鑰。然後,使用這兩個 Fernet 例項建立一個 MultiFernet 例項。MultiFernet 的 rotate 方法可以將使用舊金鑰加密的密鑰解密並重新加密使用新金鑰。一旦所有密鑰都被重新加密,舊金鑰就可以安全地退役了。

以下是使用 MultiFernet 進行金鑰輪替的示例:

from cryptography.fernet import Fernet, MultiFernet

# 載入舊金鑰
old_key = read_key_from_somewhere_safe()

# 建立舊金鑰的 Fernet 例項
old_fernet = Fernet(old_key)

# 生成新金鑰
new_key = Fernet.generate_key()

# 建立新金鑰的 Fernet 例項
new_fernet = Fernet(new_key)

# 建立 MultiFernet 例項
multi_fernet = MultiFernet([new_fernet, old_fernet])

# 載入需要輪替的密鑰
old_tokens = read_tokens_from_somewhere_safe()

# 進行金鑰輪替
new_tokens = [multi_fernet.rotate(t) for t in old_tokens]

這樣,舊金鑰就可以安全地退役,所有密鑰都被重新加密使用新金鑰。這個過程確保了資料的安全性和完整性。

圖表翻譯:

  flowchart TD
    A[開始] --> B[載入舊金鑰]
    B --> C[建立舊金鑰的 Fernet 例項]
    C --> D[生成新金鑰]
    D --> E[建立新金鑰的 Fernet 例項]
    E --> F[建立 MultiFernet 例項]
    F --> G[載入需要輪替的密鑰]
    G --> H[進行金鑰輪替]
    H --> I[完成]

內容解密:

上述程式碼展示瞭如何使用 MultiFernet 進行金鑰輪替。首先,載入舊金鑰和需要輪替的密鑰。然後,建立舊金鑰和新金鑰的 Fernet 例項,並使用這兩個例項建立一個 MultiFernet 例項。最後,使用 MultiFernet 的 rotate 方法進行金鑰輪替。這個過程確保了資料的安全性和完整性。

對稱式加密

對稱式加密是一種使用相同金鑰進行加密和解密的加密演算法。這類演算法可以進一步分為兩種:分組加密和串流加密。分組加密將明文分成固定長度的分組,然後對每個分組進行加密。分組大小取決於加密演算法,較大的分組大小通常被認為更安全。

分組加密

分組加密是一種將明文分成固定長度的分組,然後對每個分組進行加密的過程。每個明文分組被加密成一個密鑰分組。下面是一些流行的分組加密演算法:

  • 三重DES(Triple DES)
  • Blowfish
  • Twofish
  • 高階加密標準(AES)

三重DES

三重DES是一種根據資料加密標準(DES)的改進演算法。由於其名稱所示,三重DES在內部使用DES三次,因此被認為是一種速度較慢的演算法。三重DES使用64位元的分組大小和56、112或168位元的金鑰大小。

警告: 三重DES已被棄用,不建議使用。

Blowfish

Blowfish是在1990年代初期由Bruce Schneier開發的一種演算法。Blowfish使用64位元的分組大小和32到448位元的可變金鑰大小。Blowfish曾經是一種流行的免版稅加密演算法,但在2016年,由於其分組大小導致的一種稱為SWEET32的攻擊,使其不再被推薦使用。

警告: Blowfish已不再被推薦使用,甚至其創造者也建議使用Twofish取代。

Twofish

Twofish是Blowfish的後繼者,同樣由Bruce Schneier開發。Twofish使用128位元的分組大小和128、192或256位元的金鑰大小。Twofish被設計為一種更安全、更快速的加密演算法。

高階加密標準(AES)

AES是一種廣泛使用的對稱式加密演算法,被認為是最安全的加密演算法之一。AES使用128位元的分組大小和128、192或256位元的金鑰大小。

串流加密

串流加密是一種使用不同的金鑰對每個明文位元進行加密的過程。串流加密通常比分組加密更快,但也更容易受到攻擊。

對稱式加密演算法

對稱式加密演算法使用相同的金鑰進行加密和解密。這類演算法可以分為兩大類:分塊加密(Block Ciphers)和流加密(Stream Ciphers)。

分塊加密

分塊加密演算法將明文分成固定大小的塊進行加密。每個塊的大小通常為 128 位元組。分塊加密演算法的優點是可以高效地加密大資料,但也存在一些缺點,例如需要填充(Padding)來確保塊大小的一致性。

Twofish

Twofish 是一種分塊加密演算法,於 1990 年代末期開發。它使用 128 位元組的塊大小和 128、192 或 256 位元組的金鑰大小。Twofish 曾經是 Advanced Encryption Standard(AES)競賽的最終候選者之一。

Advanced Encryption Standard(AES)

AES 是一種廣泛使用的分塊加密演算法,於 2000 年被選為 AES 競賽的優勝者。它使用 128 位元組的塊大小和 128、192 或 256 位元組的金鑰大小。AES 是目前最安全的分塊加密演算法之一,其安全記錄非常良好。

流加密

流加密演算法不將明文分成塊進行加密,而是將明文視為一個個別的位元組流進行加密。流加密演算法的優點是可以高效地加密小資料,並且不需要填充。

RC4

RC4 是一種流加密演算法,曾經被廣泛使用於網路協定中。然而,RC4 已經被發現有多個漏洞,並且不再被推薦使用。

ChaCha

ChaCha 是一種流加密演算法,被認為是安全且快速的。它將在第 6 章中被提及,該章節將涵蓋 TLS(Transport Layer Security)協定。

加密模式

對稱式加密演算法可以在不同的模式下執行,每個模式都有其優缺點。加密模式的選擇通常取決於具體的應用需求。

Electronic Codebook(ECB)模式

ECB 模式是最簡單的加密模式。它使用相同的金鑰對每個塊進行加密。以下是使用 AES 在 ECB 模式下的加密範例:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import (
    Cipher, algorithms, modes)

# 建立一個 128 位元組的金鑰
key = b'\x00' * 16

# 建立一個 AES-ECB 模式的加密器
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=default_backend())

# 加密明文
plaintext = b'Hello, World!'
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()

print(ciphertext)

其他加密模式

還有其他幾種加密模式,例如 CBC(Cipher Block Chaining)模式、CFB(Cipher Feedback)模式和 OFB(Output Feedback)模式等。每個模式都有其優缺點,應用開發者需要根據具體需求選擇合適的加密模式。

密碼學中的加密模式:ECB 和 CBC

在密碼學中,加密模式是指用於將明文(plaintext)轉換為密鑰(ciphertext)的方法。其中,ECB(Electronic Codebook)模式和 CBC(Cipher Block Chaining)模式是兩種常見的加密模式。

ECB 模式

ECB 模式是一種簡單的加密模式,它將明文分成固定大小的塊(block),然後使用相同的金鑰(key)對每個塊進行加密。這種模式的優點是簡單易於實作,但它也有著嚴重的安全性問題。

ECB 模式的安全性問題在於,它會將相同的明文塊加密成相同的密鑰塊。這意味著,如果攻擊者獲得了密鑰,他可以透過分析密鑰中的模式來推斷出明文的內容。例如,如果攻擊者發現兩個密鑰塊是相同的,他就可以推斷出相應的明文塊也是相同的。

CBC 模式

CBC 模式是一種更安全的加密模式,它可以克服 ECB 模式的安全性問題。CBC 模式也將明文分成固定大小的塊,但它在加密每個塊之前,會先將前一個塊的密鑰與當前塊的明文進行 XOR 運算。這樣可以確保,即使兩個明文塊是相同的,它們的密鑰塊也會是不同的。

CBC 模式需要一個初始向量(IV),它是一個隨機生成的資料,用於初始化加密過程。IV 的長度通常與金鑰的長度相同。

實作 CBC 模式

以下是使用 Python 實作 CBC 模式的例子:

import secrets
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import (
    Cipher, algorithms, modes)

# 生成一個隨機的 IV
iv = secrets.token_bytes(16)

# 建立一個 AES-CBC 密碼器
cipher = Cipher(algorithms.AES(b'key'), modes.CBC(iv), backend=default_backend())

# 加密兩個相同的明文塊
plaintext1 = b'Hello, World!'
plaintext2 = b'Hello, World!'

encryptor = cipher.encryptor()
ciphertext1 = encryptor.update(plaintext1) + encryptor.finalize()
encryptor = cipher.encryptor()
ciphertext2 = encryptor.update(plaintext2) + encryptor.finalize()

print(ciphertext1)
print(ciphertext2)

在這個例子中,我們使用了 secrets 模組生成了一個隨機的 IV,然後建立了一個 AES-CBC 密碼器。接著,我們加密了兩個相同的明文塊,使用相同的金鑰和 IV。由於 CBC 模式的特性,兩個密鑰塊是不同的。

加密技術:保密性的基本

加密是保護資料保密性的重要手段,透過將明文轉換為密鑰,從而防止未經授權的存取。加密技術的核心是使用金鑰和演算法來實作資料的轉換。

AES 加密演算法

AES(Advanced Encryption Standard)是一種廣泛使用的對稱加密演算法,支援 128、196 和 256 位的金鑰長度。AES 的工作模式包括 ECB、CBC、GCM 等,其中 CBC 模式需要一個初始化向量(IV)來確保加密的隨機性。

初始化向量(IV)

IV 是一個隨機生成的位元組序列,用於初始化加密過程。IV 的長度通常與加密演算法的分組大小相同,例如 AES 的分組大小為 16 個位元組。IV 的作用是確保即使對相同的明文進行加密,也會產生不同的密鑰。

Fernet 加密

Fernet 是一個根據 AES 的加密系統,自動生成和管理 IV,無需使用者手動干預。Fernet 將 IV、密鑰和雜湊值封裝在一起,形成一個令牌,從而簡化了加密和解密的過程。

加密模式

AES 支援多種加密模式,包括 ECB、CBC、GCM 等。GCM 模式允許 AES 像流加密器一樣工作,從而提供更高的安全性和效率。

加密的重要性

加密是保護資料保密性的基本,透過使用金鑰和演算法來實作資料的轉換。加密技術的核心是確保資料的安全性和完整性,從而防止未經授權的存取和篡改。

實際應用

在實際應用中,需要注意 IV 的生成和管理,確保 IV 的隨機性和唯一性。同時,需要選擇合適的加密演算法和模式,根據具體的需求和限制。

程式碼實作

以下是使用 Python 實作 AES 加密的例子:

from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os

def encrypt(data, key):
    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(128).padder()
    padded_data = padder.update(data) + padder.finalize()
    return iv + encryptor.update(padded_data) + encryptor.finalize()

key = b'\x00' * 32  # 256 位金鑰
data = b'the same message' * 2
encrypted_data = encrypt(data, key)
print(encrypted_data)

這個例子使用 AES-256-CBC 進行加密,生成一個隨機的 IV,並將其封裝在密鑰中。

5. 不對稱加密和數字簽章

在前一章中,我們探討瞭如何使用對稱加密來確保資料的機密性。然而,對稱加密並不是萬能的。其中一個主要的限制是金鑰分發問題。當傳送者和接收者是同一方時,對稱加密工作得很好,但當需要將加密的資料傳送給多個接收者時,金鑰分發就變得非常困難。

5.1 金鑰分發問題

假設 Alice 想要將一條機密的訊息傳送給 Bob。Alice 將訊息加密並將密鑰傳送給 Bob。但是,Bob 需要 Alice 的金鑰來解密訊息。現在,Alice 需要找到一個方法將金鑰分發給 Bob,而不讓 Eve 這個竊聽者擷取金鑰。Alice 可以使用第二個金鑰來加密她的金鑰,但她如何安全地將第二個金鑰傳送給 Bob?這個問題是遞迴的。

如果 Alice 想要將訊息傳送給 10 個人,比如 Bob,情況就會更加糟糕。即使 Alice 將金鑰物理地分發給所有方,如果 Eve 從其中一人獲得金鑰,Alice 就需要重複工作。金鑰旋轉的機率和成本將增加十倍。或者,Alice 可以為每個人管理一個不同的金鑰,但這將是更多的工作。這個金鑰分發問題是非對稱加密的主要動機之一。

5.2 非對稱加密

非對稱加密使用一對金鑰:公鑰和私鑰。公鑰用於加密資料,而私鑰用於解密資料。這樣,Alice 就可以使用 Bob 的公鑰加密訊息,並將密鑰傳送給 Bob。Bob 然後可以使用他的私鑰解密訊息。

非對稱加密的優點是它可以解決金鑰分發問題。Alice 不需要將她的金鑰傳送給 Bob,而是使用 Bob 的公鑰加密訊息。這樣,Eve 就無法擷取金鑰,因為她沒有 Bob 的私鑰。

5.3 數字簽章

數字簽章是一種使用非對稱加密的技術,用於確保資料的真實性和完整性。數字簽章使用傳送者的私鑰生成一個雜湊值,該雜湊值可以使用傳送者的公鑰驗證。

數字簽章的優點是它可以確保資料的真實性和完整性。如果資料被竄改,則數字簽章將無法驗證。這樣,接收者就可以確保資料是由傳送者傳送的,並且資料在傳輸過程中沒有被竄改。

圖表翻譯:

此圖示非對稱加密和數字簽章的流程。首先,生成一對金鑰:公鑰和私鑰。然後,使用公鑰加密資料,並將密鑰傳送給接收者。接收者使用私鑰解密資料。數字簽章使用傳送者的私鑰生成一個雜湊值,該雜湊值可以使用傳送者的公鑰驗證。

5.2 非對稱加密

非對稱加密是一種使用不同金鑰進行加密和解密的演算法。這種加密方式使用一對金鑰,分別為私鑰和公鑰。私鑰由玄貓保密,而公鑰則公開分發給任何人。私鑰可以解密公鑰加密的內容,反之亦然。

非對稱加密是一種解決金鑰分配問題的經典方案。假設Alice想要安全地將機密訊息傳送給Bob,Bob可以生成一對金鑰,保密私鑰並公開公鑰。Alice使用Bob的公鑰加密訊息,並公開傳送密鑰給Bob。Bob收到密鑰後,使用私鑰解密。

這個方案解決了兩個問題。首先,金鑰分配問題得以解決。如果Eve獲得Bob的公鑰和Alice的密鑰,仍然無法解密訊息。只有Bob的私鑰才能解密公鑰加密的內容。其次,這個方案具有可擴充套件性。如果Alice想要傳送訊息給10個人,每個人只需要生成自己的金鑰對即可。如果Eve攻陷了一個人的私鑰,其他參與者不會受到影響。

5.2.1 RSA公鑰加密

RSA是一種經典的非對稱加密演算法,已經經受住了時間的考驗。這個公鑰密碼系統是在1970年代末期由玄貓、Adi Shamir和Leonard Adleman開發的。

以下命令示範如何使用OpenSSL生成3072位元的RSA私鑰:

$ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:3072

注意RSA金鑰和AES金鑰的大小差異。RSA金鑰需要比AES金鑰大得多,以達到可比的強度。例如,AES金鑰的最大大小是256位元,而RSA金鑰的大小需要更大。

以下命令示範如何從私鑰檔案中提取RSA公鑰:

$ openssl rsa -pubout -in private_key.pem -out public_key.pem

私鑰和公鑰有時會儲存在檔案系統中。管理這些檔案的存取許可權非常重要。私鑰檔案不應該被任何人讀取或寫入,除了所有者。公鑰檔案可以被任何人讀取。

OpenSSL將金鑰序列化到磁碟上,使用一種稱為Privacy-Enhanced Mail (PEM)的格式。PEM是編碼金鑰對的 facto 標準方式。

以下是使用cryptography套件生成金鑰的示範:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa

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

private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

public_pem = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

內容解密:

上述程式碼使用cryptography套件生成了一對RSA金鑰,包括私鑰和公鑰。私鑰使用PKCS#8格式編碼,公鑰使用SubjectPublicKeyInfo格式編碼。這些編碼格式是PEM格式的一部分,可以用於儲存和交換金鑰。

RSA金鑰生成和序列化

在實作安全的加密系統時,RSA金鑰對的生成和序列化是一個重要的步驟。以下是使用Python和cryptography函式庫來生成和序列化RSA金鑰對的過程。

金鑰生成

首先,我們需要生成一個RSA金鑰對。這可以使用rsa.generate_private_key()函式來完成。這個函式需要一些引數,包括公鑰的指數、金鑰的大小和後端。

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend

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

接下來,我們可以從私鑰中提取出公鑰。

public_key = private_key.public_key()

金鑰序列化

生成了金鑰對後,我們需要將其序列化為PEM格式,以便儲存到檔案中。私鑰和公鑰的序列化過程稍有不同。

私鑰序列化

私鑰的序列化需要指定編碼、格式和加密演算法。

private_bytes = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)

然後,我們可以將私鑰寫入檔案中。

with open('private_key.pem', 'xb') as private_file:
    private_file.write(private_bytes)

公鑰序列化

公鑰的序列化相對簡單,只需要指定編碼和格式。

public_bytes = public_key.public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo
)

同樣地,公鑰也可以寫入檔案中。

with open('public_key.pem', 'xb') as public_file:
    public_file.write(public_bytes)

金鑰反序列化

當我們需要使用已經儲存的金鑰對時,需要將其從檔案中反序列化回金鑰物件。

with open('private_key.pem', 'rb') as private_file:
    loaded_private_key = serialization.load_pem_private_key(
        private_file.read(),
        password=None,
        backend=default_backend()
    )

這樣,我們就可以使用反序列化的金鑰對進行加密和解密操作了。

圖表翻譯:

  flowchart TD
    A[生成私鑰] --> B[提取公鑰]
    B --> C[私鑰序列化]
    C --> D[公鑰序列化]
    D --> E[儲存到檔案]
    E --> F[反序列化]
    F --> G[使用金鑰]

內容解密:

上述過程展示瞭如何使用Python和cryptography函式庫來生成、序列化和反序列化RSA金鑰對。這個過程包括了私鑰和公鑰的生成、序列化為PEM格式、儲存到檔案中以及從檔案中反序列化回金鑰物件。這些步驟是實作安全的加密系統的基礎。

非對稱加密:RSA 加密和解密

非對稱加密是一種使用公鑰和私鑰的加密方式,公鑰用於加密,私鑰用於解密。這種加密方式可以確保只有持有私鑰的人才能解密加密的資料。

載入公鑰和私鑰

要使用 RSA 加密和解密,首先需要載入公鑰和私鑰。以下是載入公鑰和私鑰的示例:

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

with open('public_key.pem', 'rb') as public_file:
    loaded_public_key = serialization.load_pem_public_key(
        public_file.read(),
        backend=default_backend()
    )

with open('private_key.pem', 'rb') as private_file:
    loaded_private_key = serialization.load_pem_private_key(
        private_file.read(),
        password=None,
        backend=default_backend()
    )

RSA 加密和解密

要使用 RSA 加密和解密,需要使用 padding.OAEP 來組態加密和解密的 padding 方案。以下是使用 RSA 加密和解密的示例:

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

padding_config = padding.OAEP(
    mgf=padding.MGF1(algorithm=hashes.SHA256()),
    algorithm=hashes.SHA256(),
    label=None
)

plaintext = b'message from Alice to Bob'

ciphertext = loaded_public_key.encrypt(
    plaintext=plaintext,
    padding=padding_config
)

decrypted_by_private_key = loaded_private_key.decrypt(
    ciphertext=ciphertext,
    padding=padding_config
)

Mermaid 圖表:RSA 加密和解密流程

  flowchart TD
    A[載入公鑰] --> B[載入私鑰]
    B --> C[組態 padding 方案]
    C --> D[加密]
    D --> E[解密]
    E --> F[輸出解密結果]

圖表翻譯:

上述 Mermaid 圖表展示了 RSA 加密和解密的流程。首先,需要載入公鑰和私鑰。然後,需要組態 padding 方案。接下來,使用公鑰加密明文資料。最後,使用私鑰解密密鑰資料,並輸出解密結果。

內容解密:

上述程式碼示例展示瞭如何使用 RSA 加密和解密。首先,需要載入公鑰和私鑰。然後,需要組態 padding 方案。接下來,使用公鑰加密明文資料。最後,使用私鑰解密密鑰資料,並輸出解密結果。注意,padding 方案的組態是非常重要的,否則可能會導致加密和解密失敗。

從技術架構視角來看,Fernet 作為一種對稱式加密方案,巧妙地整合了金鑰、IV 和密鑰,簡化了加密流程。透過 MultiFernet 機制,更有效地解決了金鑰輪替的難題,在確保資料安全性的同時,也提升了金鑰管理的效率。然而,Fernet 的對稱式特性也意味著金鑰分發的挑戰依然存在,這也限制了它在大規模多方通訊場景下的應用。對於追求高效能且金鑰管理相對單純的應用情境,Fernet 仍不失為一個值得考慮的方案。展望未來,隨著量子計算的發展,對稱式加密的安全性將面臨新的挑戰,探索更安全的金鑰交換機制和後量子加密演算法將是 Fernet 持續發展的關鍵。玄貓認為,在選擇加密方案時,除了考量效能和易用性外,更需關注其長期安全性和應對未來技術挑戰的能力,才能真正保障資料的機密性。