エニグマ暗号をPythonで実装した

# 1. 動機

以下のページを読んで，Pythonで実装できそうだと思ったこと．

[たった1枚の紙でナチス・ドイツの傑作暗号機エニグマを再現できる「Paper Enigma Machine」を使ってみた - GIGAZINE](https://gigazine.net/news/20201123-paper-enigma/)

# 2. 方針

1. 3つのローターを左から順に1, 2, 3とし，ローター1の左側，ローター1の右側，ローター2の左側，……，ローター3の右側を表す6つのリストを定義する．
&#13;&#10;
&#13;&#10;
2. 入力された文字列をリストに格納し，先頭から1文字ずつ次の操作を行う．
&#13;&#10;
    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. 実装

```Python
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.')
```