PyCryptodome

【Python】PyCryptodomeによる暗号化・復号化

【Python】PyCryptodomeによる暗号化・復号化
naoki-hn

Python で暗号化、復号化するためのモジュールである PyCryptodome モジュールについて解説します。共通鍵暗号方式の AES-GCM と公開鍵暗号方式の RSA を例にして説明します。

PyCryptodome による暗号化・復号化

Python で暗号化・復号化を実装するには PyCryptodome が使用できます。以前は、pycrypto がよく知られていましたが、既に開発は終了しており PyCryptodome への移行が推奨されています。

この記事では PyCryptodome での暗号化・復号化について基本を紹介します。

以降で紹介するプログラム例の動作確認は、記事作成・更新時点で以下の動作条件で確認をしています。

  • python 3.11.2
  • pycroptodome 3.17

PyCryptodome を使用する場合は、以下のように pip でインストールしてください。

pip install pycryptodome

暗号化方式には大きく共通鍵暗号方式と公開鍵暗号方式があります。以降では共通鍵暗号方式や公開鍵暗号方式による PyCryptodome での暗号化・復号化について例を用いて紹介します。

なお、暗号理論の詳細は説明しませんので、各種暗号技術に関する書籍などを参照してもらえればと思います。

PyCryptodome は、pycrypto とほぼ同じ API を公開しているため多くのアプリケーションは変更なしで実行できるようになっています。pycrypto との互換性は公式ドキュメントのこちらを参考にしてください。

共通鍵暗号方式による暗号化・復号化

PyCryptodome を用いた共通鍵暗号方式を用いた暗号化・復号化方法について紹介します。まずは、簡単に共通鍵暗号方式について説明します。

共通鍵暗号方式 概要図

共通鍵方式では、暗号化と復号化に共通の鍵を使用します。処理速度が速いことがメリットですが、通信接続先ごとに共通鍵を作成したり、第三者に共通鍵が盗聴されないように安全に交換する必要があったりとデメリットもあります。

共通鍵暗号方式は、DES、3DES、AES といったものがあります。以降では、共通鍵方式としてよく使用される AES について PyCryptodome での使い方を紹介します。

Advanced Encryption Standard (AES)

Advanced Encryption Standard (AES) は、通信データの暗号化などでよく使われている共通鍵暗号方式です。

AES にはいくつかのモードが利用できます。以下の例では、AES の認証付き暗号モードの GCM (Galois/Counter Mode) を使用します。認証付き暗号というのは復号時にデータが改ざんされていないかの完全性確認も同時にチェックできるモードです。

GCM はガロア認証+カウンター (CTR) モードという意味です。ガロア認証は暗号文が改ざんされていないことを検知する仕組みで、カウンターモードは従来からある暗号モードで処理が並列化可能であり、パディングが不要な効率が良いモードです。

GCM では認証タグ (TAG) というものがあり、暗号化する際に暗号文と TAG を一緒に生成します。復号の際には暗号文と TAG の両方が必要で、暗号文が改ざんされた場合には復号できなくなるため、機密性と完全性を担保できます。

以降では、PyCryptodome による AES-GCM での暗号化、復号化のサンプルプログラムを紹介します。

実装例
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes


def main():
    # 暗号化対象文字列
    plaintext = "pycryptodomeを用いた文字列の暗号化・復号化"
    print(plaintext)
    # 文字列をバイトに変換
    plaintext_byte = plaintext.encode("utf-8")

    print("\n===== 暗号化 =====")
    # キーの生成
    key = get_random_bytes(16)
    # nonceの生成
    nonce = get_random_bytes(12)
    # 暗号化用オブジェクトの準備
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    # 暗号化
    cipher_text, tag = cipher.encrypt_and_digest(plaintext_byte)
    print(f"暗号化文: {cipher_text}")
    print(f"TAG: {tag}")

    print("\n===== 復号化 =====")
    # 復号化用オブジェクトの準備
    cipher_dec = AES.new(key, AES.MODE_GCM, nonce=nonce)
    try:
        # 復号化
        decrypted_text = cipher_dec.decrypt_and_verify(cipher_text, tag)
        print(decrypted_text.decode())
    except (ValueError, KeyError) as ex:
        print(f"不正な復号 {ex}")


if __name__ == "__main__":
    main()
【実行結果例】
pycryptodomeを用いた文字列の暗号化・復号化

===== 暗号化 =====
暗号化文: b'K\xf3=\xc3\xcf\x03\x11\xf6`\x06\x7f&\xdf \x8a\x8b\x88u\x12V\x03\xd3i\xf5D\x13\xdbk\x14HtR\x85\xcf\xf3$3\x99\x0f\xecpvQds\xc0\xf3c\x1a\xd2\xdb\x19\xcd\xd1mX\x19'
TAG: b'\x1d\xfd\x8a\xde\xd5\xc7\x11h\x80\xd3\xba\x15\x90f\x9eS'

===== 復号化 =====
pycryptodomeを用いた文字列の暗号化・復号化
詳細説明
    # 暗号化対象文字列
    plaintext = "pycryptodomeを用いた文字列の暗号化・復号化"
    print(plaintext)
    # 文字列をバイトに変換
    plaintext_byte = plaintext.encode("utf-8")

上記の部分は、暗号化対象文字列です。暗号化前には文字列をバイトに変換します。

    print("\n===== 暗号化 =====")
    # キーの生成
    key = get_random_bytes(16)
    # nonceの生成
    nonce = get_random_bytes(12)
    # 暗号化用オブジェクトの準備
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    # 暗号化
    cipher_text, tag = cipher.encrypt_and_digest(plaintext_byte)
    print(f"暗号化文: {cipher_text}")
    print(f"TAG: {tag}")

上記が暗号化の実行部分です。keynonce は、ランダムなバイトを生成しています。nonse としては 12 バイト (96 ビット) が一般的に使用されます。

暗号化用のオブジェクトは AES.new で生成します。その際に keynonce、モードを指定します。GCM の場合は AES.MODE_GCM となります。

暗号化は encrypt_and_digest に暗号化対象を引数として渡すことで実行ができ、暗号化文と認証タグが生成されます。

    print("\n===== 復号化 =====")
    # 復号化用オブジェクトの準備
    cipher_dec = AES.new(key, AES.MODE_GCM, nonce=nonce)
    try:
        # 復号化
        decrypted_text = cipher_dec.decrypt_and_verify(cipher_text, tag)
        print(decrypted_text.decode())
    except (ValueError, KeyError) as ex:
        print(f"不正な復号 {ex}")

上記が復号化の実行部分です。keynonce は安全に共有されているものとします。

復号化用のオブジェクトでは、keynonce を使って AES.new で生成します。復号化の際には、decrypt_and_verify に暗号文と認証タグを渡します。

正常に復号化されれば decrypted_text に復号化文が設定されます。もし、暗号化文と認証タグが一致しない場合は、例外が発生し「不正な復号 MAC check failed」のような出力がされます。

公開鍵暗号方式による暗号化・復号化

PyCryptodome を用いた公開鍵暗号方式を用いた暗号化・復号化方法について紹介します。まずは、簡単に公開鍵暗号方式について説明します。

公開鍵暗号方式 概要図

公開鍵暗号方式では、暗号化と復号化に別の鍵を使用します。暗号化に用いる鍵を公開鍵といい、復号化に用いる鍵を秘密鍵と言います。

公開鍵暗号では受信者側が公開鍵と暗号鍵のペアを生成し、公開鍵を受信者に共有します。送信者は公開鍵を使ってデータを暗号化し、受信者へ送信します。受信者は秘密鍵を使ってデータを復号化します。公開鍵暗号方式では、秘密鍵でしか復号化することができないため、公開鍵は第 3 者に知られても特に問題はありません。

公開鍵暗号方式は、通信接続先に関係なく 1 つペアを作成すればよいので鍵の管理が容易であったり、公開鍵を一般に公開するだけでよかったりといったメリットがありますが、共通鍵暗号方式に比べて処理が遅い点がデメリットになります。そのため、鍵の交換のみ公開鍵暗号方式を使用し、データの暗号化は共通鍵暗号方式を使用するというハイブリッドな方式も使用されます。

公開鍵暗号方式としては、RSA や ElGamal といったものがあります。以降では、公開鍵方式として代表的な RSA について PyCryptodome での使い方を紹介します。

RSA

RSA 暗号は、桁数が大きい合成数の素因数分解が現実的な時間内で困難であることを安全性の根拠とした公開鍵暗号方式の 1 つです。発明者であるロナルド・リン・リベスト(Ronald Linn Rivest)、アディ・シャミア(Adi Shamir)、レオナルド・マックス・エーデルマン(Leonard Max Adleman)の頭文字から名前が付けられています。

以降では、PyCryptodome による RSAでの暗号化、復号化の方法を紹介します。

実装例

[generate_keys.py]

from Crypto.PublicKey import RSA


def main():
    # キーペアの生成
    key = RSA.generate(2048)
    # 秘密鍵
    private_key = key.export_key()
    with open("private.pem", "wb") as f:
        f.write(private_key)

    # 公開鍵の生成
    public_key = key.public_key().export_key()
    with open("public.pem", "wb") as f:
        f.write(public_key)


if __name__ == "__main__":
    main()

[rsa.py]

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA


def main():
    # 暗号化対象文字列
    plaintext = "pycryptodomeを用いた文字列の暗号化・復号化"
    print(plaintext)
    # 文字列をバイトに変換
    plaintext_byte = plaintext.encode("utf-8")

    print("\n===== 暗号化 =====")
    # 公開鍵の読み込み
    with open("public.pem", "r") as f:
        public_key = RSA.import_key(f.read())

    # 暗号化用オブジェクトの準備
    cipher = PKCS1_OAEP.new(public_key)
    cipher_text = cipher.encrypt(plaintext_byte)
    print(f"暗号化文: {cipher_text}")

    print("\n===== 復号化 =====")
    # 秘密鍵の読み込み
    with open("private.pem", "r") as f:
        private_key = RSA.import_key(f.read())

    # 復号化用オブジェクトの準備
    cipher_dec = PKCS1_OAEP.new(private_key)
    decrypted_text = cipher_dec.decrypt(cipher_text)
    print(decrypted_text.decode())


if __name__ == "__main__":
    main()
【実行結果】秘密鍵と公開鍵がgenerate_keys.pyを実行し生成された後に実行
pycryptodomeを用いた文字列の暗号化・復号化

===== 暗号化 =====
暗号化文: b'\x17K\x8a\x92\xcf\x89c\xe9k\xf5\xaeEJ\x08\xb9\x8da\t\xe7\x1e\xd2\xa3TA\xb5b\x04f\xa3\xf0\x89\xac\xb1%\xa9^\'\x19\xdd\xed\xea\x92\xce\xd0\r\xcb\xc3S\xa5\x8d\x18A\x83!F\xe4\x85\xb7JV\x0b\xf3\xebQ\xe6\x16w\xb9\xec\xcb\x12\x00\xec\xf6"|-\x94p"\x00\x88\x19\xb1\xe2\x1b\x7f\x9d\xb7\\\x9f^\x1e\x01\'mP\x01N]\xce\xf2\x0f\x08Hff\xe2\x0fP\x19\xbf\x9b\xb1\x90v{\xee|\xed\xc2\xed\x88\xb1\x18+\xe0\xe7\xa4\x86z\x0b\xa5\t+\xf7\xe7\xe3\xf8\xce\xc5\xe1d\xbc\xe7\xa6\x19\xf6^9\x86\x8f<6\xc6I\xc5\x19\xca\xe8\xe0\xb3\xc4X&\xa6\xe4a\xbe\x18\x18@\x04\x7fE\x9b\xdd\x1e\xd7Hw\xde\x95\x12k\xc2\xca\xdd\x18\x18\xa0\xe8\x82~\xb2\xf6\n&\xac\x8e\xe3\xb4\xd9\xc6\x97\xb4=n\x82Qv\xd6\xab\xe9a\xa8!\xde\xc4\xd0y\xf9\xff\x12\x10n\xbb\xe0@\r\x135x\x13\xabJ\xeb\xbb&\xceH\xda\xf9\x9d\x8b\xd9\xf1\xb7\x95\xc2\xed\xf7\x94\x89G^'

===== 復号化 =====
pycryptodomeを用いた文字列の暗号化・復号化
詳細説明
    # キーペアの生成
    key = RSA.generate(2048)
    # 秘密鍵
    private_key = key.export_key()
    with open("private.pem", "wb") as f:
        f.write(private_key)

    # 公開鍵の生成
    public_key = key.public_key().export_key()
    with open("public.pem", "wb") as f:
        f.write(public_key)

各キーペアの生成は、generate_keys.py 内で実行しています。

キーは、RSA.generate で生成します。引数はキーの長さで少なくとも 1024 である必要がありますが、2048 が公式ドキュメントなどでも推奨されています。

生成した keyexport_key() により秘密鍵が生成でき、private.pem というファイルで保存しています。また、公開鍵は生成した keypublic_key().export_key() により生成でき、public.pem というファイルで保存しています。

キーペアの生成は受信者側で実施し、公開鍵ファイルについては送信者が使用します。

以降は、生成した公開鍵を使って暗号化、秘密鍵を使って復号化している rsa.py の処理内容を説明します。

    # 暗号化対象文字列
    plaintext = "pycryptodomeを用いた文字列の暗号化・復号化"
    print(plaintext)
    # 文字列をバイトに変換
    plaintext_byte = plaintext.encode("utf-8")

上記が、暗号化対象文字列です。暗号化前には文字列をバイトに変換してください。

    print("\n===== 暗号化 =====")
    # 公開鍵の読み込み
    with open("public.pem", "r") as f:
        public_key = RSA.import_key(f.read())

    # 暗号化用オブジェクトの準備
    cipher = PKCS1_OAEP.new(public_key)
    cipher_text = cipher.encrypt(plaintext_byte)
    print(f"暗号化文: {cipher_text}")

上記が暗号化の実行部分です。こちらは送信側で受信者が公開している公開鍵を読み込んで暗号化を実施している部分と思ってください。

暗号化用オブジェクトとしては PKCS1_OAEP.new公開鍵を指定して生成します。PKCS1_OAEP は、RSA と OAEP パディングに基づく非対称暗号です。

暗号化は、生成したオブジェクトの encrypt に対象を渡すことで暗号化できます。

   print("\n===== 復号化 =====")
    # 秘密鍵の読み込み
    with open("private.pem", "r") as f:
        private_key = RSA.import_key(f.read())

    # 復号化用オブジェクトの準備
    cipher_dec = PKCS1_OAEP.new(private_key)
    decrypted_text = cipher_dec.decrypt(cipher_text)
    print(decrypted_text.decode())

上記が、復号化の実行部分です。こちらは受信者が送信者から送られてきた暗号文を秘密鍵で復号化していると思ってください。

ほとんど暗号化の時と同じような構成になっていますが、鍵が秘密鍵である点と decrypt を使用している点が異なります。実行結果を見ると公開鍵で暗号化した暗号文が、秘密鍵で復号化できていることが分かります。

まとめ

Python で暗号化、復号化するためのモジュールである PyCryptodome モジュールについて解説しました。共通鍵暗号方式の AES-GCM と公開鍵暗号方式の RSA を例にして説明しています。

暗号化・復号化モジュールとしては pycrypto がよく知られていましたが、既に開発が終了しているため、PyCryptodome への移行が推奨されています。PyCryptodome は、pycrypto とほぼ同じ API を公開しているため多くのアプリケーションは変更なしで実行できると思います。

この記事では、共通鍵暗号方式の AES-GCM と公開鍵暗号方式の RSA を例にして説明しています。他にも使用方法はあるため、公式ドキュメントを確認して、色々と使用してみてください。

PyCryptodome の公式ドキュメントはこちらを参照してください。

ソースコード

上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。

あわせて読みたい
【Python Tech】プログラミングガイド
【Python Tech】プログラミングガイド
ABOUT ME
ホッシー
ホッシー
システムエンジニア
はじめまして。当サイトをご覧いただきありがとうございます。 私は製造業のメーカーで、DX推進や業務システムの設計・開発・導入を担当しているシステムエンジニアです。これまでに転職も経験しており、以前は大手電機メーカーでシステム開発に携わっていました。

プログラミング言語はこれまでC、C++、JAVA等を扱ってきましたが、最近では特に機械学習等の分析でも注目されているPythonについてとても興味をもって取り組んでいます。これまでの経験をもとに、Pythonに興味を持つ方のお役に立てるような情報を発信していきたいと思います。どうぞよろしくお願いいたします。

※キャラクターデザイン:ゼイルン様
記事URLをコピーしました