蛇ノ目の記

技術のことも。そうでないことも。

「暗号解読」を読んでるのでシーザー暗号をPythonで書いてみる

最近、サイモン・シンの「暗号解読(上)」を読んでいる。主にライブ開演待ちの時間に読む程度なのでなかなか読み終わらない。 硬いタイトルだけど、暗号理論の難しい話は無くて紀元前から始まる暗号にまつわるエピソードがメインになっている。この手の科学読み物的な本はかなり好み。 シーザー暗号やらヴィジュネル暗号、エニグマまでが今のところ登場していて、それぞれのエピソードと並んでアルゴリズムについても触れられている。というわけで今回はシーザー暗号をPythonで書いてみる。

シーザー暗号 is 何

ユリウス・カエサルが使ったことからシーザー暗号と呼ばれている。 "A" →"C", "B" → "D" というように平文のアルファベットに常にひとつのアルファベットが対応する単一換字式暗号という種類の暗号。平文に含まれるアルファベットを平アルファベット、暗号に含まれるアルファベットを暗号アルファベットと呼ぶ。

シーザー暗号では、平文を暗号化するときに各文字をアルファベット順で何文字シフトするか、というのを鍵にする。

例えば、鍵が2であるときHello world は各文字を2つシフトさせて jgnnqyqtnfとなる。

このときの平アルファベットと暗号アルファベットの対応は以下のようになる。

平アルファベット a b c d E ...
暗号アルファベット C D E F G ...

Pythonでシーザー暗号

文字をシフトさせると聞いて、初めに思いついたのがord()Unicodeコードポイントに変換、そこに鍵となる値を足し、chr()で文字列に戻すという方法だった。

>>> ord('a')
97
>>> chr(ord('a')+2)
'c'

しかし、この場合zを暗号化するとUnicodeでこれらの文字の2文字隣にコードされている文字(=|)になってしまって具合がよくない。

>>> chr(ord('z')+2)
'|'

そこで、string.ascii_lowercaseを使ってアルファベット26文字を取得。これを平アルファベットとする。 暗号アルファベットはself.plain_alphabet[self.key:] + self.plain_alphabet[:self.key] のように、平アルファベットのkey文字目移行のスライスと、key文字目までのスライスを組み合わせて作る。暗号化は平文の各文字の平アルファベット上の位置を、暗号アルファベット上の位置に置き換えることで行う。

import string

from typing import List


class Caesar:
    def __init__(self, msg: str, key: int):
        self.msg = msg
        self.key = key

    @property
    def plain_msg(self) -> str:
        return ''.join(
            [c.lower() for c in self.msg if c.lower() in self.plain_alphabet]
        )

    @property
    def plain_pos(self) -> List:
        alphabet_dict = {}
        for idx, c in enumerate(self.plain_alphabet):
            alphabet_dict[c] = idx
        return [alphabet_dict[c] for c in self.plain_msg]

    @property
    def plain_alphabet(self) -> str:
        return string.ascii_lowercase

    @property
    def encrypted_alphabet(self) -> str:
        return self.plain_alphabet[self.key:] + self.plain_alphabet[:self.key]

    def encrypt(self) -> str:
        # ここでは暗号は大文字で表す
        return ''.join(
            [self.encrypted_alphabet[pos] for pos in self.plain_pos]
        ).upper()

    def decrypt(self) -> str:
        # ここでは平文は小文字で表す
        return ''.join(
            [self.plain_alphabet[pos] for pos in self.plain_pos]
        ).lower()

アルファベット26文字すべて登場することから、暗号化がわかりやすくなるのでフォントの例文によく使われるアレを使う。こういうのをパングラムって言うらしい。

鍵は2なので t は 2文字シフトしてV に、hも同様に2文字シフトしてJに暗号化されている。、zは一周してBに、yAに暗号化されている。

>>> from caesar import Caesar
>>> msg = 'The quick brown fox jumps over the lazy dog'
>>> key = 2
>>> caesar = Caesar(msg=msg, key=key)
>>> cipher = caesar.encrypt()
>>> cipher
'VJGSWKEMDTQYPHQZLWORUQXGTVJGNCBAFQI'
>>> plain = caesar.decrypt()
>>> plain
'thequickbrownfoxjumpsoverthelazydog'

というわけでシーザー暗号をPythonで書いてみる話だった。

初心者の課題とかにもいいかも、とか思った。