【Python】シーザー暗号プログラム

2023年1月11日

Pythonでシーザー暗号のプログラムの解説をします。

シーザー暗号とは

シーザー暗号は古代ローマの軍人ジュリアス・シーザーにちなんで名づけられた暗号です。

シーザー暗号はアルファベットをシフトして、メッセージの各文字を新しい文字に置き換えるものです。

例えばメッセージ内のすべてのAをDに、BをEに、CをFに変えていきます。

アルファベットの最後を越える場合は最初に戻ります。

XをA、YをB、ZをCということです。

暗号ホイール

シーザー暗号で簡単に暗号化する方法はいろいろありますが代表的なものに暗号ホイールがあります。

暗号ホイールは2つの円盤で構成されていて、アルファベットの場合文字数の26個の枠があります。

外側の円盤は平文の文字を表し、内側の円盤は対応する暗号文の文字を表します。

内側の円盤の文字には0から25までの数字が付けられています。

この数字は暗号鍵といい、シフトする文字数を表します。

下の表は暗号ホイールを表にしたものです。

内側 外側 番号
A V 0
B W 1
C X 2
D Y 3
E Z 4
F A 5
G B 6
H C 7
I D 8
J E 9
K F 10
L G 11
M H 12
N I 13
O J 14
P K 15
Q L 16
R M 17
S N 18
T O 19
U P 20
V Q 21
W R 22
X S 23
Y T 24
Z U 25

もし暗号鍵が25より大きいとアルファベットがラップアラウンド(最初に戻る)します。

そのため26でシフトするのは0でシフトすることと同じで、27でシフトするのは1でシフトすることと同じです。

暗号ホイールで暗号化

今回はメッセージ「THIS IS A PEN」を暗号化させます。

まず内側と外側の円盤の文字が一致していることを確認します(AとAなど)。

次に内側の円盤をシフトしたい数だけ時計回りに回転させます。

このとき外側の円盤でのAに対応する番号が暗号鍵になります。

上の表の場合だと暗号鍵は5ということになります。

暗号
T Y
H M
I N
S X
   
I N
S X
   
A F
   
P U
E J
N S

メッセージ内の各文字を外側の円盤から探し、対応している内側の円盤の文字に置き換えていきます。

最初の文字Tは内側の円盤ではYに対応しているので置き換えます。

これを順番にやっていくのですが今回の場合IとSはそれぞれ2つずつあります。

それらはそれぞれ同じ文字に変換されるので1回調べたらメッセージ内のすべてを置き換えておくと時間が節約できます。

ただしスペース文字(空白)はそのままになります。

このようにすることで「YMNX NX F UJS」と暗号化できます。

暗号ホイールで復号

次に暗号文「YMNX NX F UJS」を復号します。

復号する際に暗号鍵がわからないと余程のハッカーでもない限りは解読は難しいです。

ですが今回は暗号鍵5が使用されていると知っている前提にします。

まず内側と外側の円盤の文字が一致していることを確認します(AとAなど)。

次に内側の円盤の暗号鍵の数字がある文字と、外側の円盤の文字Aを対応させるようにします。

暗号
Y T
M H
N I
X S
   
N I
X S
   
F A
   
U P
J E
S N

内側の円盤で文字Yを探して、外側の円盤で対応する文字Tを見つけます。

同様のことを残りの文字すべてでやっていきます。

こうすることでメッセージ「THIS IS A PEN」が復号できました。

やり方は暗号化と真逆です。

計算でシーザー暗号の暗号化と復号

シーザー暗号の暗号化や復号には計算でも可能です。

そのための準備としてアルファベットのAからZまでを数字0から25までに対応させます。

アルファ
ベット
番号
A 0
B 1
C 2
D 3
E 4
F 5
G 6
H 7
I 8
J 9
K 10
L 11
M 12
N 13
O 14
P 15
Q 16
R 17
S 18
T 19
U 20
V 21
W 22
X 23
Y 24
Z 25

暗号化

シーザー暗号で暗号化することを計算で実現するにはまず暗号化したい文字の番号を調べます。

そしてその数に暗号鍵の数を加算します。

例えばDOGを暗号鍵3で暗号させるとします。

このとき文字D、O、Gは番号でそれぞれ3、14、6です。

この番号に暗号鍵の3をそれぞれ足して3+3=6、14+3=17、6+3=9となります。

この加算された番号に対応する文字を探すとそれぞれG、R、JなのでGRJと暗号化できます。

次にPOWを暗号鍵5で暗号化するとします。

文字P、O、Wの番号はそれぞれ15、14、22なので暗号鍵5を足してそれぞれ15+5=20、14+5=19、22+5=27となります。

ところが番号は25までしかないので文字と暗号鍵の加算結果が26以上の場合は26を引く必要があります。

よってWの暗号化後の番号は27-26=1となるのでUTBと暗号化できます。

復号

復号は暗号鍵の数を引くことで可能になります。

例えば暗号文XPDを暗号鍵5で復号するとします。

文字X、P、Dはそれぞれ番号23、15、3です。

それぞれの番号から暗号鍵の数を引いて23-5=18、15-5=10、3-5=-2です。

3-5=-2のように番号から暗号鍵を引いた計算結果が0未満の場合は26を足す必要があります。

よって-2+26=24となるので復号結果はSKYとなります。

シーザー暗号のソースコード

コード例

# シーザー暗号(caesarCipher.py)

# 暗号化・復号する文字列
message = input('文字列 : ')

# 暗号化・復号の鍵
key = int(input('鍵の値 : '))

# 暗号化(encrypt)するか復号(decrypt)するか
mode = input('encrypt(暗号化)/decrypt(復号) : ')

# 暗号化できるシンボル
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'

# メッセージの暗号化・復号の結果を格納
translated = ''

for symbol in message:
    # 文字列SYMBOLに含まれるシンボルのみを暗号化・復号
    if symbol in SYMBOLS:
        symbolIndex = SYMBOLS.find(symbol)

        # 暗号化・復号を実行
        if mode == 'encrypt':
            translatedIndex = symbolIndex + key
        elif mode == 'decrypt':
            translatedIndex = symbolIndex - key

        # 必要であればラップアラウンド処理
        if translatedIndex >= len(SYMBOLS):
            translatedIndex -= len(SYMBOLS)
        elif translatedIndex < 0:
            translatedIndex += len(SYMBOLS)

        translated += SYMBOLS[translatedIndex]
    else:
        # 暗号化・復号しないでそのままシンボル追加
        translated += symbol

# 暗号化・復号後の文字列を表示
print(translated)

実行結果(暗号化)

文字列 : This is a pen.
鍵の値 : 15
encrypt(暗号化)/decrypt(復号) : encrypt
iwx8Lx8LpL5t3O

実行結果(復号)

文字列 : iwx8Lx8LpL5t3O
鍵の値 : 15
encrypt(暗号化)/decrypt(復号) : decrypt
This is a pen.

input関数で文字を入力させる

変数message、key、modeではinput()関数でキーボードからの文字の入力を促します。

messageでは暗号化・復号させたい文字列を入力させています。

keyでは鍵の値を入力させてその文字列をint関数でint型に変換しています。

modeでは暗号化・復号のどちらをしたいかを選択させています。

定数に暗号化可能な文字を格納

変数SYMBOLSには暗号化の対象となる文字を格納しています。

変数名を大文字にすることで定数ということをプログラマーに知らせます。

基本はこの変数を変更することはないし、変更してしまうと暗号化・復号で違う結果になることもあります。

その後に空の文字列の変数translatedを作ります。

for文で文字列の暗号化・復号

for文では変数messageを1文字ずつ取り出して変数symbolに格納します。

この後にそれぞれの文字を暗号化・復号するかどうかをif文で判定しています。

in演算子で暗号化可能な文字を探索

if symbol in SYMBOLS:でSYMBOLS(に格納された文字の中)にsymbol(に格納された文字)が含まれているかを判定しています。

含まれている場合は次のブロックに移動します。

find()メソッドでインデックス検索

SYMBOLS.find(symbol)でSYMBOLSからsymbolにある文字のインデックスを探します。

そのインデックスを変数symbolIndexに格納しています。

暗号化・復号実行

if文とelif文でmodeに格納された文字により暗号化・復号を実行します。

暗号化する際は対応する文字の数字に暗号鍵を加算することで変換後の文字のインデックスが求まります。

逆に復号する際は対応する文字の数字に暗号鍵を減算することで変換後の文字のインデックスが求まります。

なので変数symbolIndexに暗号鍵keyを暗号化(encrypt)なら加算、復号(decrypt)なら減算しています。

その加減算の結果を変数translatedIndexに格納しています。

ラップアラウンド処理

変数translatedIndexに格納されている値は0未満や変数SYMBOLSの最大インデックスを越える場合があります。

その時暗号化では変数SYMBOLSの要素数の値を引く、復号では足すことで正常な値になります。

translatedIndex >= len(SYMBOLS)つまり最大インデックスを越える(最大インデックスは要素数-1)なら

translatedIndexからlen(SYMBOLS)つまりSYMBOLSの要素数を減算します。

translatedIndex < 0ならlen(SYMBOLS)を加算します。

こうすることでtranslatedIndexをSYMBOLSのインデックス範囲内に当てはめることができます。

累算代入+=で変換後の文字を格納

変数translatedIndexには変換後の文字に対応するインデックス値が格納されています。

よってSYMBOLS[translatedIndex]は変換後の文字が格納されています。

それを累算代入+=で変数translatedの終端に連結させて格納しています。

未変換の文字の処理

messageの中にはSYMBOLSに存在しない文字が入っている可能性があります。

そのような文字は変換せずにそのまま変数translatedに追加させます。

translated += symbolが含まれるelif文はif symbol in SYMBOLS:とペアになっています。

そのif文で変換不能と判断された文字はこのelif文でただ単に追加されるだけになります。

最後にprint関数で変換後の文字列が表示されます。

シーザー暗号の解読プログラムソースコード

コード例

# シーザー暗号の解読(caesarhacker.py)

message = 'iwx8Lx8LpL5t3O'
SYMBOLS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'

# 候補となるすべての鍵でループ
for key in range(len(SYMBOLS)):
    # ループするたびに空文字列に初期化
    translated = ''

    # message内の各シンボルでループ
    for symbol in message:
        if symbol in SYMBOLS:
            symbolIndex = SYMBOLS.find(symbol)
            translatedIndex = symbolIndex - key

            # ラップアラウンド処理
            if translatedIndex < 0:
                translatedIndex += len(SYMBOLS)

            # 解読したシンボルを追加
            translated += SYMBOLS[translatedIndex]

        else:
            # 復号せずにシンボル追加
            translated += symbol

    # すべての復号結果を表示
    print(f'Key {key:2}: {translated}')

実行結果

Key  0: iwx8Lx8LpL5t3O
Key  1: hvw7Kw7KoK4s2N
Key  2: guv6Jv6JnJ3r1M
Key  3: ftu5Iu5ImI2qzL
Key  4: est4Ht4HlH1pyK
--略--
Key 11: XlmwAmwAeAtirD
Key 12: Wklv.lv.d.shqC
Key 13: Vjku?ku?c?rgpB
Key 14: Uijt!jt!b!qfoA
Key 15: This is a pen.
--略--
Key 61: n23?Q3?QuQ0y8T
Key 62: m12!P2!PtP9x7S
Key 63: lz1 O1 OsO8w6R
Key 64: kyz0Nz0NrN7v5Q
Key 65: jxy9My9MqM6u4P

総当たり攻撃によるシーザー暗号の解読

総当たり攻撃とは候補となりうるすべての解読用の鍵を使って解読していく方法です。

シーザー暗号の鍵の候補となるのは暗号化対象の文字列の長さに相当します。

そのすべての鍵で復号していき、どれが平文になるかを調べます。

総当たり攻撃の準備

総当たり攻撃するにあたっての準備として変数translatedを空文字列に初期化しています。

これをやらないと1つ目の鍵で復号した文の後ろに次の鍵で復号した文がそのままつながってしまうためです。

それを防ぐためにループの最初で初期化をしています。

f文字列で書式化

最後の行ではf文字列を使って書式化しています。

変数keyを表示させる際に見た目を整えるために2桁で表示させるようにしています。

シーザー暗号の最大の弱点

シーザー暗号の最大の弱点は暗号化で使用できる鍵の数が非常に少ないことです。

今回の場合は66個しかないので数秒で解読できてしまいます。

学習用には最適ですが安全性を確保するにはもっと使用できる鍵が多い暗号でないといけません。

Python

Posted by ほりえりお