[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."と入力すれば暗号化できます。

入力に関する補足(クリックして開く)

  1. 平文にピリオドは使用不可(平文終了の目印になるため)
  2. 平文の英字以外はそのまま出力し、鍵文字を更新しない
  3. 平文には大文字・小文字いずれも使用可能
  4. 鍵には大文字・小文字いずれも使用可能

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"に暗号化されます。 (大文字・小文字も変わりません。)

処理の流れ

処理の流れはおおよそ次のようにしました。

  1. 平文と鍵を入力する
  2. ★ループ開始地点
  3. 平文から暗号化する文字を取り出す(全て暗号化済みなら終了)
  4. 鍵から暗号化に使う文字を取り出す
  5. 平文の文字が英字以外ならそのまま出力して★ループ開始地点へ戻る
  6. 鍵の文字がアルファベット順で何番目か調べる(K番目とする)
  7. 平文の文字をK文字アルファベット順にずらして出力する
  8. 暗号化に使う鍵文字の位置を1文字ずらす
  9. ★ループ開始地点に戻る

この処理を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ソースコードを生成してみます。

ヴィジュネル暗号ソースコード(2倍に拡大)

出力画像サイズ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」で完了します。

その間、キー入力受付、アルファベット判定、ピリオド判定、暗号化計算など、 さまざまな処理を行なっています。 図の準備ができたら別記事で解説しますので、もう少々お待ちください。

ではまた。