PyKCS11 は、Python で PKCS#11 インタフェース関数を利用するラッパーライブラリで、マイナンバーカードの電子署名ツールとしても紹介されているライブラリです。
他には python-pkcs11 というライブラリもあります。
テスト環境の準備
SoftHSM のインストール
-
Debian Linux
sudo apt install -y softhsm2 libsofthsm2 opensc
SoftHSM の初期化
-
初期化
softhsm2-util \ --init-token \ --slot 0 \ --label "SoftHSMToken" \ --so-pin 4321 \ --pin 1234
-
トークンの確認
softhsm2-util --show-slots
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -L
pkcs11-tool での操作
-
サポートアルゴリズム一覧の確認
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -M
-
登録鍵一覧取得
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -O \ --login --pin 1234
-
鍵作成
-
対称鍵
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --keygen \ --key-type aes:32 \ --id 01 \ --label "aeskey"
-
RSA 鍵
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --keypairgen \ --key-type rsa:4096 \ --id 02 \ --label "rsakey"
公開鍵のエクスポート
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --read-object \ --type pubkey \ --id 02 \ --output-file public_key.der
DER 形式 ⇒ PEM 形式に変換
openssl rsa \ -in public_key.der \ -inform der -pubin \ -outform pem \ -out public_key.pem
-
EC 鍵 (NIST P-256 の例)
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --keypairgen \ --key-type EC:secp256r1 \ --id 03 \ --label "eckey"
-
-
鍵の削除
-
対称鍵
-
ID 指定削除
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --delete -d 01 -y secrkey
-
Label 指定削除
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --delete -y secrkey --label "aeskey"
-
-
サンプルコード
対称鍵で暗号・復号
import binascii
import secrets
from PyKCS11 import PyKCS11
""" 設定 """
# PKCS11 モジュールのパス
PKCS11_LIB = "/usr/lib/softhsm/libsofthsm2.so"
# 暗号化対称データ
plaintext = b"I hate working overtime..."
""" 設定 """
# PKCS11 モジュールをロード
pkcs11 = PyKCS11.PyKCS11Lib()
pkcs11.load(PKCS11_LIB)
# スロットを取得し、セッションを開始
slot = pkcs11.getSlotList(tokenPresent=True)[0]
session = pkcs11.openSession(slot, PyKCS11.CKF_RW_SESSION | PyKCS11.CKF_SERIAL_SESSION)
# ログイン
session.login("1234")
# AES 256 キーを作成
key_template = [
(PyKCS11.CKA_LABEL, "aes-key"),
(PyKCS11.CKA_ID, binascii.unhexlify("9876")),
(PyKCS11.CKA_VALUE_LEN, 32), # 32バイト = 256ビット
(PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE), # トークンに保存
(PyKCS11.CKA_PRIVATE, PyKCS11.CK_TRUE),
]
# AES-256 対称鍵の生成
key_handle = session.generateKey(key_template)
""" 暗号化・復号 """
# イニシャルベクタ (16バイト)
# iv = b"\x00" * 16
iv = secrets.token_bytes(16)
# 暗号化
enc_mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_CBC_PAD, iv)
ciphertext = session.encrypt(key_handle, plaintext, enc_mechanism)
print("Encrypted:", binascii.hexlify(bytearray(ciphertext)))
# 復号
decrypted = session.decrypt(key_handle, ciphertext, enc_mechanism)
print("Decrypted:", decrypted)
# print(bytes(decrypted).decode("utf-8"))
# 暗号化・復号の検証
assert plaintext == bytes(decrypted)
# 鍵削除
session.destroyObject(key_handle)
# セッションを閉じる
session.logout()
session.closeSession()
暗号化鍵の Wrap/UnWrap
import binascii
import secrets
from PyKCS11 import PyKCS11
""" 設定 """
# PKCS11 モジュールのパス
PKCS11_LIB = "/usr/lib/softhsm/libsofthsm2.so"
# 暗号化対称データ
plaintext = b"I hate working overtime..."
""" 設定 """
def encrypt_decrypt(
key_handle: PyKCS11.LowLevel.CK_OBJECT_HANDLE, plaintext: bytes
) -> None:
"""暗号・復号テスト
指定された鍵ハンドルを使用して、指定した平文を暗号化⇒復号できることを確認する。
Args:
key_handle (PyKCS11.LowLevel.CK_OBJECT_HANDLE): 暗号化に使用する鍵ハンドル
plaintext (bytes): 暗号化する平文
Returns: None
"""
# イニシャルベクタ (16バイト)
iv = secrets.token_bytes(16)
# 暗号化
enc_mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_CBC_PAD, iv)
ciphertext = session.encrypt(key_handle, plaintext, enc_mechanism)
print("Encrypted:", binascii.hexlify(bytearray(ciphertext)))
# 復号
dec_mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_CBC_PAD, iv)
decrypted = session.decrypt(key_handle, ciphertext, dec_mechanism)
print("Decrypted:", decrypted)
assert plaintext == bytes(decrypted)
# PKCS11 モジュールをロード
pkcs11 = PyKCS11.PyKCS11Lib()
pkcs11.load(PKCS11_LIB)
# スロットを取得し、セッションを開始
slot = pkcs11.getSlotList(tokenPresent=True)[0]
session = pkcs11.openSession(slot, PyKCS11.CKF_RW_SESSION | PyKCS11.CKF_SERIAL_SESSION)
# ログイン
session.login("1234")
# AES 256 キーを作成
key_template = [
(PyKCS11.CKA_LABEL, "aes-key"),
(PyKCS11.CKA_ID, binascii.unhexlify("9876")),
(PyKCS11.CKA_VALUE_LEN, 32), # 32バイト = 256ビット
(PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE), # トークンに保存
(PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
(PyKCS11.CKA_EXTRACTABLE, PyKCS11.CK_TRUE),
]
# AES-256 対称鍵の生成
enckey_handle = session.generateKey(key_template)
wrapkey_handle = session.generateKey(key_template)
# 暗号鍵のエクスポート
# wrap using CKM_AES_KEY_WRAP
wrap_mechanism = PyKCS11.Mechanism(PyKCS11.CKM_AES_KEY_WRAP)
wrapped_key = session.wrapKey(wrapkey_handle, enckey_handle, wrap_mechanism)
assert wrapped_key is not None
# 暗号化鍵を削除
session.destroyObject(enckey_handle)
# unwrap
template = [
(PyKCS11.CKA_CLASS, PyKCS11.CKO_SECRET_KEY),
(PyKCS11.CKA_KEY_TYPE, PyKCS11.CKK_AES),
(PyKCS11.CKA_TOKEN, PyKCS11.CK_TRUE),
(PyKCS11.CKA_PRIVATE, PyKCS11.CK_FALSE),
(PyKCS11.CKA_ENCRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_DECRYPT, PyKCS11.CK_TRUE),
(PyKCS11.CKA_SIGN, PyKCS11.CK_FALSE),
(PyKCS11.CKA_VERIFY, PyKCS11.CK_FALSE),
]
unwrappedkey_handle = session.unwrapKey(
wrapkey_handle, wrapped_key, template, wrap_mechanism
)
assert unwrappedkey_handle is not None
# 暗号・復号テスト
encrypt_decrypt(unwrappedkey_handle, plaintext)
# 鍵削除
session.destroyObject(unwrappedkey_handle)
session.destroyObject(wrapkey_handle)
# セッションを閉じる
session.logout()
session.closeSession()