Namespace: WvdS.System.Security.Cryptography.Encryption
Static class for AES-GCM encryption with Post-Quantum key support. Supports classical, hybrid and pure PQ encryption.
This class provides three encryption approaches:
| Mode | Classical | ML-KEM | Usage |
| Classic | RSA-OAEP / ECDH | - | Standard .NET behavior |
| Hybrid | RSA-OAEP / ECDH | ✓ | Maximum security |
| PostQuantum | - | ✓ | Pure post-quantum |
Encrypts data with AES-GCM using an ML-KEM shared secret.
// Shared Secret from ML-KEM Key Exchange byte[] sharedSecret = session.SharedSecret; // Encrypt byte[] plaintext = Encoding.UTF8.GetBytes("Secret message"); byte[] encrypted = SymmetricEncryptionExtensions.EncryptWithPqKey( plaintext, sharedSecret); // With Additional Authenticated Data (AAD) byte[] aad = Encoding.UTF8.GetBytes("Context-Info"); byte[] encryptedWithAad = SymmetricEncryptionExtensions.EncryptWithPqKey( plaintext, sharedSecret, associatedData: aad);
byte[] decrypted = SymmetricEncryptionExtensions.DecryptWithPqKey( encrypted, sharedSecret, associatedData: aad); // If used during Encrypt string message = Encoding.UTF8.GetString(decrypted);
Combines RSA-OAEP key encapsulation with ML-KEM for quantum-safe hybrid encryption.
using var rsa = RSA.Create(4096); var (mlKemPublicKey, mlKemPrivateKey) = PqCrypto.GenerateKeyPair(); byte[] plaintext = GetSecretData(); // Hybrid encryption (RSA + ML-KEM) HybridEncryptedData encrypted = SymmetricEncryptionExtensions.EncryptHybrid( plaintext, rsa, // Recipient's RSA Public Key mlKemPublicKey, // Recipient's ML-KEM Public Key CryptoMode.Hybrid); // Serialize for transport byte[] serialized = encrypted.ToBytes();
// Deserialize HybridEncryptedData encrypted = HybridEncryptedData.FromBytes(serialized); // Decrypt byte[] plaintext = SymmetricEncryptionExtensions.DecryptHybrid( encrypted, rsaPrivateKey, // RSA Private Key mlKemPrivateKey); // ML-KEM Private Key
ECIES-style encryption with ephemeral ECDH and ML-KEM.
using var recipientEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP384); var (mlKemPublicKey, mlKemPrivateKey) = PqCrypto.GenerateKeyPair(); byte[] plaintext = GetSecretData(); // ECDH + ML-KEM hybrid encryption HybridEncryptedData encrypted = SymmetricEncryptionExtensions.EncryptEcdhPq( plaintext, recipientEcdh, // Recipient's ECDH Public Key mlKemPublicKey, // ML-KEM Public Key CryptoMode.Hybrid); // Ephemeral ECDH Public Key is contained in encrypted.EphemeralPublicKey
byte[] plaintext = SymmetricEncryptionExtensions.DecryptEcdhPq( encrypted, recipientEcdhPrivateKey, mlKemPrivateKey);
Direct AES-256-GCM encryption without key encapsulation.
byte[] key = RandomNumberGenerator.GetBytes(32); // 256-bit Key byte[] plaintext = GetData(); // Standard AES-GCM byte[] encrypted = SymmetricEncryptionExtensions.EncryptAesGcm( plaintext, key); // With AAD byte[] aad = Encoding.UTF8.GetBytes("message-context"); byte[] encryptedWithAad = SymmetricEncryptionExtensions.EncryptAesGcm( plaintext, key, associatedData: aad);
Output format:
+------------------------------------------+ | [12 Bytes] Nonce (randomly generated) | | [n Bytes] Ciphertext | | [16 Bytes] GCM Authentication Tag | +------------------------------------------+
byte[] plaintext = SymmetricEncryptionExtensions.DecryptAesGcm( encrypted, key, associatedData: aad); // If used
For large files with chunk-based processing.
byte[] key = RandomNumberGenerator.GetBytes(32); using var inputStream = File.OpenRead("large-file.dat"); using var outputStream = File.Create("large-file.enc"); SymmetricEncryptionExtensions.EncryptStream( inputStream, outputStream, key, chunkSize: 64 * 1024); // 64 KB chunks (default)
Chunk format:
+---------------------------------------------+ | [12 Bytes] Base Nonce | +---------------------------------------------+ | Chunk 0: | | [4 Bytes] Chunk length | | [n Bytes] Encrypted data | | [16 Bytes] GCM Tag | +---------------------------------------------+ | Chunk 1: (Nonce = Base + 1) | | [4 Bytes] Chunk length | | [n Bytes] Encrypted data | | [16 Bytes] GCM Tag | +---------------------------------------------+ | ... more chunks ... | +---------------------------------------------+ | [4 Bytes] End marker (0x00000000) | +---------------------------------------------+
using var inputStream = File.OpenRead("large-file.enc"); using var outputStream = File.Create("large-file.decrypted"); SymmetricEncryptionExtensions.DecryptStream( inputStream, outputStream, key);
Derives an AES-256 key from a shared secret.
byte[] sharedSecret = GetMlKemSharedSecret(); // Standard derivation byte[] aesKey = SymmetricEncryptionExtensions.DeriveAesKey(sharedSecret); // With salt and info byte[] salt = RandomNumberGenerator.GetBytes(32); byte[] info = Encoding.UTF8.GetBytes("MyApp-Encryption-Key"); byte[] aesKeyCustom = SymmetricEncryptionExtensions.DeriveAesKey( sharedSecret, salt: salt, info: info);
Internal implementation: HKDF-SHA256 with info=„WvdS-PQ-AES-Key“
Derives multiple keys for different purposes.
byte[] sharedSecret = GetMlKemSharedSecret(); var (encryptionKey, macKey, iv) = SymmetricEncryptionExtensions.DeriveMultipleKeys( sharedSecret, salt: optionalSalt); // encryptionKey: 32 bytes (AES-256) // macKey: 32 bytes (HMAC) // iv: 16 bytes (Initialization vector)
Container for hybrid-encrypted data with serialization.
| Property | Type | Description |
|---|---|---|
Mode | CryptoMode | Encryption mode used |
ClassicEncapsulatedKey | byte[]? | RSA-encrypted content key |
EphemeralPublicKey | byte[]? | Ephemeral ECDH public key |
PqCiphertext | byte[]? | ML-KEM ciphertext |
EncryptedContent | byte[] | AES-GCM encrypted data |
HybridEncryptedData encrypted = EncryptData(); // To byte array byte[] serialized = encrypted.ToBytes(); // From byte array HybridEncryptedData restored = HybridEncryptedData.FromBytes(serialized);
Simplified API for pure PQ encryption.
// Generate key pair var (publicKey, privateKey) = PqCrypto.GenerateKeyPair(); // Encrypt byte[] plaintext = Encoding.UTF8.GetBytes("Secret message"); var (ciphertext, encryptedData) = PqCrypto.Encrypt(plaintext, publicKey); // Decrypt byte[] decrypted = PqCrypto.Decrypt(ciphertext, encryptedData, privateKey);
| Method | Parameters | Return |
|---|---|---|
EncryptWithPqKey | byte[] plaintext, byte[] sharedSecret, byte[]? aad | byte[] |
DecryptWithPqKey | byte[] ciphertext, byte[] sharedSecret, byte[]? aad | byte[] |
EncryptHybrid | byte[] plaintext, RSA pubKey, byte[] pqPubKey, CryptoMode? | HybridEncryptedData |
DecryptHybrid | HybridEncryptedData, RSA privKey, byte[] pqPrivKey | byte[] |
EncryptEcdhPq | byte[] plaintext, ECDiffieHellman pubKey, byte[] pqPubKey, CryptoMode? | HybridEncryptedData |
DecryptEcdhPq | HybridEncryptedData, ECDiffieHellman privKey, byte[] pqPrivKey | byte[] |
EncryptAesGcm | byte[] plaintext, byte[] key, byte[]? aad | byte[] |
DecryptAesGcm | byte[] ciphertext, byte[] key, byte[]? aad | byte[] |
EncryptStream | Stream input, Stream output, byte[] key, int chunkSize | void |
DecryptStream | Stream input, Stream output, byte[] key | void |
DeriveAesKey | byte[] sharedSecret, byte[]? salt, byte[]? info | byte[] |
DeriveMultipleKeys | byte[] sharedSecret, byte[]? salt | (byte[], byte[], byte[]) |
| Method | Parameters | Return |
|---|---|---|
GenerateKeyPair | - | (byte[] PublicKey, byte[] PrivateKey) |
Encrypt | byte[] plaintext, byte[] recipientPublicKey | (byte[] Ciphertext, byte[] EncryptedData) |
Decrypt | byte[] ciphertext, byte[] encryptedData, byte[] privateKey | byte[] |
using WvdS.System.Security.Cryptography; using WvdS.System.Security.Cryptography.Encryption; // 1. Generate keys (Recipient) using var rsa = RSA.Create(4096); var (mlKemPublicKey, mlKemPrivateKey) = PqCrypto.GenerateKeyPair(); // 2. Transmit public keys to sender // (In practice: certificate with embedded PQ keys) // --- Sender --- // 3. Encrypt message byte[] message = File.ReadAllBytes("document.pdf"); HybridEncryptedData encrypted = SymmetricEncryptionExtensions.EncryptHybrid( message, rsa, // Recipient's RSA Public Key mlKemPublicKey, // Recipient's ML-KEM Public Key CryptoMode.Hybrid); // 4. Serialize and send byte[] package = encrypted.ToBytes(); File.WriteAllBytes("document.encrypted", package); // --- Recipient --- // 5. Receive and deserialize byte[] receivedPackage = File.ReadAllBytes("document.encrypted"); HybridEncryptedData receivedData = HybridEncryptedData.FromBytes(receivedPackage); // 6. Decrypt byte[] decrypted = SymmetricEncryptionExtensions.DecryptHybrid( receivedData, rsa, // Own RSA Private Key mlKemPrivateKey); // Own ML-KEM Private Key File.WriteAllBytes("document.decrypted.pdf", decrypted);
DeriveAesKey without salt is deterministic - only for specific use casesCombined Key = HKDF-SHA256(
ikm = classicSecret || pqSecret,
info = "WvdS-Hybrid-Key"
)
Even if an attacker compromises the classical secret, the encryption remains protected by the PQ secret (and vice versa).
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional