暗号生成・解読プログラムをC/C++/Java/Pythonで書く(単一換字式暗号編)

どうも、最近Flutterを始めたポテト君です(今回の内容とは関係ないです)

Flutter、Android,iOS,windows,ブラウザのアプリ・ページをそれぞれ別に記述しなくても作れるというのだからすごいですよね。

とりあえず最初のアプリとしてこんなものを作ってみました。

はい、ということで今回の本題に入っていきます。
まずはアルゴリズムから、といっても説明はすぐ終わります。

名前の通り1対1で文字を対応させて置き換えるだけなので
a-zの各文字に対応した新しい文字配列で平文の文字を置き換えるだけです。

例えば
"abc…xyz"に"あいう…ねのは"を対応させると
potato

たそとあとそ
になります。

これを各言語で書いていきます。鍵となる文字配列は関数使用時に決めることとします。

投稿数分後の追記:
C++,Java,Pythonのプログラムをデバッグしました。
対応してない文字を変換しようとしたときは例外処理で変換しないようにしました。

C言語

/**
 * 単一換字式暗号を扱います。
 * @param mode "m":暗号化モード,"r":復号化モード
 * @param sentence 変換する文字列(かつ変換後の文字列を格納する配列)
 * @param sen_size sentenceの要素数
 * @param key 鍵(a-zに対応した文字列)
 */
void substitution(char mode, char *sentence, int sen_size, char *key)
{
    char abc[] = "abcdefghijklmnopqrstuvwxyz", *p;
    if (mode == 'm')
    {
        for (int i = 0; i < sen_size; i++)
        {
            if (sentence[i] >= 'a' && sentence[i] <= 'z')
            {
                p = strchr(abc, (int)sentence[i]);
                sentence[i] = key[p - abc];
            }
        }
    }
    if (mode == 'r')
    {
        for (int i = 0; i < sen_size; i++)
        {
            char keystr[26];
            for (int i = 0; i < 26; i++)
                keystr[i] = key[i];
            p = strchr(keystr, (int)sentence[i]);
            if (p - keystr < 26)
                sentence[i] = abc[p - keystr];
        }
    }
}

strchr(char* str,int i)はstrにおいてiの文字コードの文字を探し、見つかったときにそのアドレスを返す関数です。
(アドレスがメモリ上の位置であり番地はアドレスを表す数値であるというような話もありますがめんどいのでアドレスも番地もアドレスと呼びます。)

p = strchr(abc, (int)sentence[i]);
sentence[i] = key[p - abc];

この部分ではまず変換したい文字のabc配列上でのアドレスの番地をpに代入します。
pはabc配列上のアドレスを示すポインタであり、abcはabc配列の最初のアドレスを示します。
アドレスは1バイト毎にあてられ、char型のサイズは1バイトであり、配列というのは連続したアドレスで管理されます。
つまり、sentence[i]と同じ文字が存在するのはabc配列上ではp-abcというインデックスのはずです。
インデックスが分かったのでそれを鍵(変換用の文字配列)上のそのインデックスの文字に置き換えれば暗号化ができます。

複合化も同じように行いますが、strchr関数は厳密にはconst char*(つまり配列)を受け取るため、ポインタをそのまま渡せません。
そのためkeystrという配列を宣言してそこにコピーします。
なぜこの処理をfor文中に入れているのだろうと僕も思いました、おそらく1回実行すれば十分です(じゃあ直せ)。
また、鍵は文字コードが連続するとは限らず、同じ形式で判定しずらいので

if (p - keystr < 26)

とします。strchr関数は文字が見つからなかった場合はNULLを返すからです。

C++

class Cipher{
    /**
     * 単一換字式暗号を扱います。
     * 
     * @param mode "m":暗号化モード,"r":復号化モード
     * @param sentence 変換する文字列
     * @param key 鍵(a-zに対応した文字列)
     * @return string 暗号文または平文
     */
    public:static string substitution(string mode,string sentence,string key){
        string abc="abcdefghijklmnopqrstuvwxyz";
        string ret(sentence);
        if(mode[0]=='m'){
            for(int i=0;i<sentence.length();i++){
                try
                {
                    ret[i]=key[abc.find(sentence[i])];
                }
                catch(const std::exception& e)
                {
                    ret[i]=sentence[i];
                }
                
            }
        }
        if(mode[0]=='r'){
            for(int i=0;i<sentence.length();i++){
                try
                {
                    ret[i]=abc[key.find(sentence[i])];
                }
                catch(const std::exception& e)
                {
                    ret[i]=sentence[i];
                }
                
            }
        }
        return ret;
    }
}

Cよりかなり短いですね、というのもC++ではアドレスを介さずに検索したい文字のインデックスを取得するメソッドがStringクラスのオブジェクトに最初からあるからです。

あとCとの大きな違いはstring型です。

Java

public class Cipher {
    /**
     * 単一換字式暗号を扱います。
     * 
     * @param mode "m":暗号化モード,"r":復号化モード
     * @param sentence 変換する文字列
     * @param key 鍵(a-zに対応した文字列)
     * @return String 暗号文または平文
     */
    static public String substitution(String mode, String sentence, String key) {
        String abc = "abcdefghijklmnopqrstuvwxyz";
        String ret = "";
        if (mode == "m") {
            for (int i = 0; i < sentence.length(); i++){
                try {
                    ret += key.charAt(abc.indexOf(sentence.charAt(i)));
                } catch (Exception e) {
                    ret+=sentence.charAt(i);
                }
            }
        }
        if (mode == "r") {
            for (int i = 0; i < sentence.length(); i++){
                try {
                    ret += abc.charAt(key.indexOf(sentence.charAt(i)));
                } catch (Exception e) {
                    ret+=sentence.charAt(i);
                }
            }
        }
        return ret;
    }
}

今回は前回までのアルゴリズムのようにわざわざ文字コードに変換しなくてもできます。

ただし今気づきましたが対応する文字が存在しないときにおそらくエラーになるという潜在バグがありました。あとでデバッグしておきます(今しないんかい)。
というかそれで言えばC++版も同じバグが起きそうですね(この記事大丈夫か?)

Python

class Cipher:
    """
    暗号を扱うクラスです
    """
    def substitution(mode:str,sentence:str,key:str)->str:
        """
        単一換字式暗号を扱います。
        mode "m":暗号化モード,"r":復号化モード
        sentence 変換する文字列
        key 鍵(a-zに対応した文字列)
        ->return 暗号文または平文
        """
        abc = "abcdefghijklmnopqrstuvwxyz"
        ret=""
        if mode=="m":
            for i in range(0,len(sentence)):
                try:
                    ret+=key[abc.index(sentence[i])]
                except:
                    ret+=sentence[i]
        if mode=="r":
            for i in range(0,len(sentence)):
                try:
                    ret+=abc[key.index(sentence[i])]
                except:
                    ret+=sentence[i]
        return ret

あれ…?

これも同じバグ起きるのでは
(コードを別言語版を見ながら書いたことの欠点)

Python版も大体C++,Javaと同じ感じです。
おそらくデバッグの結果も同じような感じになります(for文内の処理をtry文に移して例外が起きたらそこは変換してない状態の文字を使うとか)


ということで自分のプログラムのバグを見つける会でした(勝手に趣旨を変えるでない)

デバッグしたらこの記事も書き換えます。おそらく

僕はなんでこんなすぐ終わるデバッグをしないで投稿したのでしょう()

次回はポリュビオスの暗号表編です。

この記事が気に入ったらサポートをしてみませんか?