CS50 Week2 - Arrays 中文翻譯 凱薩加密法

CS50 Week2 - Arrays 中文翻譯 凱薩加密法

在這次題目,我們要實作一個程式透過凱薩密碼來加密訊息像是,底下所示。

$ ./caesar 13
plaintext:  HELLO
ciphertext: URYYB

背景

傳說是凱薩使用的加密法用於隱藏機密訊息,透過調整字母原本的位置。
舉例來說,他可能會將 A 寫成 B,B 寫成 C,C 寫成 D ...
並照字母順序循環,Z 寫成 A。
以此類推,凱薩的 HELLO 可能會寫成 IFMMP。
然後接到凱薩訊息的人就要將他們"解密",
透過將字母從相反的方向倒回原本的字。

這個"加密系統"的秘訣是基於只有凱薩跟接收者才知道的秘密,
就是凱薩會轉移字母幾碼(這邊是 1)。
在現代社會應該不是很安全,但是如果你是全世界第一個做這件事的人,那超安全的。

未加密的文字通常被稱作 plaintext 原文。
加密過的文字被叫做 ciphertext 密文。
然後秘密就被稱作 key 金鑰。

為了更清楚釐清,這邊有加密過的 HELLO 跟 被動了 1 碼的 IFMMP

plaintextHELLO
+ key11111
= ciphertextIFMMP

更正式的說法,
凱薩演算法 (cipher) 加密訊息是透過分別轉移每個字 k 個位置。
更正式一點,
假設 p 是 plaintext (未被加密的訊息),pi 是 p 之中的第 i 個字母,
然後 k 是 秘密金鑰 (非零的整數),
則每個字母 ci 作為 ciphertext , c 可以透過以下算出

ci = (pi + k) % 26

在這邊的 %26 指的是被 26 除之後的餘數。
這段公式也許讓這段加密比他看上去更複雜,但他只是用更精簡的方式解釋了這個演算法而已。
延續上面討論,
把 A (或 a) 當作 0, B (或 b) 當作 1 ...
H (或 h) 當作 7,I (或 i) 當作 8 ... Z (或 z) 當作 25。
假設凱薩想要機密的說聲 Hi,而這次 key 是 3。
所以他的 plaintext p 就是 Hi
而他的第一個字母 p0 就是 H (aka 7),
然後第二個字母 p1 就是 i (aka 8)。
他的 ciphertext 第一個字 c0 就是 K
而他的 ciphertext 第二個字 c1 就是 L

我們來寫個程式叫 caesar 它可以讓你透過 凱薩密碼加密訊息。
當使用者執行這段程式時,他們可以透過 command-line 參數決定加密訊息中的 key 。
我們不會特別預設使用者的 key 一定是數字,
所以你必須去判斷,是否是數字且為正整數。

這邊有些範例顯示程式可能的運作方式。
例如,假設使用者輸入 key 是 1 且 plaintext 是 HELLO

$ ./caesar 1
plaintext:  HELLO
ciphertext: IFMMP

這裏表示程式在 key 為 13 的時候跟 plaintext 為 hello, world 時的運作:

$ ./caesar 13
plaintext:  hello, world
ciphertext: uryyb, jbeyq

注意到逗號並沒有被加密法轉移,只有轉移 alphabetical。
還有更多嘛,這裡表示 key 為 13 且更加複雜的 plaintext。

$ ./caesar 13
plaintext:  be sure to drink your Ovaltine
ciphertext: or fher gb qevax lbhe Binygvar

注意到原本訊息被保留的部分,小寫字母保持小寫,而大寫字母依然是大寫。

那假如使用者不乖乖輸入,他輸入的不是數字呢?
程式必須提示用戶如何使用這段程式:

$ ./caesar HELLO
Usage: ./caesar key

或是根本沒有拋入 command-line 參數,

$ ./caesar
Usage: ./caesar key

或是拋入額外的參數,

$ ./caesar 1 2 3
Usage: ./caesar key

規格

設計並實作 caesar 透過凱薩加密加密訊息。

  • caesar 資料夾下 caesar.c 實作這段程式
  • 你的程式必須接收一個 command-line 參數,一個正整數,我們會稱作 k 在接下來的討論。
  • 假如你的程式執行沒有任何 command-line 參數,或是多於 1 個參數時,
    你的程式應該要印出錯誤訊息(透過 printf) ,
    並且立刻從 main 回傳數字 1 (用於表示錯誤)。
  • 如果任一 command-line 參數並非是十進位數字時,
    你的程式需要印出 Usage: ./caesar key
    並且立刻從 main 回傳數字 1 (用於表示錯誤)。
  • 不要預設 k 一定小於或等於 26 。你的程式應該要能在小於 2 的 31 次方 - 26 下的所有正整數下運作,
    你不需要擔心假設用戶會挑一個超大大於 int 可承受的數字(記得 int 會 overflow),
    但是即使 k 大於 26,alphabetical 的字母永遠會保持是 alphabetical。
    例如,假設 k 是 27,A 就不該變成 [
    即便 [ 在 ASCII 是 A 往後 27 位,
    A 應該要變成 B, 在 ZA 循環下。
  • 你的程式必須印出 plaintext: 然後要求用戶輸入 string 作為 plaintext (透過 get_string)。
  • 你的程式必須印出 ciphertext: 並緊接著 ciphertext ,
    只有 alphabetical 會被轉移,非 alphabetical 字母要保持原樣。
  • 你的程式要保留大小寫
  • 在印出 ciphertext 之後,你必須要印 newline。且你的程式要從 main 回傳 0

建議

要怎麼開始?我們來分析一下這個程式的對策。

Pseudocode

首先,先讓我們在 main 寫下 pseudocode,即便我們還不確定如何實作。

計算 Command-Line 參數的數量

在實作其他功能之前,
我們先來寫一些程式看看如何接收一個參數。

如果使用者沒有提供任何 command-line 參數,或是 2 個以上,
函式必須印出 "Usage: ./caesar key\n" 並且回傳 1
用於結束這個程式。
如果使用者提供剛好 1 個參數,程式就不做任何事並簡單的回傳 0
程式的行為應該要像以下:

$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1

檢查 key

現在你的程式應該可以接收輸入,接著我們到下一步。

加入 only_digits ,他會接收一個 string 作為參數,
並會在只有數字的時候回傳 true,否則他會回傳 false

接著改動 main 用同樣的方式呼叫 only_digits 並傳入 argv[1]
如果函式回傳 falsemain 應該要印出 "Usage: ./caesar key\n" 並回傳 1

$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key

使用 key

現在讓我們將 argv[1] 轉成 int
你可以透過被定義在 stdlib.hatoi 函式。
然後透過 get_string 顯示 "plaintext: " 要求使用者輸入 plaintext。

然後實作一個函式叫做,rotate,他可以接收一個 charint
如果傳入的 char 是 alphabetical 的字母的話就將它轉移位置,在 ZA 的循環下。
如果 char 不是字母,那函式就直接回傳相同的 char

用同樣的方式更改 main 使其印出 "ciphertext: "
迭代使用者輸入 plaintext 中的每個 char 傳入 rotate 函式並印出其結果。

Did you find this article valuable?

Support Hello Kayac by becoming a sponsor. Any amount is appreciated!