こうかの雑記

こうかの雑記

昔の懐かしいこと、ubuntuのこと、その他いろいろ

pythonで共通鍵暗号を試してみた

 しばらく前から暗号化に興味を持ちました。python用のライブラリーpycryptodomeの存在を知りましたので、暗号化を試してみました。

 今回はpycryptodomeのドキュメントにある例に手を加えて試しました。

 なお、pycryptodomeのドキュメントは残念ながら英語ですが、google翻訳で十分読めます。  Examples(例)API documentationのCrypto.Cipherの中のModern modes of operation for symmetric block ciphers(対称ブロック暗号の最新の動作モード)を読めば何とかなります。

PyCryptodomeのインストール

 PyCryptodomeはPyCryptoのforkで、PyCryptoと互換性があります。PyCryptoがインストールされていない場合、次の形でインストールできます。

$ pip3 install pycryptodome

試した暗号方式

 Examples(例)の最初の例として挙げられていた共通鍵方式のAESで、EAXモードです。

 例には公開鍵方式もありましました。公開鍵方式の利用例としてはマイナンバーカードなが挙げられます。

お試しスクリプトについて

 今回、試しに書いたスクリプトは暗号化と復号化をし、暗号化されたものと暗号キーをそれぞれ別のファイルに保存します。暗号化されたデータと暗号キーはバイナリーなので、可読文字に変換するためにBase64JSONも使っています。

 暗号化すると、暗号データとnonce、認証タグができます。復号する時には暗号キーの他にこのnonceと認証タグも必要で、これらを用いて正しく暗号化/復号化できたことが確認されるようです。

 暗号化されたデータとnonce、認証タグはセットにして一つのファイルとして出力してみました。暗号キーは一緒に保管すると意味ないので別ファイルにしてみました。今回はnonce、認証タグを暗号化されたデータとセットにしましたが、これが適切かどうかわかりません。

 今回は試すだけなので簡単に端末画面でキーボードで操作するようにしました。

  • '0' をEnterすると暗号キーを作成し、ファイル'testkey'に保存します。
  • '1' をEnterすると暗号化します。暗号化前のデータはキーボードから適当に入力します。
    暗号化されたデータはファイル'testdata'に保存します。
  • '3' をEnterすると暗号化されデータファイル'testdata'と暗号キーファイル'testkey'を読み込んで復号します。
    復号結果を表示します。

f:id:koukaforest:20200929124221p:plain
VScodeで実行時のターミナルの様子

 復号化してできた復号文が原文と同じであれば成功です。暗号キーを変えると当然復号で失敗します。

お試しスクリプト

#!/usr/bin/python3
""" AES(EAXモード)での暗号化とbase64、JSON処理 動作確認
"""
from Crypto.Cipher import AES               # pycryptodome
from Crypto.Random import get_random_bytes  # pycryptodome
from base64 import b64encode
from base64 import b64decode
import json

""" 暗号化するためキー(バイト型)を生成する。
"""
def create_key():
    key = get_random_bytes(16)              # 乱数でキー(byte型)を作成
    return(key)

""" 与えられて原文(str型)を暗号化キー(バイト型)で暗号化し、結果を返す
"""
def encrypt(text_b, key):
    cipher = AES.new(key, AES.MODE_EAX)     # キーを用いてEAXモードで暗号インスタンス化
    ciphertext, tag = cipher.encrypt_and_digest(text_b)  # 暗号化結果と最終認証タグができる
    nonce = cipher.nonce
    ''' header、暗号化された結果、認証タグ、nonceをバイナリーから可読文字のリストに変換'''
    b64text = [b64encode(x).decode('utf-8') for x in [ciphertext, tag, nonce]]
    json_text = json.dumps(b64text)         # 可読文字のリストをjson形式にする
    return(json_text)                       # 結果を返す。

""" 与えられた暗号文(json)を暗号化キーで復号する。結果をstr型で返す。
"""
def decrypt(text, key):
    b64text = json.loads(text)              # json形式から可読文字のリストに戻す
    cipherdata = [ b64decode(x) for x in b64text]
    ciphertext = cipherdata[0]              # 各バイナリーに戻す
    tag        = cipherdata[1]
    nonce      = cipherdata[2]
    cipher =  AES.new(key, AES.MODE_EAX, nonce=nonce)   # 同じキーてEAXモードでAESをインスタンス化、nonceも同じにする
    try:
        plaintext = cipher.decrypt_and_verify(ciphertext, tag)  #復号化と認証タグの有効を確認
    except ValueError:
        print('MACタグが無効の為メッセージを信頼できない') 
        plaintext = b''
    return(plaintext)

""" 以下 テスト用 """
if __name__ == '__main__':
    sw = input('選択して下さい 0 or 1 or 3 --> ')
    if sw == '0': 
        key = create_key()                      # 暗号化キーの作成
        with open('testkey', 'wb') as f:
            f.write(key)                    # キーを保存
        print('keyの作成 ', key)  
    elif sw == '1':                         # 暗号化する
        with open('testkey', 'rb') as f:
            key = f.read()
        text = input('暗号化するテキストを入力 --> ')
        print('原 文:', text)
        text_b = text.encode('utf-8')       # 原文をstr型からbyte型に変換
        json_text = encrypt(text_b, key)
        with open('testdata', 'w') as f:
            f.write(json_text)
            print('暗号文:',json_text)
    elif sw == '3':
        with open('testkey', 'rb') as f:
            key = f.read()
        with open('testdata', 'r') as f:
            json_text = f.read()
        result = decrypt(json_text, key)
        result_s = result.decode('utf-8')  # 復号化成功
        print ('復号文:', result_s)

 暗号の詳しいことは理解できていませんが、pycryptodomeを簡単に使えることがわかりました。pycryptodomeは多くの方式をサポートしているので、他の方式も同様に簡単に使えるものと思います。

 今、このpycryptodomeを使って、パスワード管理ソフトの自作を考えています。