1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

エニグマをpythonで実装

Last updated at Posted at 2020-12-30

初めに

エニグマという響きがかっこよく、過去・現代・未来関係なく暗号にはロマンを感じます。作ってみたいという欲求やpythonの練習になると思い、今回実装してみました。
qiitaにも実装してみたという記事、詳細な説明記事などがたくさんあります。
以下は特に参考になった記事です。

エニグマとは

wikipediaが歴史背景含め参考になります。

実装手順

簡単な線形代数で考えるエニグマ暗号 https://qiita.com/tommyecguitar/items/5e07b622eaa329ed78a2
が文字の入れ替えを実行するうえで大変参考になりました。

以下は実装するうえで基本としたことです。

  • アルファベットA~Zを26次元のベクトルとみなす
    • A・・・(1, 0, 0, ・・・, 0)
    • B・・・(0, 1, 0, ・・・, 0)
    • ・・・
    • Z・・・(0, 0, 0, ・・・, 1)
  • 26次の単位行列 $I$ を作る
  • アルファベットの $i$ 番目と $j$ 番目の入れ替えは $I$ の $i$ 行目と $j$ 行目を入れ替えた行列を右からかける
      • 文字AとBの入れ替えは $I$ の1行目と2行目を入れ替えた行列を右からかける
      • 文字AとCの入れ替えは $I$ の1行目と3行目を入れ替えた行列を右からかける
  • 簡単にするため、プラグボードとリフレクターの設定は引数で渡す
  • ローターの設定はrandomモジュールを使用

こちらに置きました。

import numpy as np
import random


class Enigma():
    def __init__(self, plane, alphabet, plug_perm, ref_perm):
        self.alphabet = alphabet
        self.char_dict = {x: n + 1 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 = random.sample([n for n in range(1, 27)], self.size)
        self.rot2_perm = random.sample([n for n in range(1, 27)], self.size)
        self.rot3_perm = random.sample([n for n in range(1, 27)], self.size)
        self.plane = plane

エニグマの設定を行列で表す



    def get_matrix(self, cnt1, cnt2, cnt3):
        rot_rotor_perm = [(x + 1) % self.size for x in range(1, self.size + 1)]

        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 - 1] for n in self.plug_perm])
        reflector = np.mat([self.identity[n - 1] for n in self.ref_perm])
        rotor1 = np.mat([self.identity[n - 1] for n in self.rot1_perm])
        rotor2 = np.mat([self.identity[n - 1] for n in self.rot2_perm])
        rotor3 = np.mat([self.identity[n - 1] 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 = ''
        tmp = ''
        cnt1 = 0
        cnt2 = 0
        cnt3 = 0
        for char in text:
            tmp = ''
            if char not in self.alphabet:
                tmp += char
            else:
                vec = [0] * self.size
                vec[self.char_dict[char] - 1] = 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 = (vec * m).A[0]
                for n, x in enumerate(vec):
                    if int(x) == 1:
                        for key, value in self.char_dict.items():
                            if n == value - 1:
                                tmp = key
                                break
                        else:
                            continue
                        break
            after += tmp
        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]
    enigma = Enigma(plane, alphabet, plug_perm, ref_perm)
    cipher = enigma.get_text(plane)
    decrypt = enigma.get_text(cipher)
    print(cipher)
    print(decrypt)

実行結果

VNPKB NPJCN. CG GNXF QP JTRTS. DZKS BW UCMB NCW.
HELLO WORLD. MY NAME IS ALICE. NICE TO MEET YOU.

修正 1

  • コメントで指摘して頂いたので修正
    • char_dict の value は 0 始まりに修正
      • これに伴い、n - 1 だった個所はすべて n に修正
      • col_perm_mat = np.mat([self.identity[n - 1] for n in rot_rotor_perm]) はローターを回す際必要なので n - 1 で固定
    • 文字連結について、\ で改行をやめ () で連結
    • 以下の暗号化した文字を取得する箇所を簡略化

for n, x in enumerate(vec):
    if int(x) == 1:
        tmp = self.alphabet[n]
        break

修正前とは見違えるように読みやすくなりました。

class Enigma():
    def __init__(self, plane, alphabet, plug_perm, ref_perm):
        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 = random.sample(range(self.size), self.size)
        self.rot2_perm = random.sample(range(self.size), self.size)
        self.rot3_perm = random.sample(range(self.size), self.size)
        self.plane = plane

    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 = ''
        tmp = ''
        cnt1 = 0
        cnt2 = 0
        cnt3 = 0
        for char in text:
            tmp = ''
            if char not in self.alphabet:
                tmp += char
            else:
                vec = [0] * 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 = (vec * m).A[0]
                for n, x in enumerate(vec):
                    if int(x) == 1:
                        tmp = self.alphabet[n]
                        break
            after += tmp
        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]
    plug_perm = [x - 1 for x in plug_perm]
    ref_perm = [x - 1 for x in ref_perm]
    enigma = Enigma(plane, alphabet, plug_perm, ref_perm)
    cipher = enigma.get_text(plane)
    decrypt = enigma.get_text(cipher)
    print(cipher)
    print(decrypt)

修正2

  • コメントで指摘して頂いたので修正

    • rotor2 が 1/26 回転した直後から、rotor3 がずっと回転した状態になっていた
  • 修正前

cnt1 += 1
if cnt1 % self.size == 0:
    cnt2 += 1
if cnt2 % self.size == 0:
    cnt3 += 1
  • 修正後
cnt1 += 1
if cnt1 % self.size == 0:
    cnt2 += 1
if cnt1 % self.size ** 2 == 0:
    cnt3 += 1

修正3

  • コメントで教えて頂いた方法
    • divmod()で以下を実現
      • cnt2, cnt3 のインクリメント不要(cnt1 のインクリメントを用いる)
      • 26で割りきれるかどうかに関する条件分岐不要
      • 常に 0 $\leq$ cnt1. cnt2. cnt3 $\leq$ 25

こちらを使わせて頂きたいと思います。

  • 修正前
    def get_text(self, text):
        after = ''
        tmp = ''
        cnt1 = 0
        cnt2 = 0
        cnt3 = 0
        for char in text:
            tmp = ''
            if char not in self.alphabet:
                tmp += char
            else:
                vec = [0] * 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 = (vec * m).A[0]
                for n, x in enumerate(vec):
                    if int(x) == 1:
                        tmp = self.alphabet[n]
                        break
            after += tmp
        return after
  • 修正後(※以下はコメント欄から引用しています)
    def get_text(self, text):
        after = ''
        cnt = 0
        for char in text:
            if char not in self.alphabet:
                after += char
                continue
            vec = [0] * self.size
            vec[self.char_dict[char]] = 1
            cnt += 1
            cnt2, cnt1 = divmod(cnt, self.size)
            cnt3, cnt2 = divmod(cnt2, self.size)
            m = self.get_matrix(cnt1, cnt2, cnt3 % self.size)
            vec = (vec * m).A[0]
            for n, x in enumerate(vec):
                if int(x) == 1:
                    after += self.alphabet[n]
                    break
        return after

参考記事

1
3
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?