Research preview

Key Management

PEM files, rotation, storage, permissions, and operational key hygiene.

Skip to content

Key types

MaterialSizeSensitivityLifetime
Public key800 BPublic — can be distributed freelyMatches key pair lifetime
Private key1 248 BSecret — protect at all costsMonths to years (rotate on policy)
Ciphertext768 BPer-session — safe to transmitSingle use
Shared secret32 BSecret — derive session keys, then discardSingle session

Generating keys

python
from vortex_pqc import generate_keypair

kp = generate_keypair()
# kp.public_key  → 800 bytes
# kp.private_key → 1248 bytes

Each call produces a fresh, statistically independent key pair using the OS CSPRNG.

PEM file storage

Writing keys

python
from vortex_pqc import PEMKind, write_pem_file, generate_keypair

kp = generate_keypair()

write_pem_file("server.pub", PEMKind.PUBLIC_KEY,  kp.public_key)
write_pem_file("server.key", PEMKind.PRIVATE_KEY, kp.private_key)
# server.key is automatically chmod 0600

Reading keys

python
from vortex_pqc import PEMKind, read_pem_file

pk = read_pem_file("server.pub", PEMKind.PUBLIC_KEY)
sk = read_pem_file("server.key", PEMKind.PRIVATE_KEY)

PEM labels

KindHeader
Public key-----BEGIN VORTEX256 PUBLIC KEY-----
Private key-----BEGIN VORTEX256 PRIVATE KEY-----
Ciphertext-----BEGIN VORTEX256 CIPHERTEXT-----
Shared secret-----BEGIN VORTEX256 SHARED SECRET-----

→ Full spec: PEM format

Storage recommendations

EnvironmentPublic keyPrivate key
DevelopmentPEM file in project (gitignored)PEM file, mode 0600, gitignored
Server / VMConfig directory, world-readable OKEncrypted volume or secrets manager
ContainerConfigMap / env varKubernetes Secret / Vault / sealed secret
Embedded / IoTFlash, provisioned at factorySecure element, TPM, or encrypted flash

Key rotation

python
from vortex_pqc import generate_keypair, PEMKind, write_pem_file

def rotate_keys(path_prefix: str) -> None:
    """Generate new key pair and archive old ones."""
    import shutil
    from pathlib import Path

    for suffix in ("pub", "key"):
        old = Path(f"{path_prefix}.{suffix}")
        if old.exists():
            shutil.move(old, old.with_suffix(".bak"))

    kp = generate_keypair()
    write_pem_file(f"{path_prefix}.pub", PEMKind.PUBLIC_KEY,  kp.public_key)
    write_pem_file(f"{path_prefix}.key", PEMKind.PRIVATE_KEY, kp.private_key)

Rotation policy guidelines

TriggerAction
Scheduled (e.g. annually)Generate new pair, update distribution
Personnel changeRotate immediately
Suspected compromiseRotate immediately, investigate
Algorithm migrationGenerate new pair with new library version

Private key anatomy

Understanding what's inside the 1248-byte private key:

┌────────────┬──────────────────────────────────────────┐
│ Bytes      │ Content                                  │
├────────────┼──────────────────────────────────────────┤
│ 0 – 383    │ pack(s) — secret polynomial              │
│ 384 – 1183 │ Public key (embedded copy)               │
│ 1184–1215  │ H(pk) — SHA3-256 hash of public key      │
│ 1216–1247  │ z — implicit rejection token             │
└────────────┴──────────────────────────────────────────┘

Never expose bytes 0–383 or 1216–1247. The embedded public key (384–1183) is safe to share.

.gitignore template

gitignore
# VORTEX key material — never commit
*.key
*.pem
server.key
server.pub