Types (quantum_safe.types)

Core data types returned by all key and signature operations. Raw bytes are never returned directly — every output is a distinct type that prevents accidental misuse (e.g. passing a SharedSecret where a CipherText is expected).

class quantum_safe.types.PublicKey(raw, algorithm, migration_state=MigrationState.HYBRID_TRANSITION, backend_tag='unknown')[source]

Bases: BaseKey

A public key for a KEM or signature scheme.

Public keys are safe to share, log, and store without special handling. They support all serialization formats including JWK.

Parameters:

Note

PublicKey.__init__ validates the raw byte length against known FIPS sizes for all non-hybrid algorithms (ML-KEM-512/768/1024, ML-DSA-44/65/87, SLH-DSA variants). Hybrid keys (algorithm names containing +) use a length-prefixed composite format and are not validated by this table.

property raw_bytes: bytes

The raw key bytes as returned by the cryptographic backend.

property algorithm: str

Algorithm identifier, e.g. ‘ML-KEM-768’ or ‘X25519+ML-KEM-768’.

property key_type: KeyType

Whether this is a public or secret key.

property migration_state: MigrationState

The PQC migration state of this key.

classmethod from_pem(pem)[source]

Parse a public key from PEM format.

Parameters:

pem (str)

Return type:

PublicKey

classmethod from_cbor(data)[source]

Parse a public key from CBOR bytes.

Parameters:

data (bytes)

Return type:

PublicKey

classmethod from_jwk(jwk)[source]

Parse a public key from a JWK dict.

Parameters:

jwk (dict[str, Any])

Return type:

PublicKey

class quantum_safe.types.SecretKey(raw, algorithm, migration_state=MigrationState.HYBRID_TRANSITION, backend_tag='unknown')[source]

Bases: BaseKey

A secret key for a KEM or signature scheme.

Secret keys zeroize their memory on deletion. They should: - Never appear in log messages - Never be serialized to JWK (which is designed for public sharing) - Be stored encrypted at rest (see quantum_safe.protocols.envelope)

Parameters:

Note

SecretKey._raw_bytearray returns a fresh mutable bytearray copy of the key bytes for callers (e.g. backends) that need to zero their local copy after passing key material to a C library. Zero it with ctypes.memset in a try/finally block:

sk_buf = secret_key._raw_bytearray
try:
    result = c_lib_call(bytes(sk_buf), ...)
finally:
    import ctypes
    n = len(sk_buf)
    ctypes.memset((ctypes.c_char * n).from_buffer(sk_buf), 0, n)
property raw_bytes: bytes

The raw key bytes as returned by the cryptographic backend.

property algorithm: str

Algorithm identifier, e.g. ‘ML-KEM-768’ or ‘X25519+ML-KEM-768’.

property key_type: KeyType

Whether this is a public or secret key.

property migration_state: MigrationState

The PQC migration state of this key.

classmethod from_pem(pem)[source]

Parse a secret key from PEM format.

Warning: the PEM string itself contains secret material — ensure it’s handled appropriately (not logged, not stored in plaintext).

Parameters:

pem (str)

Return type:

SecretKey

classmethod from_cbor(data)[source]

Parse a secret key from CBOR bytes.

Parameters:

data (bytes)

Return type:

SecretKey

class quantum_safe.types.KeyPair(public, secret)[source]

Bases: object

A matched public/secret key pair.

This is what keygen functions return. It deliberately does NOT inherit from BaseKey — it’s a container, not a key.

Parameters:
to_cbor_bundle()[source]

Serialize both keys as a CBOR bundle.

The bundle contains both public and secret key material. Treat it with the same care as the secret key.

Return type:

bytes

classmethod from_cbor_bundle(data)[source]

Deserialize a key pair from a CBOR bundle.

Parameters:

data (bytes)

Return type:

KeyPair

class quantum_safe.types.MigrationState(value)[source]

Bases: Enum

The PQC migration state of a key.

States progress in one direction (you can’t go from PQC_PREFERRED back to CLASSICAL_ONLY through normal operations — that requires an explicit downgrade call that logs a warning).

class quantum_safe.types.kem.CipherText(data, algorithm)[source]

Bases: object

The ciphertext output of KEM encapsulation.

This is what the encapsulator transmits to the decapsulator. It’s not secret — but it is authenticated (MAC’d or KEM-authenticated depending on the scheme), so any modification will cause decapsulation to fail.

Size varies by algorithm:

ML-KEM-512: 768 bytes ML-KEM-768: 1088 bytes ML-KEM-1024: 1568 bytes

Parameters:
class quantum_safe.types.kem.HybridCipherText(classical_ct, pqc_ct, algorithm)[source]

Bases: object

Ciphertext from a hybrid KEM: classical ephemeral + PQC encapsulation.

The wire format is:

classical_ct_len (2 bytes, big-endian uint16) || classical_ct || pqc_ct

This framing allows the receiver to split the two components without needing out-of-band length information.

Parameters:
to_bytes()[source]

Encode as length-prefixed wire format.

Return type:

bytes

classmethod from_bytes(data, algorithm)[source]

Decode from length-prefixed wire format.

Parameters:
Return type:

HybridCipherText

class quantum_safe.types.kem.SharedSecret(data, algorithm, is_hybrid=False)[source]

Bases: object

The shared secret output of a KEM operation.

This is 32 bytes of symmetric key material derived from the KEM. Like SecretKey, it zeroizes on deletion.

You should use this as input to a KDF (e.g. HKDF) to derive actual encryption keys — don’t use it directly as an AES key without further processing.

The library does this for you in quantum_safe.protocols.envelope.

Parameters:
property is_hybrid: bool

Whether this secret was derived from a hybrid KEM.

derive_key(length=32, salt=None, info=b'')[source]

Derive a key from this shared secret using HKDF-SHA256.

This is a convenience wrapper. For full control, use cryptography.hazmat.primitives.kdf.hkdf directly.

Parameters:
  • length (int) – Desired output length in bytes (max 255 * 32 = 8160).

  • salt (bytes | None) – Optional salt. Defaults to a zero-filled string if None.

  • info (bytes) – Application-specific context. Include your app name and version to prevent cross-context key reuse.

Return type:

bytes

Returns:

Raw key bytes of the requested length.

Example

enc_key = ss.derive_key(32, info=b”myapp-encryption-v1”) mac_key = ss.derive_key(32, info=b”myapp-mac-v1”)

class quantum_safe.types.SignedMessage(message, signature, algorithm, context=b'', signer_fingerprint='', signed_at=<factory>, is_hybrid=False)[source]

Bases: object

A message with its signature(s) and metadata.

Immutable (frozen dataclass) — once created, the message and signature cannot be changed. This prevents accidental mutation of audit records.

message

The original message that was signed.

signature

The signature bytes. For hybrid, this contains both sub-signatures in a length-prefixed format.

algorithm

The signing algorithm, e.g. ‘ML-DSA-65’ or ‘Ed25519+ML-DSA-65’.

context

Domain-separation context (up to 255 bytes). Should include your application name and version.

signer_fingerprint

The fingerprint of the public key used to sign, for quick lookup without re-verifying.

signed_at

Unix timestamp (float) of when the signature was created. This is metadata only — it’s not part of the signed content and should not be relied upon for security decisions.

is_hybrid

Whether this is a hybrid (classical + PQC) signature.

Parameters:
to_cbor()[source]

Serialize to CBOR for storage or transmission.

Return type:

bytes

classmethod from_cbor(data)[source]

Deserialize from CBOR bytes.

Parameters:

data (bytes)

Return type:

SignedMessage

to_jwt_payload()[source]

Produce a JWT payload dict for this signed message.

The JWS (JSON Web Signature) representation follows draft-ietf-jose-pqc-signatures for algorithm identifiers.

Note: this produces the payload dict. To get a full JWT string, use quantum_safe.protocols.jwt.sign() instead.

Return type:

dict[str, Any]

class quantum_safe.types.signatures.HybridSignature(classical_sig, pqc_sig, classical_algo, pqc_algo)[source]

Bases: object

Internal representation of a hybrid signature.

Stores the classical and PQC sub-signatures separately before they’re combined into a SignedMessage. This is an intermediate type used inside HybridSign; callers don’t normally deal with it directly.

Wire format (CBOR-encoded):

{
    "classical_sig": bytes,
    "pqc_sig": bytes,
    "classical_algo": str,
    "pqc_algo": str,
}
Parameters:
to_bytes()[source]

Encode as CBOR for embedding in a SignedMessage.signature field.

Return type:

bytes

classmethod from_bytes(data)[source]

Decode from CBOR bytes.

Parameters:

data (bytes)

Return type:

HybridSignature

Utilities

quantum_safe.types.keys.generate_nonce(length=32)[source]

Generate cryptographically secure random bytes.

Thin wrapper around os.urandom that documents our intent clearly. Don’t use random.randbytes() — it’s not cryptographically secure.

Parameters:

length (int)

Return type:

bytes

Note

generate_nonce(length) emits a UserWarning if length < 12, since 12 bytes is the minimum nonce size for AEAD ciphers such as AES-GCM. The default of 32 bytes is always safe.