RPGツクールと数学のブログ

RPGツクールと数学についてのブログです。

エニグマ暗号で会話しよう!

エニグマ暗号を使った日本語文字列暗号化ツールを以前作成したので、紹介します。これを使えば、当人たちにしか分からない秘密のやり取りを、掲示板やチャットなどの公の場で行うことが出来ます。

エニグマ暗号の構造

エニグマ暗号とは、戦時中にドイツが使用していた通信用の暗号です。換字式暗号といって何かの文字を別の何かの文字に変えていくシンプルなタイプの暗号なのですが、エニグマ暗号は1文字換字する度にその変換ルールを変化させることで、暗号を複雑化しています。

f:id:fermiumbay13:20171210020509j:plain

エニグマ暗号機は上のような構造をしています。文字を入力すると、各配線を伝っていく要領で出力の文字が光ります。

例えば上の状態の暗号機に対してAを入力すると、ローター1ではC→Cの経路、ローター2ではD→Aの経路、ローター3ではB→Aの経路を通り、反転ローターでA→Dへ移動します。そして折り返し伝っていき、ローター3でB→Aに、ローター2でD→Aに、ローター1でD→Bに伝うので、最終的な出力はBとなります。Aを入力するとBに変換されるのですね。また配線は単なる電線なので、Bを入力すると全く同じ経路を取ってAが出力されます。

これだけの構造ならただの換字式暗号ですが、1文字変換するとローター1が一つずれるように出来ています。

f:id:fermiumbay13:20171210020906j:plain

ローター1が一つずれました。これは悪い例なので、この状態で再度Aを入力してみるとまたBが出力されるのですが……1文字変換されると、またさらにローター1が一つずれます。ローター1が一回転したら(Aの部分が一番上に戻ってきたら)、ローター2が一つずれます。さらに、ローター2が一回転したら(Aの部分が一番上に戻ってきたら)、ローター3が一つずれます。これが繰り返されます。

f:id:fermiumbay13:20171210021328j:plain

この状態でAを入力すると、今度はCになりますね。このように、1文字入力するたびに変換ルールが次々と変わるため、暗号解読が難しくなるのです。この場合は「AAA」を入力したら「BBC」が返るというわけですね。

この暗号機を使う利点は解読が難しいだけではなく、電線やローターといったシンプルな機構で実現でき、さらに入力と出力の配線が繋がっているところにあります。暗号化と復号化が同じ手順で行えるということです。「AAA」を暗号化して「BBC」になったとき、ローターの位置を元の状態に戻して「BBC」と打てば、平文「AAA」が戻ってくるということです。これは便利ですね。

すなわちこの暗号機は、ローターの初期位置が暗号の鍵ということになります。初期位置をどうするかさえ決めておけば暗号通信が出来るようになるというわけです。

とても複雑な暗号の仕組みでありながら、戦時中に結局はコンピュータ科学で有名なチューリングさんたちによって解かれます。解読には群論なども使われたそうですよ^^!

エニグマ暗号化ツールの仕組み

エニグマ暗号機の原理をそのまま使って、Shift-JIS文字コードの日本語を暗号化・復号化するツールを作りました。

エニグマ暗号機 - ツール集 - 空冥の幻影

この暗号機はShift-JISの文字を暗号化/復号化するものです。何でShift-JISなのかというと、Web版を作成する前にHSPでツールを作っていたのですが、HSP文字コードがShift-JISだったというそれだけの理由です……

エニグマの特徴として、暗号化・復号化が同じ操作で行えるというものがありますが、私の作った上記の暗号機は暗号化するときは暗号化、復号化するときは復号化、とそれぞれ押さなければなりません。どこがエニグマなんだと言われてしまいそうですが、実は中身はこんな感じになってます。

暗号化

① 文字をエニグマ用の記号に変換
エニグマ暗号を適用

復号化

エニグマ暗号を適用
エニグマ用の記号を文字に変換

Shift-JISには表示の出来ない文字や、未登録の文字などもありますから、変換先がそうなってしまっては暗号文として使えません。そのため、まず文字をエニグマ用の記号列に変換する必要があるのです。変換処理は共通なのですが、この記号変換に暗号化用/復号化用がそれぞれあるので暗号化と復号化で分かれてしまっているわけです。

文字をエニグマ用の記号に変換

まずはShift-JISの文字をエニグマ用の記号に変換することを考えます。Shift-JISの文字コードは以下のように2バイトのデータで全角文字1文字を表す仕組みになっています。

上位バイト:0x81~0x9f, 0xe0~0xef
下位バイト:0x40~0x7e, 0x80~0xfc 

 「0x」はその直後が16進数だよという意味です。0x81であれば16進数で81ということなので、10進数では8×16+1=129となります。

まず1バイト検出します。アスキー文字の範囲(0x20~0x7e)にあればそのアスキー文字をそのまま出力します。上位バイトの範囲に入っていれば次の1バイトを下位バイトと見なして2バイト目を検出し、それが下位バイトの範囲に入っていれば2バイトの文字として認識する、という手続きを行います。

例えば検出した1バイトが0x82なら上位バイトの範囲内にありますので、次の1バイトも検出します。2バイト目が0xa0であればこれは下位バイトの範囲内にありますので、検出された2バイト文字は0x82a0ということになります。これは「あ」を意味します。

2バイト文字は全部で何通りあるのか計算してみます。16進数ではややこしいので10進数に直してみると

上位バイト:129~159, 224~239 → 47通り
下位バイト:64~126, 128~252 → 188通り

となりますから、全部で47×188=8836通りあるということになります。

実は偶然にも、8836=94 ^{2}になるのです。

94通りのアスキー文字を2文字使えば、2バイト文字を表わすのに十分である一方で、アスキー文字は0x20~0x7eの95通りでしたから、ぎりぎり足りるのです。

そこで、そのうちの1通りを1バイト文字の検出用に使うことにすれば、すべてのShift-JIS文字をアスキー文字だけで表わすことが可能ということになります。

何でもいいのですが、ここでは0x20のスペース文字を1バイト識別用に使います。1バイト字、2バイト字関わらず、アスキー文字2個を使って表わしましょう。

このような変換規則で変換します。

アスキー文字(0x20~0x7e)の場合
・1バイト目にスペース(0x20)
・2バイト目に検出した文字コード

半角カタカナ(0xa0~0xdf)の場合
・1バイト目に検出した文字コード-127
・2バイト目にスペース(0x20)

全角文字の場合
・1バイト目が129~159だったら-129, 224~239だったら-193
・2バイト目が64~126だったら-64, 128~252だったら-65
このとき A=(1バイト目)×188+(2バイト目) とすると、
・1バイト目:[A÷94]+33
・2バイト目:(A mod 94)+33

([・]は小数点以下切り捨て、A mod BはA÷Bのあまり)

 半角カタカナも決められていますので、これはスペースの配置を変えることで対処します。水平タブ(0x09)の変換も可能だとは思うのですが、めんどくさいのでこれはそのまま出力する形にします。

以上の手続きが暗号化です。例として実際に次の文字列を暗号化してみます。

1エン玉10枚

順番に検出していきます。以下では、各文字の区別がつくようにそれぞれかぎかっこで覆っています。[1][エ][ン][玉][1][0][枚]ですね。

[1](0x31) → アスキー文字だから、[ ][1](0x20, 0x31)
[エ](0xb4) → 半角カタカナだから、[5][ ](0x35, 0x20)
[ン](0xdd) → 半角カタカナだから、[^][ ](0x5e, 0x20)
[玉](0x8bca) → 全角文字だから、後述1により、[6][L](0x36, 0x4c)
[1](0x31) → アスキー文字だから、[ ][1](0x20, 0x31)
[0](0x30) → アスキー文字だから、[ ][0](0x20, 0x30)
[枚](0x9687) → 全角文字だから、後述2により、[K][g](0x4b, 0x67)

後述1 [玉]の変換

0x8b, 0xcaはそれぞれ139, 202なので、
1バイト目が129~159だから139-129=10
2バイト目が128~252だから202-65=137
A=10×188+137=2017より、
1バイト目:[2017/94]+33=54=0x36
2バイト目:(2017 mod 94)+33=76=0x4c
0x36, 0x4cはアスキー文字でそれぞれ[6][L]となります。

後述2 [枚]の変換

0x96, 0x87はそれぞれ150, 135
1バイト目が129~159だから150-129=21
2バイト目が128~252だから135-65=70
A=21×188+70=4018
1バイト目:[4018/94]+33=75=0x4b
2バイト目:(4018 mod 94)+33=103=0x67
それぞれ[K][g]を意味します。

以上から、以下の文字列が生成されます。

[ ][1][5][ ][^][ ][6][L][ ][1][ ][0][K][g]

(↑そのまま書くと、 15 ^ 6L 1 0Kg)

これが文字の記号化です。これは単純な換字であり、まだエニグマ暗号ではありません。これを後でエニグマ暗号化します。

エニグマ用の記号を文字に変換

逆にエニグマ用に変換した記号を文字に復元する手続きを記します。先ほどと手順を逆にしただけです。エニグマ用の記号は半角2文字で1つの文字(半角全角問わず)を表しますので、2バイトずつ取り出して処理します。

1バイト目がスペース(0x20)の場合(両方スペースも含む)
・2バイト目に検出した文字コード
2バイト目だけがスペース(0x20)の場合
・1バイト目に検出した文字コード+127
いずれもスペース(0x20)でない場合
B=(1バイト目-33)×94+(2バイト目-33)
・1バイト目:[B÷188]として、これが0~30なら+129, 31~46なら+193
・2バイト目:(B mod 188)として、これが0~62なら+64, 63~187なら+65

実際に次のエニグマ用記号を文字列に戻します。

[ ][1][5][ ][^][ ][6][L][ ][1][ ][0][K][g]

2バイトずつ取り出して処理します。

[ ][1](0x20, 0x31) → 1バイト目が0x20だから、[1](0x31)
[5][ ](0x35, 0x20) → 2バイト目が0x20だから、[エ](0xb4)
[^][ ](0x5e, 0x20) → 2バイト目が0x20だから、[ン](0xdd)
[6][L](0x36, 0x4c) → スペースがないので、後述1により、[玉](0x8bca)
[ ][1](0x20, 0x31) → 1バイト目が0x20だから、[1](0x31)
[ ][0](0x20, 0x30) → 1バイト目が0x20だから、[0](0x30)
[K][g](0x4b, 0x67) → スペースがないので、後述2により、[枚](0x9687)

後述1 [6][L]の変換

0x36, 0x4cはそれぞれ54, 76なので、
B=(54-33)×94+(76-33)=2017より、
1バイト目:[2017÷188]=10で、これは0~30だから、10+129=139=0x8b
2バイト目:(2017 mod 188)=137で、これは63~187だから、137+65=202=0xca
0x8bcaは[玉]となります。

後述2 [K][g]の変換

0x4b, 0x67はそれぞれ75, 103なので、
B=(75-33)×94+(103-33)=4018より、
1バイト目:[4018÷188]=21で、これは0~30だから、21+129=150=0x96
2バイト目:(4018 mod 188)=70で、これは63~187だから、70+65=135=0x87
0x9687は[枚]となります。

 以上から、もとの文字列が復元されます。

1エン玉10枚

これだけでも換字式暗号としては十分使えそうですが……ルールを知っていればすぐに変換できてしまうので、暗号には適しません。

エニグマ暗号を適用

エニグマ用記号に対して、エニグマ暗号を適用することになります。この記号はどんな端末でも表示可能なアスキー文字なので、変換先がわけのわからない記号などにならないのです。

エニグマ暗号の仕組み図は以下の通りです。これは通常のエニグマと同じですね。

f:id:fermiumbay13:20171210132842j:plain

文字群からローターを経由してリフレクターに到達し、跳ね返って再びローターを経由して文字群に戻ってきます。この処理はもう一度すればもとの文字に戻る特徴がありますが、実質この2バイト処理では特に意味がないです。

配線は配線データにより固定されています。配線データファイルのみ共有しておけば良いですね。1字変換するたびにローター1が1つ回転し、それが1周したらローター2が1つ回転、ローター2も1周したらローター3が1つ回転、という繰り返しとなります。ですからローター10が回転することはまずないでしょう。まずっていうかないと思います。

このローターの初期位置が「パスワード」となります。

パスワード1字の文字コードをローターの初期位置とします。パスワードはアスキー文字(0x20~0x7e)で95通りあって、エニグマ用の記号と同じですから、パスワードの文字コードから0x20(32)を引いたぶんローターを移動させるということにします。10文字のパスワードを入力できるようにし、1文字ずつ1つのローターに割り当て、10個のローターを駆動するようにします。3文字しか入力されていない場合は、残りの7文字を半角スペース[ ](0x20)で埋めることで対処しています。

 

以上の仕組みにより本ツールは動いています。試しに1エン玉10枚を入れて、ローターがすべて初期位置(半角スペース10個のパスワード)としてみると、次のように変換されます。

1エン玉10枚

 ↓エニグマ用の記号に変換

 15 ^ 6L 1 0Kg

エニグマ暗号を適用(配線を伝っていった)

r3~'@{ E;~@=0c

 よって、「r3~'@{ E;~@=0c」という文字列になります。わけのわからない文字列ですが、アスキー文字だけで構成されているので、どこにでも貼ることができますね。

これに対して、復号の手続きを実施します。パスワードは同じものを使用しないと、エニグマ暗号の適用時に未登録の文字や、表示不可能な文字が割り当てられてエラーが出ます。

r3~'@{ E;~@=0c

 ↓エニグマ暗号を適用(エニグマ暗号は、入力と出力が等しい)

 15 ^ 6L 1 0Kg

 ↓エニグマ用の記号を通常の文字列に逆変換

1エン玉10枚

このようにして、もとの文字列を得ることができます。

まとめ

当然ですが、構造など知らなくてもツールは使えます。私も過去の記録を見る前は完全に忘れていました。

エニグマ暗号機 - ツール集 - 空冥の幻影

↓本ツールの実践例です。

'RJR?y@$|W(Dv=LGH1)`P#ZX2a^3p I%&Z7;;A5'Qa-75D/)
Td8MEm2;J{hJ#6 %oHj1~]5$[5Sgvydx@Xn=
*AR`X@ c@!eZ[FU-:KgD7o^.c+O-O-f1i4`#3 'jMT6J
z.M<w`%HK_F3wBV)QavhD\K';m<^!bmY}<sM>KNA
:n]YrI7ywJ,0sG mL8

パスワードは「Yahoo!」です。(歴史的なものですかっこわらい)

このパスワードを入力した場合にのみ、正しい文字列が返ります。半角と全角も区別されます。yahoo!」では復号されませんよ!

これで誰とでも秘密のやり取りができますね><