LudovicRousseau / PyKCS11

PKCS#11 Wrapper for Python
GNU General Public License v2.0
99 stars 35 forks source link

Unable to verify PyKCS11 signature using python cryptography library #126

Closed the-unreal-adi closed 1 week ago

the-unreal-adi commented 3 weeks ago

Your system information

Please describe your issue in as much detail as possible:

I am creating a data signing module which will sign data using inserted DSC token. I am able to sign the data and verify signature using PyKCS11 library. But when I am trying to verify the signature using python cryptography module, I am getting verification failure.

Sharing the code

import os
from cryptography.hazmat.primitives import hashes
from PyKCS11 import *

PKCS11_LIB_PATH = "eps2003csp11v264.dll"  

data = b"ilovepython"
data2 = b"iloveupython"

def compute_hash(data, algorithm=hashes.SHA256):
    hash_obj = hashes.Hash(algorithm(), backend=None)
    hash_obj.update(data)
    return hash_obj.finalize()

digest = compute_hash(data)
digest2 = compute_hash(data2)

pkcs11 = PyKCS11Lib()
pkcs11.load(PKCS11_LIB_PATH)

slot = pkcs11.getSlotList(tokenPresent=True)[0]
session = pkcs11.openSession(slot)
session.login("12345678")  # Replace with your token PIN

certs = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_CERTIFICATE)])
if not certs:
    raise ValueError("No certificate found on the DSC token")

cert_der = bytes(session.getAttributeValue(certs[0], [PyKCS11.LowLevel.CKA_VALUE], True)[0])

priv_keys = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_PRIVATE_KEY)])
if not priv_keys:
    raise ValueError("No private key found on the DSC token")
priv_key = priv_keys[0]

signature = bytes(session.sign(priv_key, digest, PyKCS11.MechanismRSAPKCS1))

pub_keys = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_PUBLIC_KEY)])
if not pub_keys:
    raise ValueError("No public found on the DSC token")
pub_key = pub_keys[0]

result=session.verify(pub_key,digest,signature,PyKCS11.MechanismRSAPKCS1)

print("Digest",digest.hex())
print("Certificate: ", cert_der.hex())
print("Signature (hex): ", signature.hex())
print("Verified",result)

session.logout()
session.closeSession()

Verification code

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import binascii

# Public key in DER format (hexadecimal)
PUBLIC_KEY_DER_HEX = (
    "30820122300d06092a864886f70d01010105000382010f003082010a0282010100e3e22a2bb61397445dfebfa718c50a3ca1098e0e76d7a7bda97eed799df7cf5e59b9f693c614efae4d5ba7406c926ada743d368c81c79237466c49d30a44827d642061af8efddecc62187d66371e3ceb458cc9b04d43d5c040994e3085b93e8640c9241b6e740804afb3bf36f4790c58571ecd10e77408751124723fc63e9a133a04229c457d2e663fbcd6bc18780332820dea78373dcfd769af6edaf0e5dcca1bdb256104c555b82386f3104028636b3fa269be5811e2b56c95e2bac0a1b5b6858cef711846ea0ddd7a24c716eaf8c5e370149522f839abd4d09fcfe8f227b565dce920f5416da4916258d309f15a553444188da2a0af802e308daa9b8785930203010001"
)

# Data to verify
data = b"ilovepython"

# Signature in hexadecimal format
SIGNATURE_HEX = (
    "ae726f61a595d8ce08e127b2e2f2da44a3130c30dcf61fc8cb1bbac01c821cc544e688cb5dbb61b82c0f64271e2a5ca39b16d6b62c8bc9f99d1dd8ca86668bb973a9aea1eb005fd42f7329abde65a6e63a67253599980ada38099481eb4d6ccece6a1ae3095be5c93962ef876a68c3347bfd58c8353ea247b41916e1db78f6d22e76f2847b541777dcc52f3181aeec4ae9aabe639555fca638727339e5742bfc7d72fbbd35a9321ef2fb3ad861eb4ddad2f1eb4a337040429036ce43c09600fb67e2021020586e0764a000e2ad8282f31c6042b11c7415ec6797a9e02d6ba5c39f0df67d64b1037853ff3e4bf27fc760ed84ad5b8d2531bbc377a91da2d0613c"
)

def load_public_key_from_der_hex(der_hex):
    """
    Load a public key from DER format (hexadecimal string).
    """
    public_key_der = binascii.unhexlify(der_hex)
    return serialization.load_der_public_key(public_key_der, backend=default_backend())

def verify_raw_rsa_signature(public_key, data, signature):
    """
    Verify an RSA signature with no padding (raw RSA operation).
    """
    try:
        # Compute the hash of the original data
        hash_obj = hashes.Hash(hashes.SHA256(), backend=default_backend())
        hash_obj.update(data)
        digest = hash_obj.finalize()

        # Log the hash (digest) and signature
        print("Hash (digest):", digest.hex())
        print("Signature (decoded):", signature.hex())

        # Perform raw RSA verification
        public_key.verify(
            signature,
            digest,  # Hash of the data
            padding=padding.PKCS1v15(),  # PKCS#1 v1.5 padding
            algorithm=hashes.SHA256()  # Explicitly specify the hash algorithm
        )
        print("Signature is valid.")
    except Exception as e:
        print(f"Signature verification failed: {e}")

# Main execution
if __name__ == "__main__":
    # Load the public key from DER hexadecimal
    public_key = load_public_key_from_der_hex(PUBLIC_KEY_DER_HEX)

    # Decode the signature from hex
    signature = binascii.unhexlify(SIGNATURE_HEX)

    # Verify the signature
    verify_raw_rsa_signature(public_key, data, signature)

Please let me know where I am getting wrong? I am also getting verification failure using openssl.

LudovicRousseau commented 3 weeks ago

If you use SHA256 in your verification side maybe you should use Mechanism(CKM_SHA256_RSA_PKCS, None) for the signature, instead of PyKCS11.MechanismRSAPKCS1

the-unreal-adi commented 3 weeks ago

I started with Mechanism(CKM_SHA256_RSA_PKCS, None). Here's what I did, I first hashed the data using sha256 and then signed it using Mechanism(CKM_SHA256_RSA_PKCS, None). It didn't work out. I read somewhere that if we are prehashing the data then we should use Mechanism(CKM_RSA_PKCS, None). Then I read in your documentation that use PyKCS11.MechanismRSAPKCS1 for Mechanism(CKM_RSA_PKCS, None), that's why I am using PyKCS11.MechanismRSAPKCS1. I have tried every permutation and combination, in every case signature is being verified using PyKCS11 library but verification is failing if I use cryptography library.

Here's how I am fetching certificate and public key in PKCS11 for verification.

def fetch_certificate_publicKey_ownerName(session):
    certs = session.findObjects([(PyKCS11.CKA_CLASS, PyKCS11.CKO_CERTIFICATE)])
    if not certs:
        raise ValueError("No certificate found on the DSC token")

    cert_der = bytes(session.getAttributeValue(certs[0], [PyKCS11.CKA_VALUE], True)[0])

    certificate = x509.load_der_x509_certificate(cert_der, default_backend())

    public_key = certificate.public_key()
    public_key_der = public_key.public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo) 

    owner_name = certificate.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)[0].value

    return cert_der, public_key_der, owner_name
LudovicRousseau commented 3 weeks ago

Do not hash before you sign.

the-unreal-adi commented 2 weeks ago

Thanks this finally worked for me...

Signing

import os
from cryptography.hazmat.primitives import hashes
from PyKCS11 import *

# Path to your PKCS#11 library
PKCS11_LIB_PATH = "eps2003csp11v264.dll"  # Replace with your PKCS#11 library path

# Data to be signed
data = b"ilovepython"
data2 = b"iloveupython"
# Step 1: Compute the hash of the data
def compute_hash(data, algorithm=hashes.SHA256):
    hash_obj = hashes.Hash(algorithm(), backend=None)
    hash_obj.update(data)
    return hash_obj.finalize()

digest = compute_hash(data)
digest2 = compute_hash(data2)

pkcs11 = PyKCS11Lib()
pkcs11.load(PKCS11_LIB_PATH)

# Open session and login
slot = pkcs11.getSlotList(tokenPresent=True)[0]
session = pkcs11.openSession(slot)
session.login("0")  # Replace with your token PIN

certs = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_CERTIFICATE)])
if not certs:
    raise ValueError("No certificate found on the DSC token")

cert_der = bytes(session.getAttributeValue(certs[0], [PyKCS11.LowLevel.CKA_VALUE], True)[0])

# Find the private key
priv_keys = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_PRIVATE_KEY)])
if not priv_keys:
    raise ValueError("No private key found on the DSC token")
priv_key = priv_keys[0]

# Perform raw RSA signing
signature = bytes(session.sign(priv_key, data, PyKCS11.Mechanism(PyKCS11.CKM_SHA256_RSA_PKCS)))

pub_keys = session.findObjects([(PyKCS11.LowLevel.CKA_CLASS, PyKCS11.LowLevel.CKO_PUBLIC_KEY)])
if not pub_keys:
    raise ValueError("No public found on the DSC token")
pub_key = pub_keys[0]

result=session.verify(pub_key,data,signature,PyKCS11.Mechanism(PyKCS11.CKM_SHA256_RSA_PKCS))

print("Certificate: ", cert_der.hex())
print("Signature (hex): ", signature.hex())
print("Verified",result)

# Logout and close session
session.logout()
session.closeSession()

Verification

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import binascii

# Public key in DER format (hexadecimal)
PUBLIC_KEY_DER_HEX = (
    "30820122300d06092a864886f70d01010105000382010f003082010a02820101009a5f273b913292f10381be4d2a1e3a88cb24575b5d9c7792b7ff07d7e92e720c3ef139c4a830e3114799a3b1959838bd9a13b673df04e9f98699d5d4628662256209ac734f6d4870ef8473e2089fd3a4633f999c72397b060fd031d682f698ba4c6f4bf8393621422a2ce91aad21375d0b2fcd03ed9b4ddb5731011c50bb5a9fea2c3755bf07f1d6e53e76b337ddeb51228fa443bc6f09ffddca4cb0db8a751699e93688449c98d57ddaf2c5742dd8a2b085c8ed93f8cf1b7d45342168e028876a2e3be580b7af7840283250289f0e728b24bbc1cbd4f64ee6dcfac4dbff48c084df13aaa9fdc775b77c73734d63249010928160a296364539b94e7fe86470bf0203010001"
)

# Data to verify
data = b"ilovepython"

# Signature in hexadecimal format
SIGNATURE_HEX = (
    "6d9f3e29c4e7ed9925dfd614dd66c717322945d9355d9631b1594d41f15cb31c645aa5fd8f667258fdca015873a983c1311b2e07c4fcab1cd3c8774b2883b71506cf76bf07e8bd3a7765661b46313a58eaf3d73ea1b40270337d0652bd1327a0d93541621924f117804143c8a93d5bc07b66ec9d134cbdbb9cc1f4b310f8b4bfc9ba0eb9356c0d2fbbb8872358eeed90338c2d72db94e9b2b4419beb51c74aa6cd5bffa607c30616df8a4ab096cf5015fb7bf72fa5d59b00b587e848ed38bcaee2c5183aa68a6f1cd60eaa276cb221c7746fa17fd5ee74f022555a3a2347dbccbd75d463c340d6144b2c2894e6d79f5da6df2c99a8ad0b18a27db32b16067c0d"
)

def load_public_key_from_der_hex(der_hex):
    """
    Load a public key from DER format (hexadecimal string).
    """
    public_key_der = binascii.unhexlify(der_hex)
    return serialization.load_der_public_key(public_key_der, backend=default_backend())

def verify_raw_rsa_signature(public_key, data, signature):
    """
    Verify an RSA signature with no padding (raw RSA operation).
    """
    try:
        # Compute the hash of the original data
        hash_obj = hashes.Hash(hashes.SHA256(), backend=default_backend())
        hash_obj.update(data)
        digest = hash_obj.finalize()

        # Log the hash (digest) and signature
        print("Hash (digest):", digest.hex())
        print("Signature (decoded):", signature.hex())

        # Perform raw RSA verification
        public_key.verify(
            signature,
            data,  # Hash of the data
            padding=padding.PKCS1v15(),  # PKCS#1 v1.5 padding
            algorithm=hashes.SHA256()  # Explicitly specify the hash algorithm
        )
        print("Signature is valid.")
    except Exception as e:
        print(f"Signature verification failed: {e}")

# Main execution
if __name__ == "__main__":
    # Load the public key from DER hexadecimal
    public_key = load_public_key_from_der_hex(PUBLIC_KEY_DER_HEX)

    # Decode the signature from hex
    signature = binascii.unhexlify(SIGNATURE_HEX)

    # Verify the signature
    verify_raw_rsa_signature(public_key, data, signature)

Although I was wondering can we handle data hashing on our own and just sign the data without internal hashing...

Also I don't want to hardcode the pin and want to use dsc token prompt, can we do that here?

LudovicRousseau commented 2 weeks ago

Thanks this finally worked for me...

Good!

Although I was wondering can we handle data hashing on our own and just sign the data without internal hashing...

You can sign the hash if you want to hash yourself. But the signature will also involve its own hash step.

Also I don't want to hardcode the pin and want to use dsc token prompt, can we do that here?

What is "dsc token prompt"?