python-pkcs11
python-pkcs11 は、Python で PKCS#11 インタフェース関数を利用するラッパーライブラリです。
他には pykcs11 というライブラリもあります。
実行環境準備
SoftHSM のインストール
- 
Debian Linux 
SoftHSM の初期化
- 
初期化 
- 
トークンの確認 
pkcs11-tool での SoftHSM 操作
- サポートアルゴリズム一覧の確認 - 登録鍵一覧取得 - 鍵作成 - 対称鍵pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
    --login --pin 1234 \
    --keygen \
    --key-type aes:32 \
    --id 01 \
    --label "aeskey"
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
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \
    --login --pin 1234 \
    --keypairgen \
    --key-type EC:secp256r1 \
    --id 03 \
    --label "eckey"
サンプルコード
Python PKCS#11 - High Level Wrapper API — Python PKCS#11  documentation に API ドキュメントはあるのですが、ある程度使い方を把握した人じゃないとわからない気がしました。
Nitrokey HSMを想定した Getting Started も用意されていますが、python-pkcs11/tests に動作するコードがあるので、それをベースにカスタマイズした方が要領が得られると思います。
対称鍵で暗号・復号
import pkcs11
""" 設定 """
# PKCS11 モジュールのパス
PKCS11_LIB = "/usr/lib/softhsm/libsofthsm2.so"
# User PIN
PIN = "1234"
# 暗号化対称データ
plaintext = b"I hate working overtime..."
""" 設定 """
# Initialise our PKCS#11 library
lib = pkcs11.lib(PKCS11_LIB)
slots = lib.get_slots()
slot1, slot2 = slots
# print("Slot1: ", slot1)
# print("Slot2: ", slot2)
token = slot1.get_token()
with token.open(user_pin=PIN) as session:
    enckey = session.generate_key(
        pkcs11.KeyType.AES,
        256,
        template={
            pkcs11.Attribute.EXTRACTABLE: True,
            pkcs11.Attribute.SENSITIVE: False,
        },
    )
    iv = session.generate_random(128)
    crypttext = enckey.encrypt(plaintext, mechanism_param=iv)
    decrypttext = enckey.decrypt(crypttext, mechanism_param=iv)
    enckey.destroy()
    print("Plaintext:", plaintext)
    print("Encrypted:", crypttext)
    print("Decrypted:", decrypttext)
RSA 鍵で署名・署名検証
- サンプルコードimport hashlib
import pkcs11
from pkcs11.util.rsa import encode_rsa_public_key
### 設定 ###
PKCS11_MODULE = "/usr/lib/softhsm/libsofthsm2.so"
PIN = "1234"
# 署名対象のメッセージ
message = b"I hate working overtime..."
############
def sign_message_body(
    message: bytes, private_key_handle: pkcs11.Key, public_key_handle: pkcs11.Key
) -> bytes:
    """
    指定された秘密鍵を使用してメッセージに署名し、メッセージと署名をファイルに書き込む。
    Args:
        message (bytes): 署名対象のメッセージ
        private_key_handle (pkcs11.Key): 署名に使用する秘密鍵のハンドル
        public_key_handle (pkcs11.Key): 署名検証に使用する公開鍵のハンドル
    Returns:
        bytes: 生成された署名
    Raises:
        pkcs11.exceptions.SignatureInvalid: 署名検証に失敗した場合
    Side Effects:
        メッセージを "message.txt" に書込む
        署名を "signature_body.bin" に書込む
    """
    with open("message.txt", "wb") as f:
        f.write(message)
    # 秘密鍵で署名 (SHA-256 と PKCS#1 v1.5 署名)
    signature = private_key_handle.sign(
        message, mechanism=pkcs11.Mechanism.SHA256_RSA_PKCS
    )
    # print("署名済み:", signature.hex())
    with open("signature_body.bin", "wb") as f:
        f.write(signature)
    # 署名の検証
    try:
        public_key_handle.verify(
            message, signature, mechanism=pkcs11.Mechanism.SHA256_RSA_PKCS
        )
        print("署名の検証に成功しました。")
    except pkcs11.exceptions.SignatureInvalid:
        print("署名の検証に失敗しました。")
    return signature
def sign_message_hash(
    message: bytes, private_key_handle: pkcs11.Key, public_key_handle: pkcs11.Key
) -> bytes:
    """
    指定された秘密鍵を使用してメッセージのハッシュ値に署名し、
    メッセージハッシュ値と署名をファイルに書き込む。
    この関数は以下の手順を実行します:
    1. 入力メッセージのSHA-256ハッシュを計算する
    2. 計算されたハッシュにSHA-256プレフィックスを連結してDigestInfo構造を作成する
    3. DigestInfoをRSA PKCS#1 v1.5メカニズムを使用して署名する
    4. 対応する公開鍵を使用して署名を検証します。
    Args:
        message (bytes): 署名対象のメッセージ
        private_key_handle (pkcs11.Key): 署名に使用する秘密鍵のハンドル
        public_key_handle (pkcs11.Key): 署名検証に使用する公開鍵のハンドル
    Returns:
        bytes: 生成された署名
    Raises:
        pkcs11.exceptions.SignatureInvalid: 署名検証に失敗した場合
    Side Effects:
        メッセージを "message.txt" に書込む
        署名を "signature_body.bin" に書込む
    """
    message_hash = hashlib.sha256(message).digest()
    with open("message_hash.bin", "wb") as f:
        f.write(message_hash)
    # SHA-256用のDigestInfoのプレフィックス (DERエンコード済み)
    # DigestInfo構造は以下のようになっています:
    # SEQUENCE {
    #    SEQUENCE {
    #         OBJECT IDENTIFIER sha256 (2.16.840.1.101.3.4.2.1)
    #         NULL
    #    }
    #    OCTET STRING [ハッシュ値]
    # }
    # SHA-256の場合、プレフィックスは以下の16進数列になります。
    digest_info_prefix = bytes.fromhex("3031300d060960864801650304020105000420")
    # 上記プレフィックスとハッシュ値を連結してDigestInfoを作成
    digest_info = digest_info_prefix + message_hash
    # 署名対象は DigestInfo(ハッシュ値とアルゴリズム識別子の連結済みデータ)を使用
    # 署名には RSA PKCS#1 v1.5 の raw 署名機構を利用(ハッシュ計算は行われない)
    signature = private_key_handle.sign(
        digest_info, mechanism=pkcs11.Mechanism.RSA_PKCS
    )
    # print("署名済み:", signature.hex())
    with open("signature_hash.bin", "wb") as f:
        f.write(signature)
    # 署名の検証も同様に、DigestInfo を用いて行います
    try:
        public_key_handle.verify(
            digest_info, signature, mechanism=pkcs11.Mechanism.RSA_PKCS
        )
        print("署名の検証に成功しました。")
    except pkcs11.exceptions.SignatureInvalid:
        print("署名の検証に失敗しました。")
    return signature
if __name__ == "__main__":
    # PKCS#11 モジュールのロード (SoftHSM2 の例)
    lib = pkcs11.lib(PKCS11_MODULE)
    slot1, slot2 = lib.get_slots()
    token = slot1.get_token()
    # トークンを開く
    with token.open(user_pin=PIN, rw=True) as session:
        # RSA キー生成
        public_key_handle, private_key_handle = session.generate_keypair(
            pkcs11.KeyType.RSA,
            2048,
            store=True,  # HSM に保存する
            public_template={
                pkcs11.Attribute.ID: b"rsa-key",
                pkcs11.Attribute.LABEL: "MyRSAKey",
                pkcs11.Attribute.VERIFY: True,
                pkcs11.Attribute.ENCRYPT: True,
            },
            private_template={
                pkcs11.Attribute.ID: b"rsa-key",
                pkcs11.Attribute.LABEL: "MyRSAKey",
                pkcs11.Attribute.SIGN: True,
                pkcs11.Attribute.DECRYPT: True,
                pkcs11.Attribute.SENSITIVE: True,
                pkcs11.Attribute.PRIVATE: True,
            },
        )
        # print(f"Public Key: {public_key_handle}")
        # print(f"Private Key: {private_key_handle}")
        # encode_rsa_public_key を使って DER 形式にエンコード
        der_encoded_public_key = encode_rsa_public_key(public_key_handle)
        # DER 形式でファイルに保存
        with open("public_key.der", "wb") as der_file:
            der_file.write(der_encoded_public_key)
        signature = sign_message_body(message, private_key_handle, public_key_handle)
        signature = sign_message_hash(message, private_key_handle, public_key_handle)
        public_key_handle.destroy()
        private_key_handle.destroy()