Crypto examples#

This jupyter notebook describes how to use SPSDK crypto backend using the simple example:

  1. Create RSA keys

  2. Create x.509 certificates

  3. Create certificate chain and validate it

SPSDK crypto backend#

SPSDK crypto backend was refactored as part of the SPSDK 2.0 release. It provides unified API for all crypto operations needed for secure and trust provisioning like key and certificate generation, signing, encryption, hashing and others.

It is mostly based on python package cryptography https://cryptography.io/ which is then based on OpenSSL. OpenSSL is the de facto standard for cryptographic libraries and provides high performance along with various certifications that may be relevant to developers. In the addition to the cryptography it also implements some other less known standards for example OSCCA and CMS signatures.

Keys#

Operatation with keys is implemented in spsdk.crypto.keys module. SPSDK supports all standard keys supported in cryptography like RSA and ECC with PEM and DER formats. In addition to PEM and DER encoding, SPSDK defines NXP encoding that is used with the NXP devices that are memory constrained.

SPSDK defines general base classes PrivateKey and PublicKey for manipulation with any key.

Private Key#

The PrivateKey class is an integral part of the SPSDK (Secure Provisioning SDK), designed as an abstract base class for private keys. It extends the BaseClass and defines abstract methods that must be implemented by its subclasses.

Methods#

  • generate_key(cls) -> Self: Generates an SPSDK private key.

  • load(cls, file_path, password) -> Self: Loads a private key from a specified file.

  • create(cls, key) -> Self: Creates a Private Key object.

  • signature_size(self) -> int: Returns the size of the signature data.

  • key_size(self) -> int: Returns the key size in bits.

  • get_public_key(self) -> “PublicKey”: Generates the corresponding public key.

  • verify_public_key(self, public_key: “PublicKey”) -> bool: Verifies if a provided public key is in the pair.

  • sign(self, data: bytes) -> bytes: Signs the given input data.

  • export(self, password, encoding) -> bytes: Exports the key into bytes in the requested format.

  • save(self, file_path, password, encoding): Saves the private key to a specified file.

  • eq(self, obj) -> bool: Checks equality with another object.

  • parse(cls, data, password) -> Self: Deserializes an object from a bytes array.

Properties#

  • signature_size(self) -> int: Size of the signature data.

  • key_size(self) -> int: Key size in bits.

Exceptions#

  • SPSDKInvalidKeyType: Raised when an unsupported private key type is encountered.

Public Key#

The PublicKey class is an essential component of the SPSDK (Secure Provisioning SDK), designed as an abstract base class for public keys. Similar to the PrivateKey class, it extends BaseClass and defines abstract methods that must be implemented by its subclasses.

Properties#

  • signature_size(self) -> int: Returns the size of the signature data.

  • public_numbers(self) -> Any: Returns public numbers associated with the public key.

Methods#

  • save(self, file_path: str, encoding: SPSDKEncoding = SPSDKEncoding.PEM) -> None: Saves the public key to the specified file.

  • load(cls, file_path: str) -> Self: Loads the public key from the given file.

  • verify_signature(self, signature: bytes, data: bytes, algorithm: EnumHashAlgorithm = EnumHashAlgorithm.SHA256) -> bool: Verifies input data using the signature.

  • export(self, encoding: SPSDKEncoding = SPSDKEncoding.NXP) -> bytes: Exports the public key into bytes in the requested format.

  • parse(cls, data: bytes) -> Self: Deserializes an object from bytes array.

  • key_hash(self, algorithm: EnumHashAlgorithm = EnumHashAlgorithm.SHA256) -> bytes: Gets the key hash.

  • eq(self, obj: Any) -> bool: Checks object equality.

  • create(cls, key: Any) -> Self: Creates a Public Key object.

%run ../init_notebook.ipynb

WORKSPACE = "workspace/" # change this to path to your workspace

import os
from os import path

from spsdk.crypto.keys import EccCurve, PrivateKeyEcc, PrivateKeyRsa
from spsdk.crypto.types import SPSDKEncoding

# Set the folder for data (certificates, keys)
data_dir = WORKSPACE
os.makedirs(data_dir, exist_ok=True)

# Generate and save rsa keys (size 2048) - pem format (for usage of CA certificate)
priv_key_2048 = PrivateKeyRsa.generate_key(key_size=2048)
pub_key_2048 = priv_key_2048.get_public_key()
priv_key_2048.save(path.join(data_dir, "ca_privatekey_rsa2048.pem"))
pub_key_2048.save(path.join(data_dir, "ca_publickey_rsa2048.pem"))
print(
    "The pair of private (ca_privatekey_rsa2048.pem) and public (ca_publickey_rsa2048.pem) key was generated."
)

# Generate and save rsa keys (size 2048) - pem format (for usage of chain of certificate)
priv_key_2048 = PrivateKeyRsa.generate_key(key_size=2048)
pub_key_2048 = priv_key_2048.get_public_key()
priv_key_2048.save(path.join(data_dir, "crt_privatekey_rsa2048.pem"))
pub_key_2048.save(path.join(data_dir, "crt_publickey_rsa2048.pem"))
print(
    "The pair of private (crt_privatekey_rsa2048.pem) and public (crt_publickey_rsa2048.pem) key was generated."
)

# Generate and save rsa keys (size 2048) - pem format (for usage of chain of certificate)
priv_key_2048 = PrivateKeyRsa.generate_key(key_size=2048)
pub_key_2048 = priv_key_2048.get_public_key()
priv_key_2048.save(path.join(data_dir, "chain_privatekey_rsa2048.pem"))
pub_key_2048.save(path.join(data_dir, "chain_publickey_rsa2048.pem"))
print(
    "The pair of private (chain_privatekey_rsa2048.pem) and public (chain_publickey_rsa2048.pem) key was "
    "generated."
)

# Generate and save rsa keys (size 2048) - pem format (for usage of chain of certificate)
priv_key_2048 = PrivateKeyRsa.generate_key(key_size=2048)
pub_key_2048 = priv_key_2048.get_public_key()
priv_key_2048.save(path.join(data_dir, "chain_crt2_privatekey_rsa2048.pem"))
pub_key_2048.save(path.join(data_dir, "chain_crt2_publickey_rsa2048.pem"))
print(
    "The pair of private (chain_crt2_privatekey_rsa2048.pem) and public (chain_crt2_publickey_rsa2048.pem) key "
    "was generated."
)

# Generate and save rsa keys (size 3072) - pem format
priv_key_3072 = PrivateKeyRsa.generate_key(key_size=3072)
pub_key_3072 = priv_key_3072.get_public_key()
priv_key_3072.save(path.join(data_dir, "private_rsa3072.pem"))
pub_key_3072.save(path.join(data_dir, "public_rsa3072.pem"))
print(
    "The pair of private (private_rsa3072.pem) and public (public_rsa3072.pem) key was generated."
)

# Generate and save rsa keys (size 4096) - pem format
priv_key_4096 = PrivateKeyRsa.generate_key(key_size=4096)
pub_key_4096 = priv_key_4096.get_public_key()
priv_key_4096.save(path.join(data_dir, "private_rsa4096.pem"))
pub_key_4096.save(path.join(data_dir, "public_rsa4096.pem"))
print(
    "The pair of private (private_rsa4096.pem) and public (public_rsa4096.pem) key was generated."
)

# Generate and save rsa keys (size 2048) - der format
priv_key_2048 = PrivateKeyRsa.generate_key(key_size=2048)
pub_key_2048 = priv_key_2048.get_public_key()
priv_key_2048.save(path.join(data_dir, "private_rsa2048.der"), encoding=SPSDKEncoding.DER)
pub_key_2048.save(path.join(data_dir, "public_rsa2048.der"), encoding=SPSDKEncoding.DER)
print(
    "The pair of private (private_rsa2048.der) and public (public_rsa2048.der) key was generated."
)

# Generate and save rsa keys (size 3072) - der format
priv_key_3072 = PrivateKeyRsa.generate_key(key_size=3072)
pub_key_3072 = priv_key_3072.get_public_key()
priv_key_3072.save(path.join(data_dir, "private_rsa3072.der"), encoding=SPSDKEncoding.DER)
pub_key_3072.save(path.join(data_dir, "public_rsa3072.der"), encoding=SPSDKEncoding.DER)
print(
    "The pair of private (private_rsa3072.der) and public (public_rsa3072.der) key was generated."
)

# Generate and save rsa keys (size 4096) - der format
priv_key_4096 = PrivateKeyRsa.generate_key(key_size=4096)
pub_key_4096 = priv_key_4096.get_public_key()
priv_key_4096.save(path.join(data_dir, "private_rsa4096.der"), encoding=SPSDKEncoding.DER)
pub_key_4096.save(path.join(data_dir, "public_rsa4096.der"), encoding=SPSDKEncoding.DER)
print(
    "The pair of private (private_rsa4096.der) and public (public_rsa4096.der) key was generated."
)

# Generate and save ECC keys (curve P-256) - pem format
priv_key_p256 = PrivateKeyEcc.generate_key(curve_name=EccCurve.SECP256R1)
pub_key_p256 = priv_key_p256.get_public_key()
priv_key_p256.save(path.join(data_dir, "ecc_privatekey_p256.pem"))
pub_key_p256.save(path.join(data_dir, "ecc_publickey_p256.pem"))
print(
    "The pair of private (ecc_privatekey_p256.pem) and public (ecc_publickey_p256.pem) key was generated."
)
env: JUPYTER_SPSDK=1
Created `%!` as an alias for `%execute`.
The pair of private (ca_privatekey_rsa2048.pem) and public (ca_publickey_rsa2048.pem) key was generated.
The pair of private (crt_privatekey_rsa2048.pem) and public (crt_publickey_rsa2048.pem) key was generated.
The pair of private (chain_privatekey_rsa2048.pem) and public (chain_publickey_rsa2048.pem) key was generated.
The pair of private (chain_crt2_privatekey_rsa2048.pem) and public (chain_crt2_publickey_rsa2048.pem) key was generated.
The pair of private (private_rsa3072.pem) and public (public_rsa3072.pem) key was generated.
The pair of private (private_rsa4096.pem) and public (public_rsa4096.pem) key was generated.
The pair of private (private_rsa2048.der) and public (public_rsa2048.der) key was generated.
The pair of private (private_rsa3072.der) and public (public_rsa3072.der) key was generated.
The pair of private (private_rsa4096.der) and public (public_rsa4096.der) key was generated.
The pair of private (ecc_privatekey_p256.pem) and public (ecc_publickey_p256.pem) key was generated.

Certificate#

The Certificate class is a crucial representation in the SPSDK (Secure Provisioning SDK), serving as a representation of X.509 certificates. It extends the BaseClass and encapsulates functionalities related to generating, saving, loading, and manipulating certificates.

Constructor#

  • init(self, certificate: x509.Certificate) -> None: Constructor of the SPSDK Certificate.

Methods#

  • generate_certificate( subject: x509.Name, issuer: x509.Name, subject_public_key: PublicKey, issuer_private_key: PrivateKey, serial_number: Optional[int] = None, duration: Optional[int] = None, extensions: Optional[List[x509.ExtensionType]] = None, ) -> “Certificate”: Generates a certificate.

  • save(self, file_path: str, encoding_type: SPSDKEncoding = SPSDKEncoding.PEM) -> None: Saves the certificate/CSR into a file.

  • load(cls, file_path: str) -> Self: Loads the certificate from the given file.

  • export(self, encoding: SPSDKEncoding = SPSDKEncoding.NXP) -> bytes: Converts certificates into bytes.

  • get_public_key(self) -> PublicKey: Gets public keys from the certificate.

  • validate_subject(self, subject_certificate: “Certificate”) -> bool: Validates the certificate against the subject’s certificate.

  • validate(self, issuer_certificate: “Certificate”) -> bool: Validates the certificate against the issuer’s certificate.

Properties#

  • version(self) -> SPSDKVersion: Returns the certificate version.

  • signature(self) -> bytes: Returns the signature bytes.

  • tbs_certificate_bytes(self) -> bytes: Returns the tbsCertificate payload bytes as defined in RFC 5280.

  • signature_hash_algorithm(self) -> Optional[hashes.HashAlgorithm]: Returns a HashAlgorithm corresponding to the type of the digest signed in the certificate.

  • extensions(self) -> SPSDKExtensions: Returns an Extensions object.

  • issuer(self) -> SPSDKName: Returns the issuer name object.

  • serial_number(self) -> int: Returns the certificate serial number.

  • subject(self) -> SPSDKName: Returns the subject name object.

  • signature_algorithm_oid(self) -> SPSDKObjectIdentifier: Returns the ObjectIdentifier of the signature algorithm.

  • ca(self) -> bool: Checks if the CA flag is set in the certificate.

  • self_signed(self) -> bool: Indicates whether the Certificate is self-signed.

  • raw_size(self) -> int: Raw size of the certificate.

Additional Methods#

  • public_key_hash(self, algorithm: EnumHashAlgorithm = EnumHashAlgorithm.SHA256) -> bytes: Gets the key hash.

String Representations#

  • repr(self) -> str: Text short representation about the Certificate.

  • str(self) -> str: Text information about the Certificate.

Class Methods#

  • parse(cls, data: bytes) -> Self: Deserializes an object from a bytes array.

#          Certificates' structure
#              CA Certificate
#              /      \
#             /        \
#           crt       chain_crt
#                        \
#                         \
#                       chain_crt2

from spsdk.crypto.certificate import Certificate, generate_name, generate_extensions
from spsdk.crypto.keys import PublicKeyRsa

# load private key from data folder
private_key_2048_ca = PrivateKeyRsa.load(path.join(data_dir, "ca_privatekey_rsa2048.pem"))
# load associated public key
public_key_2048_ca = PublicKeyRsa.load(path.join(data_dir, "ca_publickey_rsa2048.pem"))
subject = issuer = generate_name([{"COMMON_NAME": "first"}, {"COUNTRY_NAME": "CZ"}])
# generate CA certificate (self-signed certificate)
ca_cert = Certificate.generate_certificate(
    subject=subject,
    issuer=issuer,
    subject_public_key=public_key_2048_ca,
    issuer_private_key=private_key_2048_ca,
    serial_number=0x1,
    duration=20 * 365,
    extensions=generate_extensions(
        {"BASIC_CONSTRAINTS": {"ca": True, "path_length": 5}},
    ),
)
# Save certificates in two formats (pem and der)
ca_cert.save(path.join(data_dir, "ca_cert_pem.crt"))
ca_cert.save(path.join(data_dir, "ca_cert_der.crt"), encoding_type=SPSDKEncoding.DER)
print("The CA Certificate was created in der and pem format.")

# Create first chain certificate signed by private key of the CA certificate
subject_crt1 = generate_name([{"COMMON_NAME": "second"}, {"COUNTRY_NAME": "CZ"}])
public_key_2048_subject = PublicKeyRsa.load(path.join(data_dir, "crt_publickey_rsa2048.pem"))
crt1 = Certificate.generate_certificate(
    subject=subject_crt1,
    issuer=issuer,
    subject_public_key=public_key_2048_subject,
    issuer_private_key=private_key_2048_ca,
    serial_number=0x3CC30000BABADEDA,
    duration=20 * 365,
    extensions=generate_extensions(
        {"BASIC_CONSTRAINTS": {"ca": False, "path_length": None}},
    ),
)
# Save certificates in two formats (pem and der)
crt1.save(path.join(data_dir, "crt_pem.crt"))
crt1.save(path.join(data_dir, "crt_der.crt"), encoding_type=SPSDKEncoding.DER)
print(
    "The first chain certificate (signed by CA certificate) was created in der and pem format."
)

# First chain certificate signed by private key of the CA certificate
subject_crt2 = generate_name([{"COMMON_NAME": "third"}, {"COUNTRY_NAME": "CZ"}])
private_key_2048_subject_1 = PrivateKeyRsa.load(
    path.join(data_dir, "chain_privatekey_rsa2048.pem")
)
public_key_2048_subject_1 = PublicKeyRsa.load(
    path.join(data_dir, "chain_publickey_rsa2048.pem")
)
crt2 = Certificate.generate_certificate(
    subject=subject_crt2,
    issuer=issuer,
    subject_public_key=public_key_2048_subject_1,
    issuer_private_key=private_key_2048_ca,
    serial_number=0x2,
    duration=20 * 365,
    extensions=generate_extensions(
        {"BASIC_CONSTRAINTS": {"ca": True, "path_length": 3}},
    ),

)
# Save certificates in two formats (pem and der)
crt2.save(path.join(data_dir, "chain_crt_pem.crt"))
crt2.save(path.join(data_dir, "chain_crt_der.crt"), encoding_type=SPSDKEncoding.DER)
print(
    "The first chain certificate (signed by CA certificate) was created in der and pem format."
)

# Create first chain certificate signed by private key of first certificate
subject_crt3 = generate_name([{"COMMON_NAME": "fourth"}, {"COUNTRY_NAME": "CZ"}])
issuer_crt3 = subject_crt2
public_key_2048_subject_2 = PublicKeyRsa.load(
    path.join(data_dir, "chain_crt2_publickey_rsa2048.pem")
)
assert isinstance(public_key_2048_subject_2, PublicKeyRsa)
crt3 = Certificate.generate_certificate(
    subject=subject_crt3,
    issuer=issuer_crt3,
    subject_public_key=public_key_2048_subject_2,
    issuer_private_key=private_key_2048_subject_1,
    serial_number=0x3CC30000BABADEDA,
    duration=20 * 365,
    extensions=generate_extensions(
        {"BASIC_CONSTRAINTS": {"ca": False, "path_length": None}},
    ),
)
# Save certificates in two formats (pem and der)
crt3.save(path.join(data_dir, "chain_crt2_pem.crt"))
crt3.save(path.join(data_dir, "chain_crt2_der.crt"), encoding_type=SPSDKEncoding.DER)
print("The second certificate in a chain was created in der and pem format.")
The CA Certificate was created in der and pem format.
The first chain certificate (signed by CA certificate) was created in der and pem format.
The first chain certificate (signed by CA certificate) was created in der and pem format.
The second certificate in a chain was created in der and pem format.
# Example provides the usage of certificates validation. It validates previously created chains.

from spsdk.crypto.certificate import Certificate, validate_certificate_chain
from spsdk.exceptions import SPSDKError

# Load public key of CA certificate
ca0_pubkey_rsa2048 = PublicKeyRsa.load(path.join(data_dir, "ca_publickey_rsa2048.pem"))
# Load CA certificate
ca0_cert = Certificate.load(path.join(data_dir, "ca_cert_pem.crt"))
# Obtain public key from CA certificate
pubkey_from_ca0_cert = ca0_cert.get_public_key()
# Compare CA's public key from file and the one from certificate
if ca0_pubkey_rsa2048 != pubkey_from_ca0_cert:
    raise SPSDKError("Keys are not the same (the one from disc and the one from cert)")
# Load certificate, which is singed by CA
crt = Certificate.load(path.join(data_dir, "crt_pem.crt"))
if not ca0_cert.validate_subject(crt):
    raise SPSDKError("The certificate is not valid")
print("The certificate was signed by the CA.")
# Load chain of certificate
chain = ["chain_crt2_pem.crt", "chain_crt_pem.crt", "ca_cert_pem.crt"]
chain_cert = [Certificate.load(path.join(data_dir, cert_name)) for cert_name in chain]
ch3_crt2 = Certificate.load(path.join(data_dir, "chain_crt2_pem.crt"))
ch3_crt = Certificate.load(path.join(data_dir, "chain_crt_pem.crt"))
ch3_ca = Certificate.load(path.join(data_dir, "ca_cert_pem.crt"))
# Validate the chain (if corresponding items in chain are singed by one another)
if not validate_certificate_chain(chain_cert):
    raise SPSDKError("The certificate chain is not valid")
print("The chain of certificates is valid.")
# Checks if CA flag is set correctly
if ch3_crt2.ca:
    raise SPSDKError("CA flag is set")
if not ch3_crt.ca:
    raise SPSDKError("CA flag is not set")
if not ch3_ca.ca:
    raise SPSDKError("CA flag is not set")
The certificate was signed by the CA.
The chain of certificates is valid.