Python 的 random 模組提供偽隨機數生成,搭配 random.seed() 可固定隨機種子,確保測試一致性。這對於測試加密演算法至關重要,因為我們需要可重複的測試流程。測試程式首先生成隨機訊息,再利用所有可能的金鑰進行加密和解密,最後比對解密結果與原始訊息,驗證演算法的正確性。理解 Python 中變數與串列參考的差異,才能正確操作串列,避免非預期的結果。使用 copy.deepcopy() 或重新建立串列,可以確保操作不會影響原始串列。transpositionTest.py 程式碼範例展示瞭如何結合這些技巧,建構一個完整的置換加密演算法測試程式。

第10章 - 測試我們的程式

在前一章中,我們成功地實作了換位加密和解密程式。為了驗證這些程式的正確性,我們需要進行大量的測試。手動測試這些程式將會非常耗時,因此我們需要編寫一個程式來自動化這個過程。

自動化測試的原理

自動化測試的基本原理是生成隨機的訊息和金鑰,使用 encryptMessage() 函式加密訊息,然後使用 decryptMessage() 函式解密,最後檢查解密後的訊息是否與原始訊息相同。如果所有測試都透過,那麼我們就可以確定我們的程式是正確的。

換位加密測試程式的原始碼

# 換位加密測試
# http://inventwithpython.com/hacking (BSD Licensed)

import random, sys, transpositionEncrypt, transpositionDecrypt

def main():
    random.seed(42)  # 設定隨機種子為固定值

    for i in range(20):  # 執行20次測試
        # 生成隨機訊息進行測試
        message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40)

        # 將訊息字串轉換為列表以進行洗牌
        message = list(message)
        random.shuffle(message)
        message = ''.join(message)  # 將列表轉換回字串

        print('測試 #%s: "%s..."' % (i+1, message[:50]))

        # 對每個訊息檢查所有可能的金鑰
        for key in range(1, len(message)):
            encrypted = transpositionEncrypt.encryptMessage(key, message)
            decrypted = transpositionDecrypt.decryptMessage(key, encrypted)

            # 如果解密結果與原始訊息不符,顯示錯誤訊息並離開
            if message != decrypted:
                print('金鑰 %s 和訊息 %s 不符.' % (key, message))
                print(decrypted)
                sys.exit()

    print('換位加密測試透過.')

# 如果 transpositionTest.py 被執行(而不是被匯入為模組),呼叫 main() 函式
if __name__ == '__main__':
    main()

程式碼解析:

  1. random.seed(42):設定隨機種子為固定值,以確保每次執行的結果一致,便於除錯和驗證。
  2. message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40):生成一個隨機長度的訊息,長度介於4到40倍的 ABCDEFGHIJKLMNOPQRSTUVWXYZ 之間。
  3. random.shuffle(message):將訊息字元順序打亂,生成隨機的訊息內容。
  4. 內層迴圈:對每個生成的訊息,使用不同的金鑰進行加密和解密,並檢查結果是否正確。

測試結果

執行上述程式後,輸出結果如下所示:

測試 #1: "KQDXSFQDBPMMRGXFKCGIQUGWFFLAJIJKFJGSYOSAWGYBGUNTQX..."
測試 #2: "IDDXEEWUMWUJPJSZFJSGAOMFIOWWEYANRXISCJKXZRHMRNCFYW..."
...
測試 #20: "LNKGKSYIPHMCDVKDLNDVFCIFGEWQGUJYJICUYIVXARMUCBNUWM..."
換位加密測試透過.

結果分析:

  • 程式自動生成了20個隨機訊息,並對每個訊息使用不同的金鑰進行了加密和解密測試。
  • 如果所有測試都透過,表示我們的換位加密和解密實作是正確的。
本章重點:
  • 自動化測試的基本原理和實作方法。
  • 使用 random 模組生成隨機輸入進行測試。
  • 透過比較加密和解密結果來驗證程式正確性。

透過這種方式,我們可以確保我們的程式在各種情況下都能正確運作,為進一步的開發和應用奠定堅實的基礎。

測試程式的開發與偽隨機數的應用

在開發加密與解密程式後,我們需要一個測試程式來驗證這些程式的正確性。transpositionTest.py 就是這樣一個程式,它透過匯入 transpositionEncrypt.pytranspositionDecrypt.py 作為模組,呼叫其中的 encryptMessage()decryptMessage() 函式來進行測試。

偽隨機數與 random.seed() 函式

Python 的 random.randint() 函式產生的數字並不是真正隨機的,而是透過偽隨機數生成器演算法產生的可預測數字。這些數字看起來是隨機的,但實際上是可預測的。偽隨機數生成器演算法從一個初始數字(種子)開始,所有由此產生的隨機數都是可預測的。

import random
random.seed(42)  # 設定隨機種子為固定值
for i in range(5):
    print(random.randint(1, 10))

當我們將隨機種子設定為 42 時,每次執行程式產生的前五個「隨機」數字都是相同的。這對於測試程式非常有用,因為它確保每次執行測試時使用相同的偽隨機訊息和金鑰。

為何使用偽隨機數?

雖然使用偽隨機數在加密軟體中是一個常見的安全漏洞,因為它們可以被預測,但對於測試程式的目的來說,這是可接受的。真正的隨機數可以使用 os.urandom() 函式生成,更多資訊可以參考 http://invpy.com/random

使用 random.randint() 生成測試訊息

for i in range(20):  # 執行 20 次測試
    # 生成隨機訊息進行測試
    message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40)

這段程式碼在迴圈中執行多次測試,每次測試生成一個不同長度的隨機訊息。random.randint(4, 40) 生成一個介於 4 和 40 之間的隨機整數,用於決定訊息的重複次數,從而產生不同長度的訊息。

變數與列表參考

技術上來說,變數儲存的不是列表值,而是對列表值的參考。在大多數情況下,這個區別並不重要,但在將帶有列表參考的變數複製到另一個變數時,這個區別就變得重要了。

>>> import random
>>> random.randint(1, 20)
20
>>> random.randint(1, 20)
18

透過使用偽隨機數和控制測試次數,我們可以確保我們的加密和解密程式在多種情況下都能正確運作。

程式碼範例與解析

transpositionTest.py 程式碼片段

import random, sys, transpositionEncrypt, transpositionDecrypt

def main():
    random.seed(42)  # 設定隨機種子
    for i in range(20):  # 執行 20 次測試
        message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40)
        # ...(其他程式碼)

內容解密:

  1. 匯入必要的模組:程式開始時匯入了 randomsystranspositionEncrypttranspositionDecrypt 模組,分別用於生成隨機數、系統相關操作、以及加密和解密功能。
  2. random.seed(42) 的作用:透過設定隨機種子為固定值 42,確保每次執行程式時生成的偽隨機數序列是相同的,這對於重現測試結果非常重要。
  3. 迴圈執行測試:使用 for 迴圈執行 20 次測試,每次生成一個不同長度的隨機訊息,以全面驗證程式的正確性。
  4. 生成隨機訊息:利用 random.randint(4, 40) 生成一個隨機整數,用於決定訊息的重複次數,從而產生不同長度的測試訊息。

透過這種方式,我們可以系統性地測試我們的加密和解密程式,確保它們在各種情況下都能正確運作。

Python 中的變數與串列參考

在 Python 中,變數就像是用來儲存值的容器。當我們將一個值賦給一個變數時,例如 spam = 42,然後再將 spam 的值賦給另一個變數 cheese,即 cheese = spam,此時修改 spam 的值並不會影響 cheese 的值。這是因為 spamcheese 是兩個不同的變數,它們各自儲存自己的值。

串列參考的特殊性

然而,當我們處理串列(list)時,情況就不同了。當我們使用指定陳述式 = 將一個串列賦給一個變數時,我們實際上是將串列的參考(reference)賦給了這個變數。串列參考是一個指向串列的值。

>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = spam
>>> cheese[1] = 'Hello!'
>>> spam
[0, 'Hello!', 2, 3, 4, 5]
>>> cheese
[0, 'Hello!', 2, 3, 4, 5]

內容解密:

  1. spam = [0, 1, 2, 3, 4, 5]:建立了一個串列 [0, 1, 2, 3, 4, 5],並將其參考賦給 spam
  2. cheese = spam:將 spam 中的串列參考複製給 cheese,此時 spamcheese 都指向同一個串列。
  3. cheese[1] = 'Hello!':修改了 cheese 所指向的串列的第二個元素,因為 spamcheese 指向同一個串列,所以 spam 所指向的串列也被修改了。

如何建立獨立的串列

如果我們希望 spamcheese 儲存兩個不同的串列,我們需要建立兩個不同的串列,而不是複製參考:

>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = [0, 1, 2, 3, 4, 5]
>>> cheese[1] = 'Hello!'
>>> spam
[0, 1, 2, 3, 4, 5]
>>> cheese
[0, 'Hello!', 2, 3, 4, 5]

內容解密:

  1. spam = [0, 1, 2, 3, 4, 5]cheese = [0, 1, 2, 3, 4, 5]:建立了兩個內容相同但獨立的串列,並將它們的參考分別賦給 spamcheese
  2. cheese[1] = 'Hello!':修改了 cheese 所指向的串列,但並未影響 spam 所指向的串列。

使用 copy.deepcopy() 複製串列

另一種方法是使用 copy.deepcopy() 函式來建立一個串列的深層複製:

>>> import copy
>>> spam = [0, 1, 2, 3, 4, 5]
>>> cheese = copy.deepcopy(spam)
>>> cheese[1] = 'Hello!'
>>> spam
[0, 1, 2, 3, 4, 5]
>>> cheese
[0, 'Hello!', 2, 3, 4, 5]

內容解密:

  1. import copy:匯入了 copy 模組,用於建立物件的複製。
  2. cheese = copy.deepcopy(spam):建立了 spam 所指向的串列的一個深層複製,並將其參考賦給 cheese。這樣,spamcheese 就指向了兩個完全獨立的串列。
  3. cheese[1] = 'Hello!':修改了 cheese 所指向的串列,但並未影響 spam 所指向的串列。

使用 random.shuffle() 打亂串列

random.shuffle() 是另一個有用的函式,可以用來隨機打亂一個串列中的元素順序:

>>> import random
>>> eggs = list('Hello')
>>> eggs
['H', 'e', 'l', 'l', 'o']
>>> random.shuffle(eggs)
>>> eggs
['o', 'l', 'e', 'H', 'l']

內容解密:

  1. import random:匯入了 random 模組,用於生成隨機數。
  2. eggs = list('Hello'):將字串 'Hello'轉換為一個串列,並賦給 eggs
  3. random.shuffle(eggs):隨機打亂了 eggs 所指向的串列中的元素順序。注意,random.shuffle() 直接修改原串列,而不是傳回一個新的打亂後的串列。

測試程式的開發與驗證

在開發加密與解密程式後,我們需要設計一個測試程式來驗證這些程式的正確性。本章節將詳細介紹如何開發一個測試程式來檢查我們的置換加密(transposition cipher)實作是否正確。

字串處理與隨機化

在測試程式中,我們需要能夠處理字串並隨機打亂其內容。Python 提供了多種方法來達成此目的:

  • 使用 list() 函式將字串轉換為列表。
  • 利用 random.shuffle() 函式來打亂列表中的元素順序。
  • 藉助 ''.join() 方法將列表轉換回字串。
import random

# 原始字串
spam = 'Hello world!'

# 將字串轉換為列表並打亂
spam = list(spam)
random.shuffle(spam)
spam = ''.join(spam)

print(spam)

內容解密:

  1. 字串轉列表list(spam) 將字串 spam 轉換為字元列表,使我們能夠對其進行修改。
  2. 打亂列表元素random.shuffle(spam) 對列表中的字元進行隨機排序。
  3. 列表轉字串''.join(spam) 將打亂後的字元列表合併成一個新的字串。

測試程式的邏輯

我們的測試程式 transpositionTest.py 將對多個不同的訊息進行加密和解密測試,以確保我們的置換加密實作正確無誤。

主要步驟:

  1. 生成測試訊息:使用 random.shuffle() 對原始訊息進行隨機打亂,以產生多個不同的測試訊息。
  2. 遍歷所有可能的金鑰:對每個測試訊息,使用從 1 到訊息長度之間的所有整數金鑰進行加密和解密。
  3. 驗證解密結果:比較解密後的訊息與原始訊息是否一致,若不一致則輸出錯誤訊息並終止程式。
for i in range(20):
    # 生成隨機訊息
    message = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * random.randint(4, 40)
    message = list(message)
    random.shuffle(message)
    message = ''.join(message)

    # 遍歷所有可能的金鑰進行加密和解密
    for key in range(1, len(message)):
        encrypted = transpositionEncrypt.encryptMessage(key, message)
        decrypted = transpositionDecrypt.decryptMessage(key, encrypted)

        # 驗證解密結果
        if message != decrypted:
            print('錯誤:金鑰 %s,訊息 %s' % (key, message))
            print('解密結果:%s' % decrypted)
            sys.exit()

print('置換加密測試透過。')

內容解密:

  1. 迴圈測試:程式執行多輪測試,每輪生成一個隨機的測試訊息。
  2. 金鑰遍歷:對每個測試訊息,程式遍歷所有可能的金鑰進行加密和解密操作。
  3. 結果驗證:若解密結果與原始訊息不一致,程式輸出錯誤訊息並終止。
  4. 成功測試:若所有測試均透過,程式輸出「置換加密測試透過。」

sys.exit() 函式的應用

在測試過程中,若發現錯誤,我們使用 sys.exit() 函式立即終止程式的執行,以避免繼續執行可能導致更多錯誤或混淆的程式碼。

if message != decrypted:
    print('Mismatch with key %s and message %s.' % (key, message))
    print(decrypted)
    sys.exit()

內容解密:

  1. 錯誤檢測:檢查解密結果是否與原始訊息一致。
  2. 錯誤處理:若不一致,輸出相關的錯誤資訊。
  3. 程式終止:使用 sys.exit() 立即終止程式執行。

模組匯入與主函式呼叫

為了使 transpositionTest.py 能夠作為獨立的測試工具或被其他程式匯入,我們使用了一個特殊的變數 __name__ 來控制主函式的呼叫。

if __name__ == '__main__':
    main()

內容解密:

  1. __name__ 變數:當直接執行 transpositionTest.py 時,__name__ 被設為 '__main__'
  2. 主函式呼叫:在這種情況下,程式呼叫 main() 函式開始執行測試。
  3. 模組匯入:若 transpositionTest.py 被其他程式匯入,main() 不會被自動呼叫,避免了不必要的測試執行。

測試程式的驗證

為了確保測試程式本身的正確性,我們可以故意在加密或解密函式中引入錯誤,然後執行測試程式。若測試程式未能檢測到這些錯誤,則表明測試程式存在缺陷,需要進一步改進。

本章節透過介紹置換加密測試程式的開發過程,展示瞭如何設計和驗證一個有效的測試工具,以確保加密和解密功能的正確性。