0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

python-pkcs11を使ったHSM操作

Last updated at Posted at 2025-02-08

python-pkcs11 は、Python で PKCS#11 インタフェース関数を利用するラッパーライブラリです。
他には pykcs11 というライブラリもあります。

実行環境準備

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 での SoftHSM 操作
  • サポートアルゴリズム一覧の確認

    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"
        

サンプルコード

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()
    
  • openssl コマンドで検証する

    公開鍵の DER ⇒ PEM 形式に変換
    openssl rsa -inform DER -in public_key.der -pubin -outform PEM -out public_key.pem
    
    • 検証

      対象データそのものに対する署名の署名検証
      openssl dgst -sha256 \
          -verify public_key.pem \
          -signature signature_body.bin \
          message.txt
      
      対象データのハッシュ値に対する署名の署名検証
      # 署名の期待値の生成
      openssl dgst -sha256 -binary message.txt > openssl_message_hash.bin
      (echo "3031300d060960864801650304020105000420" \
          | xxd -r -p; cat openssl_message_hash.bin) > expected_digestinfo.bin
      # 署名検証
      openssl pkeyutl -verify \
          -pubin \
          -inkey public_key.pem \
          -in expected_digestinfo.bin \
          -sigfile signature_hash.bin \
          -pkeyopt rsa_padding_mode:pkcs1
      
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?