[Piet]ヴィジュネル暗号プログラム(その1)
前回に引き続き暗号化プログラムの作成をします。 今回扱うのはヴィジュネル暗号です。
ヴィジュネル暗号とは
今回も前回同様、英語(アルファベット)の文章を暗号化することが前提です。
前回のカエサル暗号は、平文(暗号化前の文章)の各文字を、アルファベット順にある文字数だけずらして暗号文を作りました。
この暗号には弱点があり、つまりずらす文字数のパターンが25通りしかありません。したがって、25通り総当たりで文字をずらしてみれば、あっさり平文が見つかることになります。
この弱点を改善したのが「ヴィジュネル暗号」です。 ヴィジュネル暗号は平文を1文字暗号化するごとに、ずらす文字数を変えていきます。
たとえば、「I have a pen.」という平文を暗号化します。 暗号化は"I"→"h"→"a"→"v"→"e"→...の順に行いますが、 ずらし方を、たとえば3文字、14文字、6文字の順に変えていくとします。
- "I"は3文字ずらすとして、"L"に
- "h"は14文字ずらして"v"に
- "a"は6文字ずらして"g"に
- "v"は再び3文字ずらして"y"に
- ...
1文字だけ見ればシーザー暗号と変わらず25通りの変換しかできませんが、 3文字置きにずらし方を変えていくと、25の3乗の15625通りの暗号化ができるようになります。 さらに、3文字置きではなく、5文字置き、10文字置きなど、 ずらし方は自由ですので、暗号化のパターンはさらに増えます。
ずらす文字数を数字で覚えておくのは大変なので、しばしばアルファベットで記憶する方法がとられます。 ずらす文字数が0文字なら”a”、1文字なら"b"のように数値を変換すると、 上の例の場合はずらす文字数が"dog"のようにまとめられ、簡単に記憶することができます。
このように、暗号化のヒントになる"dog"のような文字列を暗号化の「鍵」と言います。
先ほどの例で全ての文字を暗号化すると、次のようになります。
平文 | 鍵文字 | ずらす文字数 | 暗号文 |
---|---|---|---|
I | d | 3 | L |
h | o | 14 | v |
a | g | 6 | g |
v | d | 3 | y |
e | o | 14 | s |
a | g | 6 | g |
p | d | 3 | s |
e | o | 14 | s |
n | g | 6 | t |
結局"I have a pen."は"L vgys g sst."と暗号化されます。
ヴィジュネル暗号を実装する
仕様
それではPietで実装していきます。仕様は次のようにしました。
- 入力:「平文.鍵.」のように、平文と鍵をピリオド( . )で区切って入力
- 平文:使用可能な文字は、半角英数字(ピリオド除く)
- 鍵:使用可能な文字は半角英字のみ
上の例なら"I have a pen.dog."と入力すれば暗号化できます。
入力に関する補足(クリックして開く)
- 平文にピリオドは使用不可(平文終了の目印になるため)
- 平文の英字以外はそのまま出力し、鍵文字を更新しない
- 平文には大文字・小文字いずれも使用可能
- 鍵には大文字・小文字いずれも使用可能
2については、 たとえば上記の"I have a pen"のスペースを消したり英字以外の記号を入れて "#Ihave!a pen"とすると、"#Lvgys!g sst"となります。 記号はそのまま出力されますが、 "have"が"vgys"に、"pen"が"sst"に変換されるなど、平文のアルファベット部分の暗号化の結果は変わりません。
3については、 入力を"I HAVE A PEN"とすると、出力は"L VGYS G SST"となります。 入力を大文字にすると出力も大文字になりますが、暗号文は変化しません。
4については、 鍵の"dog"を"DOG"や"Dog"にしても、"I have a pen"は"L vgys g sst"に暗号化されます。 (大文字・小文字も変わりません。)
処理の流れ
処理の流れはおおよそ次のようにしました。
- 平文と鍵を入力する
- ★ループ開始地点
- 平文から暗号化する文字を取り出す(全て暗号化済みなら終了)
- 鍵から暗号化に使う文字を取り出す
- 平文の文字が英字以外ならそのまま出力して★ループ開始地点へ戻る
- 鍵の文字がアルファベット順で何番目か調べる(K番目とする)
- 平文の文字をK文字アルファベット順にずらして出力する
- 暗号化に使う鍵文字の位置を1文字ずらす
- ★ループ開始地点に戻る
この処理をPietソースコード生成用のコードで記述すると、次のようになります。 命令397個と、かなり長くなったので、興味のある方は開いてみてください。
処理コード(クリックして開く)
goto:inputPlain #------------------------------ :fInput push1 dup sub push3 push1 roll :fInput_loop inc push3 push2 roll dup push4 push1 roll if:fInput_isOK:fInput_push :fInput_push dup push3 push2 roll dup push4 push1 roll push5 add push1 roll push4 push3 roll push1 add push4 push1 roll :fInput_isEnd push2 push2 push1 roll goto:fIsPeriod :fInput_isEnd_retIsPeriod pop if:retInput: pop goto:fInput_loop :fInput_isOK push1 push2 push1 roll goto:fIsAlpha :fInput_isOK_retIsAlpha pop if:fInput_push: push1 push2 push1 roll goto:fIsPeriod :fInput_isOK_retIsPeriod pop if:fInput_push: pop goto:fInput_loop :retInput pop push2 push1 roll pop dup push1 add push2 push1 roll dup push1 dup sub sub if::inputPlain_retInput dup push1 sub if::inputKey_retInput end #---- :fIsAlpha :fIsAlpha_isUC dup dup push2 push3 push4 add mul dup push1 sub push2 push3 add mul push1 sub push2 push1 roll push1 add push2 push3 mul mul push1 add push4 push1 roll gt push3 push1 roll gt add push2 sub if:fIsAlpha_isLC:fIsAlpha_UC :fIsAlpha_isLC dup dup push2 dup dup dup dup mul mul mul mul dup push3 mul push2 push1 roll push2 dup mul mul push5 sub push4 push1 roll gt push3 push1 roll gt add push2 sub if:fIsAlpha_notAlpha:fIsAlpha_LC :fIsAlpha_notAlpha push1 push1 sub goto:retIsAlpha :fIsAlpha_UC push2 push1 sub goto:retIsAlpha :fIsAlpha_LC push1 push2 sub goto:retIsAlpha :retIsAlpha push3 push2 roll dup push1 sub if::fInput_isOK_retIsAlpha dup push2 sub if::fCalcBase_retIsAlpha end #------- :fIsPeriod dup push1 push3 dup dup push2 add mul mul add sub not push3 push2 roll dup push1 sub if::fInput_isOK_retIsPeriod dup push2 sub if::fInput_isEnd_retIsPeriod end #-------- :fCalcBase push2 push2 push1 roll goto:fIsAlpha :fCalcBase_retIsAlpha pop dup if::fCalcBase_notAlpha push3 dup mul dup mul push4 dup mul push3 push2 roll mul sub goto:retCalcBase :fCalcBase_notAlpha pop push1 dup sub goto:retCalcBase :retCalcBase push3 push2 roll dup push1 sub if::ENC_key_retCalcBase dup push2 sub if::ENC_plain_retCalcBase end #------------------------------ :inputPlain push1 dup sub dup goto:fInput :inputPlain_retInput pop :inputKey push1 push2 push1 roll goto:fInput :inputKey_retInput pop push4 push1 roll push1 sub push4 push2 roll pop goto:ENC_loop #----- :ENC_loop push3 push2 roll dup if::ENC_clearKey dup push1 sub push4 push1 roll push3 push2 roll dup push4 push1 roll dup push3 push1 roll add push4 add push1 push2 sub roll push2 push1 roll push4 add push1 push2 sub roll dup push2 push3 mul push1 roll :ENC_key push1 push2 push1 roll goto:fCalcBase :ENC_key_retCalcBase pop sub push2 push1 roll :ENC_plain push2 push2 push1 roll goto:fCalcBase :ENC_plain_retCalcBase pop dup if::ENC_loop_notAlpha dup push3 push1 roll sub push3 push2 roll add push2 dup dup dup mul mul push3 mul add mod add outc goto:ENC_loop :ENC_loop_notAlpha pop outc pop push4 push3 roll push3 push2 roll dup push4 push1 roll push3 add push1 roll goto:ENC_loop # ----- :ENC_clearKey push3 push2 roll :ENC_clearKey_loop dup if::ENC_clearPlain dup push3 add push1 push2 sub roll pop push1 sub goto:ENC_clearKey_loop :ENC_clearPlain pop :ENC_clearPlain_loop dup if::ENC_clear_done push3 push2 roll pop push1 sub goto:ENC_clearPlain_loop :ENC_clear_done pop pop end
Pietソースコード生成
先ほどのコードをもとに、Pietソースコードを生成してみます。
出力画像サイズ427 × 470(上の画像は2倍に拡大しているので854x940)でした。 今までに比べてかなり大きな部類です。
npietで試してみると次のようになります。 暗号化する文章ですが、Wikipediaに具体例があったので、 動作確認がてら正しく暗号化されるかやってみます。
平文 :THEOLDMANANDTHESEA
鍵 :athensparisstlouis
暗号文:talsyvbaeifvmssmms
...Wikipedia, ヴィジュネル暗号, https://ja.wikipedia.org/wiki/%E3%83%B4%E3%82%A3%E3%82%B8%E3%83%A5%E3%83%8D%E3%83%AB%E6%9A%97%E5%8F%B7] (as of June 2, 2022, 10:27 GMT).
この場合、入力は「THEOLDMANANDTHESEA. athensparisstlouis.」となります。
./npiet VigenereCipherx2.png -cs 2 ? THEOLDMANANDTHESEA. athensparisstlouis. ? ? (中略) ? TALSYVBAEIFVMSSMMS.
仕様の都合で大文字・小文字が異なりますが、確かに正しく暗号化できているようです。
忘れもの
そして、いま気づいたのですが、このプログラムに復号化処理(=暗号文を平文に戻す処理)を入れるのを忘れていました・・・(´・ω・`)
この修正にはちょっと手間がかかりそうです。気が向いたら修正します。
プログラムの解説
さて、今回コードが長く処理も複雑です。 文字だけで解説するのは難しいので、図で説明しようと思っているのですが、 図を作るのに手こずっています。
そこで今回は解説まではせず、全体のフローの流れだけを示して、 とりあえず複雑な流れなんだなー、と実感していただくところで終えようと思います。
先ほどのプログラムの中にコロン「:」で始まる行があります。 大雑把にいうと、それがひとまとまりの処理の開始地点を表しています。 このコロンのブロック同士の流れを図にすると、 次のようになります。
処理は頂上の「:1」というブロックから始まり、 中央右の丸ブロック「:ENC_clear_done」で完了します。
その間、キー入力受付、アルファベット判定、ピリオド判定、暗号化計算など、 さまざまな処理を行なっています。 図の準備ができたら別記事で解説しますので、もう少々お待ちください。
ではまた。