Caesar

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が生成される仕組みを説明します。

plaintextHELLO
+ key11111
= ciphertextIFMMP

より正式には、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ずつ 「ずれて」 して出力されます。アルファベット以外の文字はそのまま出力されます。
  • プログラムは大文字と小文字を保持する必要があります。大文字はずらしても大文字のままである必要があります。小文字はずらしても小文字のままである必要があります。
  • 暗号文を出力した後、改行を出力する必要があります。プログラムはmain0を返して終了するはずです。

どうやって始めますか?この問題に一歩ずつ取り組みましょう。

アドバイス

擬似コード

まず最初に、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 という関数を実装してください。これは charint を入力として受け取り、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