パスワードリンク(2)

リンクをパスワードで暗号化する,其の二。

前回のパスワードリンクでは、たった数文字の文章を100字以上の数字に置換するという非効率な方法をとっていた。実際ISO Latin-1コードのほうが処理を行いやすく、文章とパスワードに日本語が使える。しかしこうすると最終的なデータ量が膨大になってしまう。また、短いコードで置換する文字リストを作成するプログラムがある。

Chart="";
for(i=32;i<127;i++){Chart+=String.fromCharCode(i);}

Latinコードから必要な文字リストを逆生成する。 これは既にCODHTML(HTML暗号化ツール)に利用されている。 問題は、この文字列が95字である点。 100字ないと数字から変換できず、Latinコードにしないと全く扱えない日本語などが使えなくなってしまう。CODHTMLでは、足りない5文字をひらがなで補っている。 このCODHTMLの文字量減量法に、パスワードを加えて最終的な容量が遥かに小さいパスワードリンクの生成に挑戦する。

目次

アルゴリズムの概要

JavaScriptでどう処理するか(実際に書くプログラム)は別にして、原理だけを解説していく。 解説するということは、この原理が分かっても破られないからだ。まず、暗号化対象の文字列をanswer.htmlとして、これをISO Latin-1に変換する。

97,110,115,119,101,114,46,104,116,109,108

このコードの最大桁数は5桁であるが、奇数は分割が少々面倒なので6桁になるように0を加える。

000097,000110,000115,000119,000101,000114,000046,000104,000116,000109,000108

ここまではCODHTMLと全く同じである。ここからパスワードを使いこの文字列を判別不能にする。次にカンマの位置を変更する。

00,00,97,00,01,10,00,01,15,00,01,19,00,01,01,00,
01,14,00,00,46,00,01,04,00,01,16,00,01,09,00,01,08

2桁で、0〜99の数字になった。この数字を、最初に載せた

Chart="";
for(i=32;i<127;i++){Chart+=String.fromCharCode(i);}

で出力される文字列と置き換える。当然、95個しかないので最初に日本語を指定しておく。

Chart="あかさたな";
for(i=32;i<127;i++){Chart+=String.fromCharCode(i);}

これを走らせると次の文字列を出力する。

あかさたな !"#$%&'()*+,-./0123456789:;<=>?@ABCDEF
GHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

この100個の文字は、先ほどの数列の00〜99に当てはまる。 続いてパスワード。これも同様の処理をする。 ここではpassを使う。

00,01,12,00,00,97,00,01,15,00,01,15

これを順番に数値化した文字列と足していく(シーザー暗号の手法)のだが、最後の.htmlが見破られてしまう。 そこで、これを防ぐためにパスワードを数値化したこの数値の平均値をとり、この平均値も全ての数値化した文字列に足していく。 平均値を足すことによって判別を困難にできる。

なお、数値が100を越えた場合、100を0に戻す処理を行うが、ソース長が長くなるため上で作ったChartをいくつも並べることによって解決する。 最終的にこの並べたChartの文字列に置換して暗号化は終了となる。 方程式を立てづらくするために、平均値の平方をさらに足してもいいかもしれない。 パスワードも同じようにパスワードで暗号化する。 パスワードの場合は自身で自身を暗号化するため方程式を立てられやすい。 この問題はLatin-1コードに変換したものを逆さにして暗号化するなどの手法で解決できる。 計算量がやたら多くなるので結果の計算はしない。デモで試してみて欲しい。

暗号生成・複合化デモ

文章・パスワード共に全ての文字が使えます。 ランダムに生成された乱数で暗号化毎に性格の違う暗号になります。 暗号化を破りたい人へ→次の暗号を解読してください。

暗号化デモ
暗号化真前の文字列
パスワード

暗号化後の文字列
Debug

後記

前回のパスワードリンクと違って、間にセッションキーを通さなかった。 乱数で作られたキーを通さないと破られる確率が増大するが、セッションキーは最終的な暗号文の容量を増やしかねない。 今回、これを防ぐために元の文章とパスワードを使って複数の定数を作り、それらを足し引きして暗号強度を高めた。 もはや前回のパスワードリンクはゴミ同然。

できるだけ複合スクリプトの長さを縮めようとしたが、前回のパスワードリンクの暗号化⇔複合化のフルセットが7.9KBに対し、今作ったスクリプトの暗号化⇔複合化のフルセットが6.73KBとあまり大差がない。 CODHTMLへの利用は不可能そうだ。 ただし、パスワードが間違っているか合っているかを承認する機能を取っ払えばかなり小さくなるはず・・・

とりあえず前よりは軽くなったので、実用的なパスワード認証スクリプトを組んだ。

パスワード認証スクリプト

利用自由。許可不要。 ただしスクリプト中の著作権表示・リンクはそのままでお願いします。 2次配布も自分で作ったように見せないなら許可。 使用は自己責任で行ってください。 破られることはそう簡単にはないはずですが、万が一破られた場合責任負いません。 実用的とか書いてますがまだまだ長いですね・・・

※注意。暗号文中の\は\\に、"は\"に変えてください。

以下のソースをパスワードリンクをつけたい位置に挿入してください。 飛ばしたいURLを暗号生成・複合化デモの暗号化前文字列に入れ、好きなパスワード(英語でも日本語でも漢字でも何でもOK)を入れて暗号化を押します。 出力された暗号化後文字列を指定の場所に入れれば出来上がり。

<script type="text/javascript">
<!--
//パスワードリンク(2)
unCOD = function(pass){
        
        
        //「ここに暗号化後文字列」の部分に生成した暗号文を入れる。
        //※注意!! 暗号文中の\は\\に、"は\"に変えてください。
        var angou = "ここに暗号化後文字列";
        
        
        //これより下はJavaScriptが扱えない人は触れないで下さい。
        var ans_Key = pass;
        var moji_Text = angou.split("~")[0];
        var moji_Key = angou.split("~")[1];
        var moji_Random = angou.split("~")[2];
        if(ans_Key==""){alert("パスワードが入力されていません。");return;}
        Chart="あかさたなは";
        for(i=32;i<126;i++){
                Chart+=String.fromCharCode(i);
        }
        var code_Key = "";
        for(i=0;i<moji_Key.length;i++){
                var code_a = Chart.indexOf(moji_Key.charAt(i));
                if(String(code_a).length==1)code_a = "0"+code_a;
                code_Key+=code_a+",";
        }
        code_Key = code_Key.substr(0,code_Key.length-1);
        var code_Text = "";
        for(i=0;i<moji_Text.length;i++){
                var code_a = Chart.indexOf(moji_Text.charAt(i));
                if(String(code_a).length==1)code_a = "0"+code_a;
                code_Text+=code_a+",";
        }
        code_Text = code_Text.substr(0,code_Text.length-1);
        var code_Random = Chart.indexOf(moji_Random);
        var code_ans_Key="";
        for(i=0;i<ans_Key.length;i++){
                var code_a = "";
                switch(String(ans_Key.charCodeAt(i)).length){
                        case  4: code_a+="00"     ;break;
                        case  3: code_a+="000"    ;break;
                        case  2: code_a+="0000"   ;break;
                        case  1: code_a+="00000"  ;break;
                        default: code_a+="0";
                }
                code_a+=ans_Key.charCodeAt(i);
                code_ans_Key+=code_a.substr(0,2)+","+code_a.substr(2,2)+","+code_a.substr(4,2)+",";
        }
        code_ans_Key = code_ans_Key.substr(0,code_ans_Key.length-1);
        var Key_x = 0;
        for(i=0;i<ans_Key.length*3;i++){
                Key_x+=parseFloat(code_ans_Key.split(",")[i]);
        }
        Key_x = Math.ceil(Key_x/(ans_Key.length*3));
        var code_Key_long = ""; 
        for(i=0;i<=(Math.ceil(moji_Text.length*3)/(ans_Key.length*3));i++){
                code_Key_long+=code_ans_Key+",";
        }
        var code_Text_2 = "";
        for(i=0;i<moji_Text.length;i++){
code_a=parseFloat(code_Text.split(",")[i])-Key_x-parseFloat(code_Key_long.split(",")[i])-(moji_Text.length%10)-code_Random;
                while(code_a<0){code_a+=100;}
                if(String(code_a).length==1)code_a = "0"+code_a;
                code_Text_2+=code_a+",";
        }
        code_Text_2 = code_Text_2.substr(0,code_Text_2.length-1);
        var code_Key_2 = "";
        for(i=0;i<moji_Key.length;i++){
code_a=parseFloat(code_Key.split(",")[i])-Key_x-parseFloat(code_Key_long.split(",")[i])-(moji_Key.length%10)-code_Random;
                while(code_a<0){code_a+=100;}
                if(String(code_a).length==1)code_a = "0"+code_a;
                code_Key_2+=code_a+",";
        }
        code_Key_2 = code_Key_2.substr(0,code_Key_2.length-1);
        beed_code_Key = "";
        for(i=1;i<=code_Key_2.length;i++){
                beed_code_Key+=code_Key_2.charAt(code_Key_2.length-i);
        }
        code_Key_2 = beed_code_Key;
        
        
        if(code_Key_2!=code_ans_Key){
                alert("パスワードが違います。");
                return;
        }
        var i=0;
        var Text = "";
        while(i<moji_Text.length){
                Text+=String.fromCharCode(code_Text_2.split(",")[i]+code_Text_2.split(",")[i+1]+code_Text_2.split(",")[i+2]);
                i+=3;
        }
        alert("パスワードを承認しました。");
        location.href = Text;
}
//-->
</script>
<form onsubmit="return false;">
<input type="text" name="pass" value="">
<input type="button" value="認証" onclick="unCOD(this.form.pass.value)">
</form>

この暗号に挑戦!次の暗号を解読せよ

解読したい人用。 解読して送信フォームか掲示板に貼り付ければ作者を絶望のどん底に突き落とせます。 解読方法も掲載してください。

解読可能な条件について

2005/07/18

この暗号化には決定的な問題がある。 暗号に関与している数値は次の3つだ。

この3つを特定のアルゴリズムでごちゃ混ぜにしているわけだが、暗号化後の文字列は既に分かっていて、残りの3つ。 つまり上に挙げた3つの要素のうち2つが分かれば、プロセス上残りの1つを復元できてしまう。 乱数は公開状態。パスワードは完璧に暗号化されていて、コイツは破れない。 とすれば、暗号化する文章の一部を見破られると暗号が解かれてしまう。 もちろん、特定の条件下で、暗号化する文章のうち連続して判明している文字の長さがパスワードと同じかそれ以上であること。 パスワードが9桁の場合、9文字連続して文章が判っていれば暗号を解除できる。 危険なのは、パスワードリンクとしての利用でHTMLファイルのURLを暗号化する。 例えばこれを「answer.html」としよう。.htmlの部分は想像可能だ。 5文字は破られたことになる。 ここで、パスワードが5文字以下、例えば「pass」ならこの暗号は解かれてしまう。

さて、具体的なプロセスを説明するととんでもなく長いので比喩的に説明しよう。例えば...

乱数=24
パスワード=159
暗号化する文章=200,100,145
      

暗号化するデータは3つあり、本当のプロセスはもっと長いが、例えば暗号化するのに3つの数字同士をかけたとしよう。

200*24*159=763200
100*24*159=381600
145*24*159=553320

暗号化後の文章は763200,381600,553320になる。 さて、ハッカー(ちょっと言い過ぎ)がこれを解読しようとする。 乱数は公開だ。まずはこの公開された24で割る。 これで暗号文は31800,15900,23055となるわけだ。 ハッカーが知りたいのは暗号化前の文章。 これらの暗号文にはパスワードが含まれている。 よって、パスワードをχとすると、暗号文は次のようになる。

        31800=χ*A
15900=χ*B
23055=χ*C
      

ハッカーが知りたいのはA,B,Cの値だ。 しかし、これを取り出す過程でパスワードχが邪魔をする。 しかし、さっき言ったように、例えばCの値が予想できたとしよう。 C=145と判ってしまえば、χ=23055÷145でχの値159がバレる。 あとは簡単。χで割ることによってAとBも解読されてしまう。 こんな感じで暗号化は破られる。

では、これができないパターンは何か。答えは簡単だ。

単にパスワードを長くするだけでいい。

一番望ましいのは、暗号文と同じ長さにすることだ。こうすると、暗号文の1文字1文字のテキストは、全て異なったパスワードの値によって暗号化され、ハッカーが立ち入る隙はない。

因みに、この暗号に挑戦! 次の暗号を解読せよは元の暗号文が推測不能な以上、この方法では絶対に解けない。

ページ情報

作成日時
2005/07/18
最終更新日時
2005/07/18
HTML4.01版
index.html
XHTML1.1版
index.xhtml
XML原本
index.xml