8
1

More than 3 years have passed since last update.

Pythonでシーザー暗号(漢字含む)

Last updated at Posted at 2019-10-31

シーザー暗号

シーザー暗号 - Wikipedia
アルファベットを指定された文字数分ズラすという、原始的な暗号化手法です。
最も単純な換字式暗号の一つであり、文字から文字に1文字単位で変換する方式(単純換字暗号)だそうです。
暗号というよりは、いわゆる「鏡文字」みたいなものなので、まじめに暗号化するものとは少し違うかと思われます。

実装(アルファベット・数字のみ)

アルファベット、数字のみの場合は、pythonの組み込み関数のROT13を使えばできます。
codecsモジュールをインポートしてcodecs.decode()を使って変換します。
Wiki曰く、伝統的にこういう場合はシフト数は13らしいです。
13にしておけば、アルファベットは26文字なので、同じ関数で暗号化と複合化をこなせるからかと思われます。
ちなみにシーザー暗号ではシフト数は3だったそうですね。

import codecs
codecs.decode('Hello, World!', 'rot13')
# 'Uryyb, Jbeyq!'
codecs.decode('Uryyb, Jbeyq!', 'rot13')
# 'Hello, World!'

実装(漢字含む)

今回は、漢字を含む文字列のデータのマスキングが必要だったので、自前で作りました。
ただし、以下の通りの制約を設けました。

  • 制約
    • 記号 そのまま(変換しない)
    • アルファベット 数字はkeyで設定した文字分だけズラす。ズラす先はループして指定(X→key:5→C)
    • かな・カナ・漢字 1文字以上(次に存在する文字が存在するまで※)

※かなカナ漢字は、keyの数字に関係なく1文字ズラすコードにしています。理由は後述します。

以下、実装したコードです。
plaintextの部分に文字列を渡して、出力として暗号化された文字列が返ってくる関数です。
keyの情報は関数内に入れ込みました。

def caesar(plaintext):
    key=13    # アルファベットおよび数字について文字コードをズラす数を決める
    enc="cp932"    # 文字コードを指定する
    ciphertext = ""  # 暗号化した文字を1文字ずつ追加していくための変数を定義しておく
    for ch in list(plaintext): #1文字ずつ走査し、if分を使って文字コードの数字から文字の種類を判定する
        #記号
        if (' ' <= ch <= '/') or (':' <= ch <= '@') or ('[' <= ch <= '`') or ('{' <= ch <= '~') or ('、' <= ch <= '◯') :
            ciphertext +=chr(ord(ch))
        #A-Z
        elif 'A' <= ch <= 'Z':
            ciphertext += chr((ord(ch) - ord('A') + int(key)) % 26 + ord('A'))
        #a-z
        elif 'a' <= ch <= 'z':
            ciphertext += chr((ord(ch) - ord('a') + int(key)) % 26 + ord('a'))
        #0-9
        elif '0' <= ch <= '9':
            ciphertext += chr((ord(ch) - ord('0') + int(key)) % 10 + ord('0'))
        #その他(ひらがなカタカナ漢字など)
        else:
            byte=bytearray(ch.encode(enc)) # 文字コードを指定してエンコードする、この出力は2バイトのbyte列になるのでbytearrayに変換してから扱う
            while(1): # 文字が存在する文字コードにぶつかるまで、1ずつバイトをズラしていく
                try:
                    try:
                        byte[-1]+=0x01  # 末尾バイト部分を1bitズラす、
                    except:
                        byte[-1]=0x00   # 繰り上がり考慮。末尾バイトを00に戻して次バイトをインクリメントする
                        byte[-2]+=0x01  
                    x=byte.decode(enc)  # encでデコードしてみて、エラー発生を検知する
                except:
                    pass
                else:
                    break
            ciphertext += x
    return ciphertext

補足

漢字は次に存在する文字までズラす、という意味を説明します。
漢字の場合、次の番地に文字が定義されていないケースがあります。
たとえば、文字が存在しない文字コードの例が以下です。

文字が存在しない文字コード.png

この場合、"入"のとなりの番地には文字が指定されていません。
このとき、文字コードを+1してからその文字コードを文字に直そうとすると(0x93FC→0x93FD)、
0x93FDという文字は存在しないため、エラーとなってしまいます。
したがって、文字が存在する番地になるまで、+1を繰り返す、という動作を加えています。
この場合、"入"は"如"になります。
ただし、今回はズラす量を1と想定しているので、これ以上なにもしていませんが、
もし2以上にしたい場合、「"乳"も"入"も、"如"に暗号化されてしまう」みたいな状況になるので、相応の改変してください。
この関係上、かなカナ漢字は「keyで指定した文字数」に関係なく「1つだけ隣にズラす」というプログラムになっています。
とにかく、ズレて戻せさえすればそれでよかったのでそうしました。

※もろもろの判定にtry-except構文を使っているのはさすがにひどいような気がします。
取り急ぎ仕事で動けばよかっただけなので、だれか追記お願いします。

8
1
0

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
8
1