Caesar暗号を使用してメッセージを暗号化するプログラムを実装します。
$ ./caesar 13
plaintext: HELLO
ciphertext: URYYB
入門
VS Codeを開きます。
ターミナルウィンドウ内をクリックすることから始めて、それからcd
を実行します。
その後プロンプトは次のようになっていることがわかります。
$
ターミナルウィンドウの内側をクリックし、次のように入力します。
wget https://cdn.cs50.net/2021/fall/psets/2/caesar.zip
その後にEnterを押すと、caesar.zipというZIPがあなたのコードスペースにダウンロードされます。wgetと次のURLの間にあるスペースや、その他の文字を見落とさないように注意してください。
次に
unzip caesar.zip
を実行して、creditというフォルダを作成します。
ZIPファイルは不要になりましたので、
rm caesar.zip
を実行し、プロンプトで “y “に続いてEnterで応答すると、ダウンロードしたZIPファイルが削除されます。
次に
cd caesar
の後にEnterを押して、そのディレクトリに移動する(つまり、開く)。これでプロンプトは以下のようになります。
caesar/ $
すべて成功した場合は、次のように実行します。
ls
code caesar.cを実行すると、このProblem setのコードを入力するファイルが開かれるはずです。そうでない場合は、手順をたどって、どこで間違ったのかを判断してください。
背景
Caesar (シーザー/カエサル) は、機密メッセージを作る (すなわち、可逆的な方法で隠す)ために、それぞれの文字をずらしていたと考えられています。例えば、AをB、BをC、CをD、…とし、ZをAと書き、HELLOと言うために、シーザーはIFMMPと書いていたかもしれません。
この 「暗号システム」 の秘密はシーザーだけに依存しており、受取人はシーザーが彼の手紙をシフトした文字数 (例:1) という秘密を知っていました。現代の標準では特に安全というわけではありませんが、世界で初めての試みでしたでしょうから、かなり安全です。
暗号化されていないテキストは、一般に平文 (プレーンテキスト) と呼ばれます。暗号化されたテキストは、一般に暗号文と呼ばれます。使用される秘密は鍵と呼ばれます。
ここでは、HELLO
を1のキーで暗号化するとIFMMP
が生成される仕組みを説明します。
plaintext | H | E | L | L | O |
+ key | 1 | 1 | 1 | 1 | 1 |
= ciphertext | I | F | M | M | P |
より正式には、Caesarのアルゴリズム (暗号文) は、各文字をk個の位置だけ 「ずらす」ことによってメッセージを暗号化します。もっと正式にいうと、pが平文 (暗号化されていないメッセージ) で、piがpのi番目の文字で、kが秘密鍵 (負でない整数) なら、暗号文cのそれぞれの文字ciは次のように計算されます。
ci = (pi + k) % 26
ここで% 26
は 「26で割ったときの剰余」 を意味します。この公式は、暗号を実際よりも複雑に見せるかもしれませんが、アルゴリズムを正確に表現するための簡潔な方法にすぎません。実際、議論のために、A (またはa) を0、B (またはb) を1、…、H (またはh) を7、I (またはi) を8、…、Z (またはz) を25と考えてみましょう。Caesarが、今度は3のキーkを使って、内密に誰かにHiと言いたいとします。そのため、平文pはHiになり、その場合、平文の最初の文字p0はH (または7) になり、2番目の文字p1はi (または8) になります。彼の暗号文の最初の文字c0はしたがってKであり、二番目の文字 c1はLになります。
ここでは、Caesarの暗号を使用してメッセージを暗号化できるcaesar
というプログラムを作成します。ユーザはプログラムを実行するときに、コマンドライン引数を指定して、実行時に提供する秘密メッセージ内のキーを決定する必要があります。ユーザのキーが数字であると必ずしも仮定すべきではありません。また、数値の場合は正の整数でなければなりません。
プログラムの動作例をいくつか示します。たとえば、ユーザが1のキーとHELLO
というプレーンテキストを入力したとします。
$ ./caesar 1
plaintext: HELLO
ciphertext: IFMMP
ユーザが13のキーとhello, world
のプレーンテキストを入力すると、プログラムは次のように動作します。
$ ./caesar 13
plaintext: hello, world
ciphertext: uryyb, jbeyq
カンマもスペースも暗号によって 「シフト」 されていないことに注意してください。英字のみをシフトさせます。
もうひとつ試してみましょう。ユーザが再び13の鍵をもっと複雑な平文で示した場合、プログラムはどのように動作するかを以下に示します。
$ ./caesar 13
plaintext: be sure to drink your Ovaltine
ciphertext: or fher gb qevax lbhe Binygvar
なぜでしょうか?
元のメッセージの大文字と小文字が維持されていることに注意してください。小文字は小文字のまま、大文字は大文字のままです。
もし、ユーザーが協力してくれず、数字以外のコマンドライン引数を与えてしまったらどうでしょう?プログラムは、ユーザーにプログラムの使い方を思い出させる必要があります。
$ ./caesar HELLO
Usage: ./caesar key
あるいは、本当に協力せず、コマンドラインの引数を全く与えない場合はどうすればよいでしょうか?プログラムは、ユーザーにプログラムの使い方を思い出させる必要があります。
$ ./caesar
Usage: ./caesar key
あるいは、本当に、本当に協力せず、複数のコマンドライン引数を指定した場合はどうすればよいでしょうか?プログラムは、ユーザーにプログラムの使い方を思い出させる必要があります。
$ ./caesar 1 2 3
Usage: ./caesar key
試してみましょう
スタッフによるこの問題の実装をテストするには、次のコマンドを実行します。
./caesar key
このサンドボックス内で、keyの代わりに有効な整数を代入します。
仕様
Caesarの暗号を使用してメッセージを暗号化するプログラムcaesar
を設計および実装します。
caesar
というディレクトリにあるcaesar.c
というファイルにプログラムを実装します。- プログラムは、1つのコマンドライン引数 (負でない整数) を受け入れる必要があります。議論のためにkとしましょう。
- プログラムをコマンドライン引数なしで、または複数のコマンドライン引数を指定して実行した場合、プログラムはエラーメッセージ (
printf
で) を出力し、main
から値1 (エラーを示す) をすぐに返します。 - コマンドライン引数の文字のいずれかが10進数でない場合、プログラムは
Usage: ./caesar key
というメッセージを出力し、main
から値1
が返されます。 - kが26以下であると仮定しないでください。プログラムは、2^31 -26より小さいkのすべての非負の整数値に対して機能するはずです。つまり、ユーザが
int
に適合するには大きすぎるkの値を選択した場合に、プログラムが最終的に壊れてしまうと心配する必要はありません (int
はオーバーフローする可能性があることを思い出してください)。ただし、kが26より大きい場合でも、プログラムの入力のアルファベット文字は、プログラムの出力のアルファベット文字のままである必要があります。たとえば、kが27の場合、http://www.asciichart.com/[asciichart.com]によれば、A
は27離れている[
ではなく、Z
からA
に戻るとしてA
から27離れた位置にあるB
になります。 - プログラムは (改行なしの)
plaintext:
を出力し、ユーザに (get_string
を使って) プレーンテキストのstring
を要求しなければなりません。 - プログラムは (改行なしで)
ciphertext:
を出力しなければなりません。 平文と対応する暗号文が続き、平文中のアルファベット文字がそれぞれkずつ 「ずれて」 して出力されます。アルファベット以外の文字はそのまま出力されます。 - プログラムは大文字と小文字を保持する必要があります。大文字はずらしても大文字のままである必要があります。小文字はずらしても小文字のままである必要があります。
- 暗号文を出力した後、改行を出力する必要があります。プログラムは
main
に0
を返して終了するはずです。
どうやって始めますか?この問題に一歩ずつ取り組みましょう。
アドバイス
擬似コード
まず最初に、caesar.c に、実際のコードでどのように書くかはわからないが、擬似コードだけでプログラムを実装する main 関数を書いてみてください。
ヒント
複数の方法がありますが、ここでは1つだけを紹介します。
int main(void)
{
// プログラムが1つのコマンドライン引数で実行されたことを確認する。
// argv[1] に含まれるすべての文字が数字であることを確認する。
// argv[1] を string から int に変換する。
// 平文を入力するようにユーザに促す
// 平文に含まれる各文字ごとに繰り返す
// 文字の場合は、その文字を回転します。
}
コマンドライン引数のカウント
擬似コードが何であれ、追加機能を追加する前に、プログラムが単一のコマンドライン引数で実行されたかどうかをチェックするCコードのみを最初に記述します。
具体的には、caesar.c
を次のように変更します。ユーザがコマンドライン引数を1つだけ指定した場合、Success
と表示します。コマンドライン引数を指定しなかったり、複数指定したりすると、Usage: ./caesar key
と表示されます。このキーはget_string
経由ではなく、実行時にコマンドラインから取得されるため、ユーザに再度プロンプトを表示することはできません。結果のプログラムの動作は次のようになります。
$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1
キーの確認
さて、あなたのプログラムは(うまくいけば!)規定通りに入力を受け付けられるようになったので、もう1つのステップに進みます。
文字列を引数にとり、その文字列が0から9までの数字だけを含んでいればtrueを、そうでなければfalseを返す、 only_digits
という関数を caesar.c の main
以下の部分に追加してください。この関数のプロトタイプをmainの上に追加してください。
そして、argv[1]に対してonly_digits
を呼び出すようにmainを修正します。もし、その関数がfalse
を返したら、mainは "Usage: ./caesar key\n"
と表示し、1
を返します。そうでなければ、mainは単に0
を返さなければなりません。このように、プログラムは以下のように動作するはずです。
$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key
キーの使用
ここで、argv[1]
をint型に変換するようにmainを修正します。stdlib.h
で宣言されているatoi
は役に立つと思います。そして、get_string
を使ってユーザーに平文を入力するように促し、 "plaintext: "
と表示します。
そして、例えば rotate
という関数を実装してください。これは char
と int
を入力として受け取り、char が文字(つまりアルファベット)であればその数だけ回転させ、必要に応じて Z から A(および z から a)へと回り込みます。もしcharが文字でなければ、この関数は代わりに同じcharをそのまま返します。
次に main を変更して、"ciphertext: "
を表示するようにします。そして、ユーザーの平文に含まれるすべての文字に対して反復処理を行い、それぞれに対して rotate
を呼び出し、その戻り値を表示するように main
を修正します。
ウォークスルー
コードのテスト方法
check50
を使用して以下を実行し、コードの正確さを評価してください。ただし、コンパイルとテストは必ず自分で行ってください。
check50 cs50/problems/2022/x/caesar
以下を実行し、style50
を使用してコードのスタイルを評価します。
style50 caesar.c
提出方法
お使いの端末で、以下を実行して作品を提出してください。
submit50 cs50/problems/2022/x/caesar