ランダム文字列の生成とチェックサム

C#にはRandomクラスがあり、ランダムな数値を生成します。しかし、文字列のランダム生成は存在しません。そこでランダム文字列の生成法を紹介しようと思います。

ランダム文字列の生成

特に難しいことはなく、ランダム文字列はランダム数値で取得した乱数をインデックスとし、文字列の中からインデックスの場所を取得するという方法です。

ソースコードは以下です。

//ランダム文字列で使う文字を指定
string base_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

//ランダム文字列の長さを指定
int length = 10;

//ランダム文字列の格納を変数
char[] random_chars = new char[length];

//ランダムクラスを作成(Environment.TickCountは書いても書かなくてもよい)
Random random = new Random();

//文字列の長さ文繰り返す
for (int i = 0; i < length; i++)
{
    //文字列のインデックスをランダムに取得
    int char_index = random.Next(base_string.Length);

    //インデックスの位置の文字を取得する
    random_chars[i] = base_string[char_index];
}

//取得した文字配列を文字列に変換する
string result = new string(random_chars);

//結果の出力
Console.WriteLine(result);

よく同じ乱数時にならないようにseedを指定しましょう。指定するseedは「Environment.TickCount」(パソコンの起動時間)がいいと紹介されています。

私も先輩に教えられてこれを結構長い間やってきました。しかし、Randomクラスの定義に移動してコンストラクタをみたら・・・

//
// 概要:
//     時間に応じて決定される既定のシード値を使用し、System.Random クラスの新しいインスタンスを初期化します。
[__DynamicallyInvokable]
public Random()
    : this(Environment.TickCount)
{
}

となっていました。「Environment.TickCount」入ってますね。いつから入っていたのでしょう?最初から?少なくとも私が調べたのは.NetFrameWork4.7.2だったのでそれ以降のバージョンに関しては、「Environment.TickCount」は必要ないのです。なのでRandomクラスの宣言時は、

Random random = new Random();

でseedなし大丈夫ということですね。

ただし、欠点として「Environment.TickCount」はミリ秒単位なので1ミリ秒以内にもう一度Randomクラスのインスタンスを作成するとseedが被ってしまうという欠点があります。

ただRandomクラスは、インスタンス時にseedを入れて乱数を初期化しているだけなので、何個も作る必要はなく、一度作ったらそれを使いまわすのがよいかと思います。

認証コードやライセンスコード、パスワードなどに応用してみる

せっかくランダム文字列の紹介をしたので一歩踏み込んで実用的なところまで紹介できればと思います。

アプリケーションの認証コードなどは、悪用されないためにデータベースに登録したりして運用するかと思いますが、もう一つコード中にそのコードの有用性をチェックするためにチェックサムを忍ばせる方法があります。

チェックサムを使ってコードを作成

チェックサムとはコードの信頼性をチェックする計算式で作成された文字を含む文字列を指します。
チェックサムの計算式は、決まっていません。決まっていたらみんながチェックサムを解読できてしまうため、意味がなくなってしまいます。

一番最後の文字がチェックサムである必要もありません。何文字目がわからないようにしておくのもよいでしょう。

//ランダム文字列で使う文字を指定
string base_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

//ランダム文字列の長さを指定
int length = 10;

//チェックサムインデックス
int chksum_index1 = 2;
int chksum_index2 = 8;

//ランダム文字列の格納を変数
char[] random_chars = new char[length];

//ランダムクラスを作成(Environment.TickCountは書いても書かなくてもよい)
Random random = new Random();

//文字列の長さ文繰り返す
for (int i = 0; i < length; i++)
{

    //チェックサムのところは飛ばす。
    if (chksum_index1 == i) continue;
    if (chksum_index2 == i) continue;
 

    //文字列のインデックスをランダムに取得
    int char_index = random.Next(base_string.Length);

    //インデックスの位置の文字を取得する
    random_chars[i] = base_string[char_index];
}

//チェックサムを生成していれる
random_chars[chksum_index1] = GetCheckSum1(random_chars);
random_chars[chksum_index2] = GetCheckSum2(random_chars);

//取得した文字配列を文字列に変換する
string result = new string(random_chars);

//結果の出力
Console.WriteLine(result);

チェックサムを生成する関数

チェックサムを計算する関数を作成します。この関数は、文字列を生成するときもチェックするときも利用します。

チェックサムの計算式は、ランダム生成した文字をつかって生成します。計算式は適当です。絶対にわからないだろうっていう式を考えてみてください。私は()のなかに自分の誕生日の月をかけてみたり、車のナンバーで割ってみたり、最後にどんな数値が来てもbase_string.Lengthで割った余り(%)を取得すれば、指定文字数の中に納まります。

private char GetCheckSum1(char[] chars)
{
    //チェックサムで使う文字列を指定
    string base_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    //チェックする文字を取得
    char char1 = chars[0];
    char char2 = chars[1];
    char char3 = chars[5];
    char char4 = chars[9];

    //チェックサムを計算する
    int chksum = (char1 + char2 + char3 + char4) % base_string.Length;

    //計算式からでてきた数値のインデックスの位置で文字を取得する
    return base_string[chksum];
}

private char GetCheckSum2(char[] chars)
{
    //チェックサムで使う文字列を指定
    string base_string = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    //チェックする文字を取得
    char char1 = chars[3];
    char char2 = chars[4];
    char char3 = chars[6];
    char char4 = chars[7];

    //チェックサムを計算する
    int chksum = (char1 * char2 * char3 / char4) % base_string.Length;

    //計算式からでてきた数値のインデックスの位置で文字を取得する
    return base_string[chksum];
}

入力値のチェック

入力された文字列のチェックサムを確認して正しい値になっているかチェックします。

//画面から入力された文字と仮定
//string input = "5QxNis7oSh";      //チェックサムが通らない不正文字
string input = "yFewSOfc86";      //チェックサムが通る正しい文字

//チェックサムインデックス
int chksum_index1 = 2;
int chksum_index2 = 8;

//チェックサムの確認
if (input[chksum_index1] != GetCheckSum1(input.ToCharArray()) ||
    input[chksum_index2] != GetCheckSum2(input.ToCharArray()))
{
    Console.WriteLine("入力されたコードは不正です。");
    return;
}

//チェックサムを通ったことによって、コードは正しいものである
Console.WriteLine("入力されたコードは正常です。");

チェックサムを通って文字コードの有効性が確認できたら認証を進める処理に入ります。

まとめ

チェックサムは、計算式がばれてしまうと意味がなくなってしまいます。計算式を複雑にする。チェックサムのチェックサムを作成する。チェックサムの計算を何回も行って結果を取得するなど複雑化しておくと解析しにくいものとなるかと思います。

この記事が皆様のお役に立てたら幸いです。

タイトルとURLをコピーしました