苦しんで覚えるC言語

シーザー、排他的論理和法


第1項:暗号化とは

暗号化とは、元のデータを変形させて、意味がわからないようにする技術です。
ただし、意味がわからないのでは無意味なので、元に戻せることが前提です。
元のデータに戻すことを復号化と呼び、元のデータ自体を平文と呼びます。

暗号化の意図は、当然ながら第三者にデータを盗まれないためです。
そのため、他のアルゴリズムと異なり、なによりも暗号強度が重視されます。
いくら高速でも、簡単に解読されるアルゴリズムは使い道が限定されます。

目次に戻る

第2項:シーザー暗号

世界で初めて暗号を使用したのは、有名なシーザー(カエサル)であるといわれています。
その方法は極めて単純な方法で、文字を数文字ずつずらすだけです。

例えば、CAESAR(シーザー)を1文字ずつずらせば、DBFTBS、となります。
これを逆に向きに1文字ずつずらせば、元のCAESARが導き出せます。

次のプログラムは、これを一般的なバイナリファイルに適用します。

使用法
CodeCaesar(入力ファイル名、出力ファイル名、パスワード);
※パスワードは0~255の範囲内の数値にします。
※パスワードをマイナスの数にすれば復号できます。

void CodeCaesar(char finame[],char foname[],int key)
{
    FILE *fi,*fo;
    int value;

    fi = fopen(finame,"rb");
    if (fi == NULL) return;
    fo = fopen(foname,"wb");
    if (fo == NULL) return;

    while ((value = getc(fi)) != EOF) {
        putc(value + key,fo);
    }

    fclose(fi);
    fclose(fo);
}
この暗号は、元のデータとの差を比較すれば簡単に解読されます。

目次に戻る

第3項:排他的論理和暗号

シーザー暗号は単純なので、簡単に解読されてしまいます。
そこで、もう少しわかりにくくするために、排他的論理和を使用します。
排他的論理和
2進数の計算で、同じ数が入力された時に出力が0となる。
排他的論理和では、一見するとでたらめな数値になるため、暗号化に適しています。
C言語で排他的論理和を計算するには、^ 記号を使用します。

次のプログラムは、これを一般的なバイナリファイルに適用します。
使用法
CodeExor(入力ファイル名、出力ファイル名、パスワード);
※パスワードは0~255の範囲内の数値にします。
※同じパスワードで復号できます。

void CodeExor(char finame[],char foname[],int key)
{
    FILE *fi,*fo;
    int value;

    fi = fopen(finame,"rb");
    if (fi == NULL) return;
    fo = fopen(foname,"wb");
    if (fo == NULL) return;

    while ((value = getc(fi)) != EOF) {
        putc(value ^ key,fo);
    }

    fclose(fi);
    fclose(fo);
}
この暗号は、一見しただけで解読されることはありませんが、
256通りのパスワードをテストすれば解読されてしまいます。(コンピュータなら一瞬です)

目次に戻る

第4項:長いパスワード

これまでの暗号化ではパスワードが256通りしかないため、解読は容易です。
そこで、より長いパスワードを使用できるようにします。

考え方は単純です。複数の数値を繰り返して使えばよいだけです。
次のプログラムは、これを一般的なバイナリファイルに適用します。
使用法
CodeExor(入力ファイル名、出力ファイル名、パスワード文字列);
※パスワードは任意の長さの文字列を指定できます。
※同じパスワードで復号できます。

void CodeExorLong(char finame[],char foname[],char key[])
{
    FILE *fi,*fo;
    int value,i = 0;

    fi = fopen(finame,"rb");
    if (fi == NULL) return;
    fo = fopen(foname,"wb");
    if (fo == NULL) return;

    while ((value = getc(fi)) != EOF) {
        putc(value ^ key[i],fo);
        i++;
        if (key[i] == '\0') i = 0;
    }

    fclose(fi);
    fclose(fo);
}
これにより、パスワードの個数を256通りよりも増加させることができますが、
現在のコンピュータの性能を考えると、数文字程度のパスワードだとすぐに解読されてしまいます。

目次に戻る

第5項:疑似乱数の応用

排他的論理和の結果は一見でたらめですが、同じ計算は簡単に行えてしまいます。
これを防ぐには、疑似乱数を使って計算内容がわからないようにします。

疑似乱数では、初期値が同じであれば同じ結果を返し続けるので、
パスワードから初期値を設定すれば、必ず同じ計算結果を得られます。
乱数のアルゴリズム
コンパイラが異なれば標準ライブラリの乱数アルゴリズムも異なります。
そのため、異なるコンパイラでは計算結果が異なることになります。
これを防ぐには、乱数のアルゴリズムも自分で作る必要があります。
パスワードから初期値を作る方法は様々ですが、今回は単純に文字コード値の合計とします。
次のプログラムは、これを一般的なバイナリファイルに適用します。
使用法
CodeExorRandom(入力ファイル名、出力ファイル名、パスワード文字列);
※パスワードは任意の長さの文字列を指定できます。
※同じパスワードで復号できます。

int GetRandom(int min,int max)
{
    return min + (int)(rand()*(max-min+1.0)/(1.0+RAND_MAX));
}

void CodeExorRandom(char finame[],char foname[],char key[])
{
    FILE *fi,*fo;
    int value,early = 0,i;

    fi = fopen(finame,"rb");
    if (fi == NULL) return;
    fo = fopen(foname,"wb");
    if (fo == NULL) return;

    for (i = 0;key[i] != '\0';i++) {
        early += key[i];
    }

    srand(early);

    i = 0;
    while ((value = getc(fi)) != EOF) {
        putc(value ^ GetRandom(0,255),fo);
        i++;
        if (key[i] == '\0') i = 0;
    }

    fclose(fi);
    fclose(fo);
}
これを解読するには乱数の計算方法を解析しなければならず、素人には無理です。
しかし、それなりに詳しい人物がその気になれば解読されてしまうでしょう。
この暗号化の実用度
ここに示した暗号化はいずれも簡単な方法で、その気になれば解読できます。
しかし、その分動作は高速ですし、プログラムも簡単に作れます。
自作ゲームのセーブデータに組み込む程度であれば十分実用的です。

余談ですが、市販ゲームでもセーブデータが簡単に改造できることがあります。
特に、美少女系ゲームはシステムセーブデータを 0xFF で埋めるだけで、
CG、回想共にあっさり見られるようになることも多々あります。
その会社のプログラマーさん、これぐらいの暗号化はして下さいよ。

目次に戻る