エニグマ暗号はPythonで解読できないのだろうか?
背景
NHKの番組表を見ていた。番組は見ていないのだが内容は大体想像できる。
エニグマ暗号はPythonで解読できるのだろうか?
エニグマ暗号機とは
ここを参考
シーザー暗号の改善版でしょう
キーを打つごとに、1文字ごとに、3枚の(のちに4枚)ローターが回転して、ずらし量を変えて行く暗号なわけです。
エニグマ暗号機をPythonで実装
この方のサイトのコードを改変しました。
スクリプト
ローターの数値を固定しました。ローターは3種類です。
ローターの初期位置を設定可能に変更しました。
import numpy as np
import random
class Enigma():
def __init__(self, plane, alphabet, rotor1, rotor2, rotor3, plug_perm, ref_perm ,enigma_x, enigma_y, enigma_z):
self.alphabet = alphabet
self.char_dict = {x: n for n, x in enumerate(alphabet)}
self.size = len(alphabet)
self.identity = np.eye(self.size)
self.plug_perm = plug_perm
self.ref_perm = ref_perm
self.rot1_perm = rotor1
self.rot2_perm = rotor2
self.rot3_perm = rotor3
self.plane = plane
self.enigma_x = enigma_x
self.enigma_y = enigma_y
self.enigma_z = enigma_z
def get_matrix(self, cnt1, cnt2, cnt3):
rot_rotor_perm = list(range(self.size))
col_perm_mat = np.mat([self.identity[n - 1] for n in rot_rotor_perm])
row_perm_mat = col_perm_mat.T
plugbord = np.mat([self.identity[n] for n in self.plug_perm])
reflector = np.mat([self.identity[n] for n in self.ref_perm])
rotor1 = np.mat([self.identity[n] for n in self.rot1_perm])
rotor2 = np.mat([self.identity[n] for n in self.rot2_perm])
rotor3 = np.mat([self.identity[n] for n in self.rot3_perm])
m = (plugbord *
(row_perm_mat ** cnt1) * rotor1 * (col_perm_mat ** cnt1) *
(row_perm_mat ** cnt2) * rotor2 * (col_perm_mat ** cnt2) *
(row_perm_mat ** cnt3) * rotor3 * (col_perm_mat ** cnt3) *
reflector *
((row_perm_mat ** cnt3) * rotor3 * (col_perm_mat ** cnt3)).I *
((row_perm_mat ** cnt2) * rotor2 * (col_perm_mat ** cnt2)).I *
((row_perm_mat ** cnt1) * rotor1 * (col_perm_mat ** cnt1)).I *
plugbord)
return m
def get_text(self, text):
after = ''
cnt1 = self.enigma_x
cnt2 = self.enigma_y
cnt3 = self.enigma_z
for char in text:
if char not in self.alphabet:
after += char
else:
vec = np.zeros(self.size)
vec[self.char_dict[char]] = 1
cnt1 += 1
if cnt1 % self.size == 0:
cnt2 += 1
if cnt2 % self.size == 0:
cnt3 += 1
m = self.get_matrix(cnt1, cnt2, cnt3)
vec = np.dot(vec, m).A[0]
for n, x in enumerate(vec):
if int(x) == 1:
after += self.alphabet[n]
break
return after
if __name__ == "__main__":
alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
plane = 'HELLO WORLD. MY NAME IS ALICE. NICE TO MEET YOU.'
plug_perm = [2, 1, 4, 3, 5, 6, 7, 8, 9, 16, 11, 12, 20, 14,
15, 10, 17, 18, 19, 13, 21, 22, 23, 24, 25, 26]
ref_perm = [26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15,
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
rotor1 = [19, 3, 13, 12, 14, 23, 4, 21, 25, 9, 5, 18, 24, 2, 20, 17, 6, 11, 7, 1, 15, 16, 10, 0, 8, 22]
rotor2 = [1, 2, 19, 18, 20, 11, 0, 10, 7, 12, 23, 13, 9, 22, 21, 3, 5, 24, 8, 17, 25, 6, 4, 15, 16, 14]
rotor3 = [18, 6, 0, 19, 22, 10, 24, 15, 21, 9, 7, 17, 16, 14, 3, 1, 23, 25, 4, 5, 11, 2, 8, 20, 13, 12]
plug_perm = [x - 1 for x in plug_perm]
ref_perm = [x - 1 for x in ref_perm]
enigma = Enigma(plane, alphabet, rotor1, rotor2, rotor3, plug_perm, ref_perm, 3, 4, 5)
cipher = enigma.get_text(plane)
decrypt = enigma.get_text(cipher)
print(f"Cipher Text: {cipher}")
print(f"Decrypted Text: {decrypt}")
出力結果
HELLO→MRQNT
と暗号化されるわけです。
解読は?
選択暗号文攻撃 (CCA)を用います。任意の暗号文(ただし解読対象の暗号文は除く)に対応する平文が得られるとします。
「ミッドウェーで水が不足している」と嘘を敵に流せば、「ミッドウェー(Midway)」と「水(water)」の単語が推測されるわけです。
ローターとプラグボードは「何らかの手段」で入手することとします(沈んだ潜水艦からエニグマ暗号機を回収する映画がありましたよね。)
解析する必要があるのが、初期のローターの回転位置(26x26x26)とローターの配置(6通り)
HELLO→MRQNT
となるローターの回転位置を総当たりで求めるとします。
コードは
for x in range(len(alphabet)):
for y in range(len(alphabet)):
for z in range(len(alphabet)):
enigma = Enigma(plane, alphabet, rotor1, rotor2, rotor3, plug_perm, ref_perm, x, y, z)
cipher2 = enigma.get_text(plane)
if decrypt[0:5] == enigma.get_text(cipher[0:5]):
print(f"Found matching setting: {decrypt[0:5]}")
print(x, y, z)
結果、
数分で求めることができました。初期位置(3,4,5)なので。総当たりでも数10分でしょう。
ローターの配置(6通り)も総当たりで何とかなるのでは。
一旦、ローターの位置が解析されてしまえば、次の暗号のローターの初期位置を現在の暗号で送信するので、次の暗号もバレてしまうわけです。(軍事的にも公開鍵暗号方式の技術は必要なんですね。)
所感
>エニグマ暗号機とローターとプラグボードは「何らかの手段」で入手する
これが無理なのでしょうね。
ローターとプラグボードを入手できない場合は総当たりでは無理っぽいよね。
潜水艦から入手?(笑
とはいえ、エニグマ暗号機は敵に鹵獲されることを前提とした暗号機です。80年後は個人の計算機で総当たり攻撃できちゃうとは、当時は思ってもみなかったかもしれませんね。