SymmetricEncryptionExtensions

Namespace: WvdS.System.Security.Cryptography.Encryption

Static class for AES-GCM encryption with Post-Quantum key support. Supports classical, hybrid and pure PQ encryption.

Overview

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

AES-GCM with PQ Key

EncryptWithPqKey

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);

DecryptWithPqKey

byte[] decrypted = SymmetricEncryptionExtensions.DecryptWithPqKey(
    encrypted,
    sharedSecret,
    associatedData: aad);  // If used during Encrypt
 
string message = Encoding.UTF8.GetString(decrypted);

Hybrid Encryption (RSA + ML-KEM)

Combines RSA-OAEP key encapsulation with ML-KEM for quantum-safe hybrid encryption.

EncryptHybrid

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();

DecryptHybrid

// Deserialize
HybridEncryptedData encrypted = HybridEncryptedData.FromBytes(serialized);
 
// Decrypt
byte[] plaintext = SymmetricEncryptionExtensions.DecryptHybrid(
    encrypted,
    rsaPrivateKey,  // RSA Private Key
    mlKemPrivateKey);  // ML-KEM Private Key

ECDH + ML-KEM Encryption

ECIES-style encryption with ephemeral ECDH and ML-KEM.

EncryptEcdhPq

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

DecryptEcdhPq

byte[] plaintext = SymmetricEncryptionExtensions.DecryptEcdhPq(
    encrypted,
    recipientEcdhPrivateKey,
    mlKemPrivateKey);

Core AES-GCM Operations

Direct AES-256-GCM encryption without key encapsulation.

EncryptAesGcm

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        |
+------------------------------------------+

DecryptAesGcm

byte[] plaintext = SymmetricEncryptionExtensions.DecryptAesGcm(
    encrypted,
    key,
    associatedData: aad);  // If used

Stream-based Encryption

For large files with chunk-based processing.

EncryptStream

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)           |
+---------------------------------------------+

DecryptStream

using var inputStream = File.OpenRead("large-file.enc");
using var outputStream = File.Create("large-file.decrypted");
 
SymmetricEncryptionExtensions.DecryptStream(
    inputStream,
    outputStream,
    key);

Key Derivation

DeriveAesKey

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“

DeriveMultipleKeys

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)

HybridEncryptedData Class

Container for hybrid-encrypted data with serialization.

Properties

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

Serialization

HybridEncryptedData encrypted = EncryptData();
 
// To byte array
byte[] serialized = encrypted.ToBytes();
 
// From byte array
HybridEncryptedData restored = HybridEncryptedData.FromBytes(serialized);

PqCrypto Convenience Class

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);

Methods Overview

SymmetricEncryptionExtensions

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[])

PqCrypto

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[]

Complete Example

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);

Security Notes

  • AES-GCM nonces must NEVER be reused
  • In hybrid mode, the key is derived from classical AND PQ secret
  • DeriveAesKey without salt is deterministic - only for specific use cases
  • Stream encryption uses incremental nonces per chunk
Key combination in hybrid mode:
Combined 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).

See Also


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: on 2026/01/30 at 12:12 AM