Color scheme: Dark
You are here: start » en » Documentation » Post-Quantum Cryptography » WKL.System.Security.Cryptography overview » Concepts » Keys and key storage

Keys and key storage

graph TD
    NEW["Newly issued certificate"]
    PEM["Certificate loaded from PEM or DER"]
    PFX["PFX file, PBES2 with AES-256"]
    STORE["Key file next to the certificate store"]
    OS["Operating system key store: DPAPI or Secret Service"]
    BAK["Backup file, Argon2id and AES-256-GCM"]
    TABLE["Process key table, by thumbprint"]
    NEW -- "automatic" --> TABLE
    PEM -- "Attach method" --> TABLE
    PFX -- "ImportPfx" --> TABLE
    TABLE -- "ExportPfx" --> PFX
    TABLE -- "X509Store.Add with CryptoMode" --> STORE
    STORE -- "X509Store.Find with restorePqKeys" --> TABLE
    OS -. "protects" .-> STORE
    STORE -- "BackupAllPqKeys" --> BAK
    BAK -- "RestoreFromBackup" --> STORE

Figure: At run time, private PQ keys live in a table inside the process. For long-term storage, they go into a PFX file or into a key file next to the certificate store, protected by the operating system's key store. A backup bundles all key files under one password.

The .NET X509Certificate2 class doesn't know ML-DSA or ML-KEM keys, so it can't carry them. That's why the library keeps private PQ keys outside the certificate and associates them with it through its thumbprint.

Key features

  • Automatic association at issuance: A newly issued certificate finds its private PQ key without any further call.
  • Later association with the Attach methods, for certificates loaded from files without a key.
  • Transport as a PFX file with ExportPfx and ImportPfx, encrypted with PBES2 and AES-256.
  • Storage next to the certificate store through the X509Store extensions, protected by the operating system's key store on Windows and Linux.
  • Backup of all stored keys to a password-protected file with PqKeyStoreManager, which you can restore on a different machine and under a different account.

Core concepts

The process key table

When the library issues a certificate with a PQ part, it puts the private PQ key into a table that applies to the whole process. The table is keyed by the certificate's thumbprint, so every X509Certificate2 object with the same content finds the same key, including a freshly loaded copy. The entries remain until the process ends.

A certificate that another process issued and that you load from a PEM or DER file has no key in the table. Associate the key with the method that matches the key type:

KeyReadAssociate
ML-DSA key of a hybrid certificateGetAlternativePrivateMLDsaKeyAttachAlternativePrivateMLDsaKey
ML-DSA key of a pure ML-DSA certificateGetPrivateMLDsaKeyAttachPrivateMLDsaKey
ML-KEM keyGetPrivateMLKemKeyAttachPrivateMLKemKey
Composite ML-KEM keyGetPrivateCompositeMLKemKeyAttachPrivateCompositeMLKemKey

The read methods return null if the process doesn't know a key. The .NET HasPrivateKey property only refers to classical keys. For a pure ML-DSA certificate, it stays false even after you associate the key.

Like the .NET method, CopyWithPrivateKey(privateKey, mode) only attaches the classical key. The copy has the same thumbprint, so it finds a PQ key that's already associated. The method doesn't generate a PQ key. With CryptoMode.Hybrid, it only checks that the certificate carries an alternative ML-DSA key.

PFX files

ExportPfx writes the certificate, the classical key, and the PQ key to a password-protected file. X509Certificate2ExportExtensions.ImportPfx reads the file and associates the PQ key again. This is how you move keys between processes and machines.

With a PQ key, ExportPfx encrypts every part of the file with PBES2 (RFC 8018): AES-256-CBC with a key from PBKDF2 using HMAC-SHA-256 and 2,048 iterations. SHA-256 computes the file's integrity checksum (MAC). That's the OpenSSL 3.6 default. An overload that takes PbeParameters lets you choose the algorithm and the iteration count. For details, see Transfer certificates with PQ keys as PFX files.

Storage next to the certificate store

The operating system's certificate store doesn't accept PQ keys. So X509Store.Add(certificate, mode) adds the certificate to the store as usual and also writes the PQ key to a file named <thumbprint>.pqkey in the WKL.Crypto/PqKeys folder under the account's local application data folder (%LOCALAPPDATA% on Windows, ~/.local/share on Linux). X509Store.Find(findType, findValue, validOnly, restorePqKeys: true) reads the file during the search and associates the key again.

The operating system's key store protects the file's content for the signed-in account:

Operating systemProtection
WindowsWindows Data Protection API (DPAPI) for the signed-in account
Linux with Secret Service (for example GNOME Keyring or KWallet)AES-256-GCM with a random wrapping key that the account's Secret Service holds; the file is bound to its certificate's thumbprint
macOS, Linux without Secret Serviceno storage: X509Store.Add in a PQ mode and PersistPqKeys throw PlatformNotSupportedException, and the certificate store stays unchanged

On systems without a key store, keep the keys in a PFX file.

RestorePqKeys still reads key files that an earlier version of the library wrote outside Windows. If a key store is available, the method immediately rewrites such a file in the new format.

Backup

PqKeyStoreManager.BackupAllPqKeys(backupPath, password) decrypts all key files of the account and writes the keys to a backup file:

  • Argon2id (RFC 9106) derives the key from the password, with 3 passes, 64 MiB of memory, and a parallelism of 4.
  • AES-256-GCM encrypts and protects the content, so a modified file is rejected on restore.

RestoreFromBackup(backupPath, password) creates the key files under the protection of the account it runs in. That's why you can restore the backup on a different machine and under a different account. The method still reads backups from an earlier version of the library, but those restore only under the original account on the original machine.

CleanupOrphanedKeys deletes key files whose certificate is no longer in the certificate store.

Limitations

  • Protection against other accounts, not against your own. DPAPI and Secret Service hand the key to any program that runs under the same account. Anyone who can run code under the account can read the key files.
  • Storage only on Windows and on Linux with Secret Service. On other systems, and in Linux sessions without Secret Service, such as services and containers without a login session, the library doesn't create key files.
  • The password alone protects the PFX file and the backup. Whoever has the file and the password has the keys. Use a long, random password, and send it through a different channel than the file.
  • Older import programs. Programs that can't read PBES2, such as older Windows versions, can't import the PFX file. For them, ExportPfx with PbeEncryptionAlgorithm.TripleDes3KeyPkcs12 writes the weaker Triple DES variant.
  • Keys in memory. Private PQ keys stay unencrypted in the process's memory until the process ends.
  • Argon2id requires OpenSSL 3.2 or later. Backup and restore compute Argon2id in OpenSSL. Any version with ML-DSA, that is, 3.5 or later, meets this requirement. If the loaded installation doesn't provide Argon2id, the backup throws a CryptographicException that names ARGON2ID.
en/wiki/pqcrypt/lib/concepts/keys-and-key-storage.txt · Last modified: by Wolfgang van der Stille