1. 動機
以下のページを読んで,Pythonで実装できそうだと思ったこと.
たった1枚の紙でナチス・ドイツの傑作暗号機エニグマを再現できる「Paper Enigma Machine」を使ってみた - GIGAZINE
2. 方針
- 3つのローターを左から順に1, 2, 3とし,ローター1の左側,ローター1の右側,ローター2の左側,……,ローター3の右側を表す6つのリストを定義する.
- 入力された文字列をリストに格納し,先頭から1文字ずつ次の操作を行う.
a. その文字 $ l_1 $ が,アルファベットの何番目であるか求める.($ n_1 $ とする)
b. ローター1の右側で同じく $ n_1 $ 番目にある文字 $ l_2 $ を求める.
c. $ l_2 $ がローター1の左側で何番目であるか求める.($ n_2 $ とする)
d. ローター2の右側で同じく $ n_2 $ 番目にある文字 $ l_3 $ を求める.
……
g. $ l_4 $ がローター3の左側で何番目であるか求める.($ n_4 $ とする)
h. リフレクターで同じく $ n_4 $ 番目にある文字 $ l_5 $ を求める.
i. $ l_5 $ がリフレクターで何番目であるか求める.($ n_5 \ (\neq n_4) $ とする)
j. ローター3の左側で同じく $ n_5 $ 番目にある文字 $ l_6 $ を求める.
……
p. アルファベットの $ n_8 $ 番目の文字 $ l_9 $ を求める.
3. 実装
from collections import deque
rt1_l = deque([_ for _ in range(26)]); rt2_l = deque([_ for _ in range(26)]); rt3_l = deque([_ for _ in range(26)])
rt1_r = deque([4, 10, 12, 5, 11, 6, 3, 16, 21, 25, 13, 19, 14, 22, 24, 7, 23, 20, 18, 15, 0, 8, 1, 17, 2, 9])
rt2_r = deque([0, 9, 3, 10, 18, 8, 17, 20, 23, 1, 11, 7, 22, 19, 12, 2, 16, 6, 25, 13, 15, 24, 5, 21, 14, 4])
rt3_r = deque([1, 3, 5, 7, 9, 11, 2, 15, 17, 19, 23, 21, 25, 13, 24, 4, 8, 22, 6, 0, 10, 12, 20, 18, 16, 14])
rf = [0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 10, 6, 12, 10, 12, 8, 4, 1, 5, 19, 2, 21, 21, 9, 0, 19]
alp_list = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ ')
lkup = lambda lst: alp_list[lst[0]]
b26 = lambda num: len(str(int(str(num), base = 26))) - 1
#各ローターの初期配置を決定する
print('\n' + 'Input three natural numbers you like with a space inbetween. (e.g. 2 7 18)')
print('\n' + 'To load the default setting, just press the ENTER key.' + '\n')
while True:
rc = input('INPUT> ') #rc:rollcount
if len(rc) == 0:
rc1 = 12; rc2 = 2; rc3 = 10
break
try:
rc1 = rc.split(' ')[0]; rc2 = rc.split(' ')[1]; rc3 = rc.split(' ')[2]
rc3 = int(rc3) % 26; rc2 = int(rc2) + b26(rc3); rc1 = int(rc1) + b26(rc2)
except IndexError:
print('\n' + 'ERROR: There are less than three numbers.' + '\n')
continue
except ValueError:
print('\n' + 'ERROR: Non-natural number/character(s) detected.' + '\n')
continue
else:
break
rt1_r.rotate(-rc1); rt1_l.rotate(-rc1); rt2_r.rotate(-rc2); rt2_l.rotate(-rc2); rt3_r.rotate(-rc3); rt3_l.rotate(-rc3)
print('\n' + 'Setting successfully applied.')
print('\n' + 'R1 R2 R3' + '\n' +lkup(rt1_l) + lkup(rt1_r), lkup(rt2_l) + lkup(rt2_r), lkup(rt3_l) + lkup(rt3_r))
#暗号化・複合化を繰り返せるようにするための無限ループ
while True:
output = ''
print('\n' + 'Type a word/sentence to be encrypted/decrypted using alphabets, and, spaces if preferred.' + '\n')
#入力された文字列が1文字以上のアルファベットとスペースのみで構成されていることを確認する
while True:
sent = list(input('INPUT> ').upper())
try:
ltrs_list = [_ for _ in sent if _ in alp_list]
except:
print('\n' + 'ERROR: Invalid character(s) detected.' + '\n')
continue
else:
pass
if len(ltrs_list) == ltrs_list.count(' '):
print('\n' + 'ERROR: There are no alphabets.' + '\n')
else:
break
print('\n' + 'R1 R2 R3')
for j in range(len(ltrs_list)):
n = alp_list.index(ltrs_list[j])
if n == 26:
output += ' ' #スペースは暗号化・複合化しない
else:
rc3 += 1; rt3_r.rotate(-1); rt3_l.rotate(-1) #初めに右のローターを一つ回す
if rc3 % 26 == 22:
rc2 += 1; rt2_r.rotate(-1); rt2_l.rotate(-1) #右のローターが一回転したら中央のローターを一つ回す
if rc2 % 26 == 5:
rc1 += 1; rt1_r.rotate(-1); rt1_l.rotate(-1) #中央のローターが一回転したら左のローターを一つ回す
rc1 = rc1 % (26 ** b26(rc1)); rc2 = rc2 % (26 ** b26(rc2)); rc3 = rc3 % (26 ** b26(rc3))
rf_in = rt1_l.index(rt1_r[rt2_l.index(rt2_r[(list(rt3_l).index(rt3_r[n]))])])
for rf_out in range(26):
if rf_out != rf_in and rf[rf_out] == rf[rf_in]:
result = alp_list[rt3_r.index(rt3_l[rt2_r.index(rt2_l[rt1_r.index(rt1_l[rf_out])])])]
output += result
print(lkup(rt1_l) + lkup(rt1_r), lkup(rt2_l) + lkup(rt2_r), lkup(rt3_l) + lkup(rt3_r), ltrs_list[j], ' -> ', result)
print('\n' + 'RESULT: ' + output + '\n' * 2 + 'You can continue with the current (not the initial) setting.')