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:
BaseKeyA 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:
raw (
bytes)algorithm (
str)migration_state (
MigrationState)backend_tag (
str)
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 key_type: KeyType¶
Whether this is a public or secret key.
- property migration_state: MigrationState¶
The PQC migration state of this key.
- class quantum_safe.types.SecretKey(raw, algorithm, migration_state=MigrationState.HYBRID_TRANSITION, backend_tag='unknown')[source]¶
Bases:
BaseKeyA 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:
raw (
bytes)algorithm (
str)migration_state (
MigrationState)backend_tag (
str)
Note
SecretKey._raw_bytearrayreturns a fresh mutablebytearraycopy 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 withctypes.memsetin atry/finallyblock: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 key_type: KeyType¶
Whether this is a public or secret key.
- property migration_state: MigrationState¶
The PQC migration state of this key.
- class quantum_safe.types.KeyPair(public, secret)[source]¶
Bases:
objectA 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.
- class quantum_safe.types.MigrationState(value)[source]¶
Bases:
EnumThe 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:
objectThe 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
- class quantum_safe.types.kem.HybridCipherText(classical_ct, pqc_ct, algorithm)[source]¶
Bases:
objectCiphertext 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.
Bases:
objectThe 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.
Whether this secret was derived from a hybrid KEM.
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:
- Return type:
- 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:
objectA 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:
- classmethod from_cbor(data)[source]¶
Deserialize from CBOR bytes.
- Parameters:
data (
bytes)- Return type:
- class quantum_safe.types.signatures.HybridSignature(classical_sig, pqc_sig, classical_algo, pqc_algo)[source]¶
Bases:
objectInternal 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, }
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.
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.