エニグマ暗号に挑みたい②

エフ
2026-06-08
2026-06-08

表紙

 

前回

前回作成した簡易エニグマ
こちらで今回、解読する暗号を作成しました。

WUTGJVSPABINDTDOFVJVLXERMZLURQESVJOADHRLMARHBNYXSKOXVHJWZFRBOYEVNATYICEKPDOGMJEJIENWNRHXCABYOBYRRXOJJXSJQLAJGUTCRPNPDINZSFIJOISBTMPSOCHHANODUEXMZSIZLXWZGLLNOEHIALHZDCAGLYKRVBGJVGHOQJXYOEKZFAB

ちょっと長くしてみました。
この暗号文だけ、通信網で流れてきたということで、
当然これだけでは訳が分かりません。
今回はこれを解読していきます。

しかし、これだけではさすがにとっかかりがない。
ちなみにこのまま暗号文をAIに解読させてみましたが、
さすがに暗号文だけではまったく解読できず。さすがエニグマ暗号。

アラン・チューリングはどのようにしたか。
確認したところ、チューリングはエニグマの実機を手に入れて、
各回転子の配置パターンは把握していたとのこと。
もし違ってたらすいません。

前回作成したコードの部分で言うと
-----------------------------------
# 固定ローター
rotor1 = list("EKMFLGDQVZNTOWYHXUSPAIBRCJ")
rotor2 = list("AJDKSIRUXBLHWTMCQGZNPYFVOE")
rotor3 = list("BDFHJLCPRTXVZNYEIWGAKMUSQO")
-----------------------------------
この部分ですね。

この部分、コードだと文字配列だけど、実際のエニグマ暗号機では
ローター配線パターンということになると思われます。

なので、どこかでエニグマ暗号機を奪取し、回転子の配置は把握できた。
ただ共通鍵がわからない、という想定で暗号解読に挑みます!

まずは泥臭く解決したいと思います。
この簡易暗号ですが、そもそもこの回転子の初期位置を決定する
3つの数字のみに依存しており、この数字以外の要素はありません。
※各回転子の配置パターンがわかっている場合

ということは
この暗号で生成されるパターンは単純に

26×26×26  の  17,576通り

ということで、まずは、この17,576通りの総当たりを実施します。

-----------------------------------
# 固定ローター
rotor1 = list("EKMFLGDQVZNTOWYHXUSPAIBRCJ")
rotor2 = list("AJDKSIRUXBLHWTMCQGZNPYFVOE")
rotor3 = list("BDFHJLCPRTXVZNYEIWGAKMUSQO")
-----------------------------------
この固定配置を使って、17,576通りを総当たりするコードを作成します。

コードが以下
-----------------------------------------
import string
from typing import List, Tuple

LETTERS = string.ascii_uppercase
L2I = {c: i for i, c in enumerate(LETTERS)}  # 文字→index

# 固定ローター(そのまま)
ROTOR1 = "EKMFLGDQVZNTOWYHXUSPAIBRCJ"
ROTOR2 = "AJDKSIRUXBLHWTMCQGZNPYFVOE"
ROTOR3 = "BDFHJLCPRTXVZNYEIWGAKMUSQO"

def rotate_str(s: str) -> str:
    return s[1:] + s[:1]

def decrypt_bruteforce_all(cipher: str, out_path: str = "bruteforce_results.txt") -> None:
    """
    暗号文cipherに対して offsets (1..26)^3 を総当たりし、全復号結果を out_path に出力する。
    暗号方式・回転方式は元コードと同一(オドメータ式)。
    """
    cipher_u = cipher.upper()

    with open(out_path, "w", encoding="utf-8") as f:
        for o1 in range(1, 27):
            for o2 in range(1, 27):
                for o3 in range(1, 27):
                    r1, r2, r3 = ROTOR1, ROTOR2, ROTOR3
                    step1 = step2 = 0
                    out_chars: List[str] = []

                    for ch in cipher_u:
                        if ch not in L2I:
                            out_chars.append(ch)
                            continue

                        # 逆変換:r3 -> r2 -> r1 (元コードと同じ)
                        idx = r3.index(ch)
                        ch2 = LETTERS[(idx - o3) % 26]
                        idx = r2.index(ch2)
                        ch3 = LETTERS[(idx - o2) % 26]
                        idx = r1.index(ch3)
                        plain = LETTERS[(idx - o1) % 26]
                        out_chars.append(plain)

                        # カスケード回転(元コードと同じ)
                        r1 = rotate_str(r1)
                        step1 += 1
                        if step1 % 26 == 0:
                            r2 = rotate_str(r2)
                            step2 += 1
                            if step2 % 26 == 0:
                                r3 = rotate_str(r3)

                    plain_text = "".join(out_chars)
                    f.write(f"{plain_text}\toffsets=({o1},{o2},{o3})\n")

def main():
    cipher = input("復号したい暗号文を入力してください: ").strip()
    out_path = input("出力ファイル名(空なら bruteforce_results.txt): ").strip() or "bruteforce_results.txt"
    decrypt_bruteforce_all(cipher, out_path)
    print(f"総当たり結果を {out_path} に保存しました。")

if __name__ == "__main__":
    main()
-----------------------------------------

というわけで、暗号文を総当たりで解読しました。
以下はその一部です。

------------------------------------------------------------
SLXZRSXZXNAYWMUBEGDETYTDGZPGWBIVPMCODYMCOIISOICNEGMJTAGGKZOLJXXIRIJRGTPHRUVIBJQMENWWUHVGHIZFIWCXWWCSRSWOFPGMRMNPWSOQVFKNYIAWMXSMVYDDQDUTHNKHMGVILPVISOGEXNMVLVSGLMOYCJFXFOQKWMQOSNYOXIKCHDYSPFS  offsets=(1,1,1)
KDYPHOGAUHFNKNIZSCTABEVJDYTBQXSCCCWQWDGGPKCXSBHKLAGGNDZTPKFKUUWCCSGORVOCQXGMCCSCOPABYCUMXMKNLHKSRCFIHYMEOOTXZSYJTNSLXXOYBQSHLPVJGVGVVHBANSPSLYWBBHBYUPRUEPOAVAVTECRFVIYNVBBLAUGBWDBYEVPDRAICLGB  offsets=(1,1,2)
PAEHQUYGKFHMETCOGICGRFNKHUJCOEXXZHSCACEWJWAWMCADGCCZXOCEJNEVQFTMHWXZQRLMSWCZXFABQXELCKEFDQNRGKOAZVAHGRHDRZBWOPVIOHDFUYZXHSTGUQBIDJKUCJMLUIWJEJQATGTQGJXMPBAQCNIYUPEJDYOYISUDXERETOOFIYFWXBJFUNL  offsets=(1,1,3)
REFMEVVOCUZICUAXAJQHALCQEAOWNZQYSGZIUVDBLCZPIPBXHOJTURNDTMGRIKEJBJWENKWQOHUKHQELYBLNJADATVIVWFSQPQQRQMANXYQEGDQTIGCETLYEEPGOXDYRYWMNUVXWFAOODORGUZERNKPNVIHIGEBLFSXGNLZLLZKCJLERFBHJVLYGQUQKDUR  offsets=(1,1,4)
JBLOSBZEHDOORAPPYPENOMBYUBNYRARSMZUDQZHAXXDTPMOVBUERLQQAQXCJREJALUYYYNBDHDDJLTIKCFRVPGOISFYCRVZWVYLQPUVMUGIGHQKSHRJPCPFPSOKQDHMUSEYMTCONLBNVJEUNOYJLYNAHNTSJTLJCSFFSUOMQYDVSKPJKGGPWGELJGEXANZK  offsets=(1,1,5)
YRTGMCWNJVNPAIYMNQYOCSXOMHGKHUEUKDVNXTXTDHTNKFLUDPFQKBPLHTVSSODZITUIDYVOKVEGYSPVGMKZIWRYCHTIKQFMLOEBAKZXIIJDUYJASQUOFAQMFXVNASZARTFSMNTSDVGNQWYYPEZMJRFIYEDDEPTJXYPTYBRDRAIFCCWSYTZHRMWOAHCTTWA  offsets=(1,1,6)
XJJVKIMBBSJVSYQQWWWUWADXRIKQMWBGJXPSSPCXYMYJLZEYPZZUMXAQGLYTNLNBZQNFXBFNVEZRJDVYNSFDDVMEBPMBFJYLKUZEDQDAVFWCYNUCRYRWLZNHNAUMORHXCLQZLYAZOWFMBXAESLRPATVLDVUEPMANKGWLLUEUZMNIBNNCXKGSKWQEKMZGMXU  offsets=(1,1,7)
TOSUZQEPQWPWPHNNOELCUQELTOELLIUMNTRLTWBRIFXQFXYOVEBKIPWKIUJOXCKXYBQWHACKYFJWIZOTTLNKLFCUMTHWJETVUKDZYGIVDEALJFTZZAMYIGIBCGBVBYWLBMBKRPSRTZLFHRMWWWSTFFNPTAZHIYEKBQAKWCVBJNAVRYUJNRKLADOXLCARCQE  offsets=(1,1,8)
ZQGQIGJJPTQCTVRDLUUSJZKZLWAVEOOHDADMNRUNNGQLHWWTQXNPBYOUEVMYBBBQAGBVELTVXANQFRJJMGDQBEXTPXLENIBUTJHPOFSLSNLOIGBYBXGVWRCAUDMYJJOYJZSQYURQJDSEZSTHYCMVMMORLHGLYZRWIXNAHMCFQFROERYNAVXBLHVKSVTLWAF  offsets=(1,1,9)
AIUWAPLHLJWKQJOVPDMBSNLTAMHAIJMRIVJZPSYUGTUMTAVSAYTOEZXRXQLCODATWAEXVHSAIKAAQAREHOJJHPQDKEPUSMRFETMKJPUGKQKUPTDHYWFUJOBLVRJEYGPGLDXIJBKJBFDKKVEMKUNHEXIDMZYNJRCXMBYNATGCUEYWHHVADSIMYURVZIDJGDM  offsets=(1,1,10)
------------------------------------------------------------

offsets=の部分は複合鍵の数値ですね。
こんな感じで、総当たりの17,576通りの表示が出てきました。

うむ。基本は日本文をローマ字出力しているだけなのですが、
この中から、意味のある配列を見つけ出すのは簡単ではなさそう。

まあ、それでも実際のエニグマとは違い、簡易版です、
時間があるなら、17,576通りをすべて見ていって、意味のある文章を見つける方法もあるでしょう。
しかし、実際のエニグマだっともっと複雑でパターンも多かったはずだし、
何より、現在のプログラムで一瞬で17,576通りの総当たりが可能でしたが、昔には
このパターンを出力するだけでもえらい時間がかかるはず。

というわけで、実際の解読にはどのようにしたか。

嘘か、誠か、有名な話。ドイツ軍の打電文章の中に
必ず、含まれる文字列があったので、そこから解読したという話なんですね。

そう。ドイツ軍というと、「ハイル ○ットラー」 とか。
後は、天気が必ず、入っていたという話もあるみたいです。

というわけで、暗号化の元になった文章にある文字列を組み込みました。
そうエンジニアが必ず使用する、あの言葉、「HELLO WORLD」です。

それでは、先ほどの総当たりファイルを「HELLO WORLD」で検索してみましょう。

出ました。一撃です。
------------------------------------------------------------
HELLOWORLDSEKAIWAKAIDOKUKANOUNANODAROUKAALGORISMWAKISOKUWOSAGASHISUUGAKUWASHINRIWOOIMOTOMERUSOREDEMONAOKANZENNARIKAINIWATODOKANAIDAGASONOFUKANZENSAKOSOGAJINRUIWOZENSHINSASERUGENDOURYOKUNANODA  offsets=(18,4,24)
------------------------------------------------------------

HELLOWORLD SEKAI WA KAIDOKU KANOU NA NO DAROU KA
ALGORISM WA KISOKU WO SAGASHI SUUGAKU WA SHINRI WO OIMOTOMERU
SOREDEMO NAO KANZEN NA RIKAI NI WA TODOKANAI
DA GA SONO FUKANZEN SA KOSO GA JINRUI WO ZENSHIN SASERU GENDOURYOKU NA NO DA

HELLOWORLD 世界は解読可能なのだろうか
アルゴリズムは規則を探し 数学は真理を追い求める
それでもなお 完全な理解には届かない
だがその不完全さこそが 人類を前進させる原動力なのだ


出来ました! 簡易とはいえ、エニグマ暗号を解読しました!
共通鍵も(18,4,24)とわかっているので、同じ共通鍵を使用する暗号文も解読可能です。


よくよく見てみると、恥ずかしい文章ですが、私が作ったものではないので許してね。