1. はじめに
仕事でVPN関係のシステム開発をすることになりました。まずは暗号機能の基本を思い出すため、Pythonで、PKCS#12の公開鍵で暗号、秘密鍵で復号するプログラムを作ってみようと思います。
2. 準備
2.1 PKCS#12ファイル作成
PKCS#12とは、RFC 7292で規定されている、秘密鍵と公開鍵が一体化したデータの ファイルフォーマット規約です。データは、パスワードで暗号しています。
「PKCS#12ファイル」はOpenSSLコマンドでも作成できますが、もっとお気楽にXCAで作成します。
2.2 対象データは「共通鍵暗号」データ
RSA暗号は、暗号/復号するデータは、最大鍵ビット長になります。しかも、暗号/復号する処理が遅いと言われています。
なので、通常の場合RSA暗号で暗号化する対象は「共通鍵暗号」が多いです。
ここでも、「共通鍵暗号」を暗号してみようと思います。アルゴリズムはAESを使用しますので、データ長は256ビット(32バイト)です。
2.3 暗号ライブラリは、pyOpenSSL/PyCryptodome
使用する暗号ライブラリは二つです。
- pyOpenSSL
- OpenSSLバインディング
- ここではPKCS#12の読み込みに使用
- PyCryptodome
インストールは以下で行っておきます。
### Pythonの仮想環境作成 $ cd encrypt $ python3 -m venv ./venv ### ライブラリインストール $ source venv/bin/activate $ pip install pyopenssl $ pip install pycryptodomex $ python -m Cryptodome.SelfTest
2.4 システム構成
システム構成というほど、大きくありませんが、server/clientで通信するサンプルを作成しようと考えました。
サンプルを以下に分けます。
- PKCS#12ファイル to 公開鍵/秘密鍵抽出(pkcs12.py)
- RSAによる暗号化/復号(rsa.py)
- AESによる暗号化/復号(aes.py)
- server/client 通信サンプル(server.py)
3. PKCS#12ファイル to 公開鍵/秘密鍵抽出
PKCS#12ファイルはXCAで作成し、カレントディレクトリに置いておく前提とします。
以下でPKCS#12ファイルを読み出し、公開鍵(pubkey.pem)と秘密鍵privkey.pem)を抽出します。
$ export P12_PASSWORD='XXXXXXXXXX' $ pipenv run python3 ./pkcs12.py
PKCS#12ファイルの読み込みサンプル(pkcs12.py)は以下の通りです。
中心部分は、OpenSSLをラップした関数(load_pkcs12)を呼び出しているところです。
from OpenSSL.crypto import load_pkcs12, FILETYPE_PEM import OpenSSL import os class PKCS12: # input PKCS12_FILENAME = 'server.p12' # output PUBLIC_KEY_FILENAME = 'pubkey.pem' PRIVATE_KEY_FILENAME = 'privkey.pem' def __init__(self): pass def output(self): with open(self.PKCS12_FILENAME, 'rb') as f: c = f.read() p = None ### PKCS#12ファイルを読み込む try: p = load_pkcs12(c, os.environ['P12_PASSWORD']) except OpenSSL.crypto.Error as e: print(e) return certificate = p.get_certificate() public_key = certificate.get_pubkey() private_key = p.get_privatekey() type_ = FILETYPE_PEM with open(self.PRIVATE_KEY_FILENAME, 'wb') as f: f.write(OpenSSL.crypto.dump_privatekey(type_, private_key)) with open(self.PUBLIC_KEY_FILENAME, 'wb') as f: f.write(OpenSSL.crypto.dump_publickey(type_, public_key)) if __name__ == "__main__": p = PKCS12().output()
実行結果は以下の通りです。
$ python3 pkcs12.py encrypt$ ls -l *.pem -rw-rw-r-- 1 tanino tanino 1704 5月 19 14:26 privkey.pem -rw-rw-r-- 1 tanino tanino 451 5月 19 14:26 pubkey.pem
ちなみにパスワードを間違えると、例外を処理しているので、以下になります。
[('PKCS12 routines', 'PKCS12_parse', 'mac verify failure')]
4. RSAによる暗号化/復号
RSAによる暗号化/復号のサンプル(rsa.py)は以下の通りです。
from Cryptodome.Cipher import PKCS1_OAEP from Cryptodome.PublicKey import RSA from pkcs12 import PKCS12 from aes import generate_random_secret_key class RSACipher: def __init__(self): self._pubKey = None self._privKey = None def import_pubkey(self, public_key): """ public_key: PEM 暗号用 """ self._pubKey = RSA.importKey(public_key) def import_privkey(self, private_key): """ private_key: PEM 復号用 """ self._privKey = RSA.importKey(private_key) def encrypt(self, byte_data): if not self._pubKey: print("pubKey is not loaded") return pkcs1Cipher=PKCS1_OAEP.new(self._pubKey) encryptedString=pkcs1Cipher.encrypt(byte_data) return encryptedString def decrypt(self, encryptedString): if not self._privKey: print("privKey is not loaded") return pkcs1Cipher=PKCS1_OAEP.new(self._privKey) decryptedString=pkcs1Cipher.decrypt(encryptedString) return decryptedString if __name__ == "__main__": # 簡易テスト rsa=RSACipher() with open(PKCS12.PUBLIC_KEY_FILENAME) as f: rsa.import_pubkey(f.read()) with open(PKCS12.PRIVATE_KEY_FILENAME) as f: rsa.import_privkey(f.read()) # 公開鍵で暗号化 key = generate_random_secret_key() print("encrypted key: {}".format(key)) encryptedString = rsa.encrypt(key) # 秘密鍵で復号 decryptedString = rsa.decrypt(encryptedString) print("decrypted key: {}".format(decryptedString))
実行する際は、上記のpkcs12.pyを同じディレクトリに格納してください。
実行結果は以下の通りです。
$ python3 rsa.py encrypted key: b'e\xc7\x9a\xceJ\x99A\x7f\xdd\xba\xd4\xc5c\x8c\xca\x10d*eP\xef\xa8;\x0c\xbe\xa8\xfe\x18?\x9e.\x97' decrypted key: b'e\xc7\x9a\xceJ\x99A\x7f\xdd\xba\xd4\xc5c\x8c\xca\x10d*eP\xef\xa8;\x0c\xbe\xa8\xfe\x18?\x9e.\x97'
5. おわりに
思ったより長くなったので、ここで切ります。
次は、AES暗号化から通信する部分までを実装します。