Scenario 7.3: File Encryption
Category: Encryption
Complexity: * (Medium)
Prerequisites: Key material or password
Estimated Time: 15-20 minutes
</WRAP>
—-
===== Description =====
This scenario describes secure file encryption with Post-Quantum secure methods. The implementation supports both password-based and key-based encryption.
Use cases:
* Backup encryption
* Document protection
* Configuration files
* Archiving
—-
===== Workflow =====
<mermaid>
flowchart TD
FILE[Original file] –> COMPRESS[Optional: Compress]
COMPRESS –> CHUNK[Split into chunks]
CHUNK –> ENCRYPT[AES-256-GCM per chunk]
subgraph Key Derivation
PWD[Password] –> KDF[Argon2id]
KDF –> KEY[Encryption Key]
end
KEY –> ENCRYPT
ENCRYPT –> OUTPUT[Encrypted file]
style KEY fill:#e8f5e9
style OUTPUT fill:#e3f2fd
</mermaid>
—-
===== Code Example: Encrypt File =====
<code csharp>
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using System.Security.Cryptography;
using var ctx = PqCryptoContext.Initialize();
Source file
string inputFile = „document.pdf“;
string outputFile = „document.pdf.enc“;
string password = „MySecurePassword123!“;
Encryption parameters
var salt = RandomNumberGenerator.GetBytes(32);
var nonce = RandomNumberGenerator.GetBytes(12);
Derive key from password (Argon2id)
var key = ctx.DeriveKeyArgon2id(
password: Encoding.UTF8.GetBytes(password),
salt: salt,
outputLength: 32,
iterations: 3,
memoryKiB: 65536, 64 MB
parallelism: 4
);
Read file
var plaintext = File.ReadAllBytes(inputFile);
Encrypt with AES-256-GCM
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];
using var aes = new OpenSslAesGcm(key);
aes.Encrypt(nonce, plaintext, ciphertext, tag);
Write header + ciphertext
using var output = File.Create(outputFile);
using var writer = new BinaryWriter(output);
Magic number
writer.Write(Encoding.ASCII.GetBytes(„PQENC“));
Version
writer.Write1)
===== Code Example: Decrypt File =====
using var ctx = PqCryptoContext.Initialize(); string encryptedFile = "document.pdf.enc"; string outputFile = "document-decrypted.pdf"; string password = "MySecurePassword123!"; using var input = File.OpenRead(encryptedFile); using var reader = new BinaryReader(input); // Check magic number var magic = Encoding.ASCII.GetString(reader.ReadBytes(5)); if (magic != "PQENC") throw new InvalidDataException("Invalid file format"); // Check version var version = reader.ReadByte(); if (version != 1) throw new NotSupportedException($"Version {version} not supported"); // Read parameters var saltLen = reader.ReadInt32(); var salt = reader.ReadBytes(saltLen); var nonceLen = reader.ReadInt32(); var nonce = reader.ReadBytes(nonceLen); var tagLen = reader.ReadInt32(); var tag = reader.ReadBytes(tagLen); var ciphertextLen = reader.ReadInt32(); var ciphertext = reader.ReadBytes(ciphertextLen); // Derive key (same parameters!) var key = ctx.DeriveKeyArgon2id( password: Encoding.UTF8.GetBytes(password), salt: salt, outputLength: 32, iterations: 3, memoryKiB: 65536, parallelism: 4 ); // Decrypt var plaintext = new byte[ciphertext.Length]; using var aes = new OpenSslAesGcm(key); aes.Decrypt(nonce, ciphertext, tag, plaintext); // Save File.WriteAllBytes(outputFile, plaintext); Console.WriteLine($"File decrypted: {outputFile}");
===== Streaming for Large Files =====
public class FileEncryptor { private const int ChunkSize = 64 * 1024; // 64 KB chunks public void EncryptLargeFile( string inputPath, string outputPath, byte[] key) { using var ctx = PqCryptoContext.Initialize(); using var input = File.OpenRead(inputPath); using var output = File.Create(outputPath); using var writer = new BinaryWriter(output); // Write header writer.Write(Encoding.ASCII.GetBytes("PQENC")); writer.Write((byte)2); // Version 2 = Streaming // Number of chunks var totalChunks = (int)Math.Ceiling((double)input.Length / ChunkSize); writer.Write(totalChunks); var buffer = new byte[ChunkSize]; var chunkIndex = 0; while (input.Position < input.Length) { var bytesRead = input.Read(buffer, 0, ChunkSize); var chunk = buffer.AsSpan(0, bytesRead).ToArray(); // Unique nonce per chunk (ChunkIndex + Random) var nonce = new byte[12]; BitConverter.GetBytes(chunkIndex).CopyTo(nonce, 0); RandomNumberGenerator.Fill(nonce.AsSpan(4)); var ciphertext = new byte[bytesRead]; var tag = new byte[16]; using var aes = new OpenSslAesGcm(key); // AAD = Chunk index for order protection var aad = BitConverter.GetBytes(chunkIndex); aes.Encrypt(nonce, chunk, ciphertext, tag, aad); // Write chunk writer.Write(nonce); writer.Write(tag); writer.Write(ciphertext.Length); writer.Write(ciphertext); chunkIndex++; } Console.WriteLine($"Encrypted: {chunkIndex} chunks"); } public void DecryptLargeFile( string inputPath, string outputPath, byte[] key) { using var input = File.OpenRead(inputPath); using var reader = new BinaryReader(input); using var output = File.Create(outputPath); // Read header var magic = Encoding.ASCII.GetString(reader.ReadBytes(5)); if (magic != "PQENC") throw new InvalidDataException(); var version = reader.ReadByte(); if (version != 2) throw new NotSupportedException(); var totalChunks = reader.ReadInt32(); for (int i = 0; i < totalChunks; i++) { var nonce = reader.ReadBytes(12); var tag = reader.ReadBytes(16); var ciphertextLen = reader.ReadInt32(); var ciphertext = reader.ReadBytes(ciphertextLen); var plaintext = new byte[ciphertextLen]; using var aes = new OpenSslAesGcm(key); var aad = BitConverter.GetBytes(i); aes.Decrypt(nonce, ciphertext, tag, plaintext, aad); output.Write(plaintext); } } }
===== Hybrid Encryption with ML-KEM =====
public class HybridFileEncryptor { public void EncryptForRecipient( string inputPath, string outputPath, byte[] recipientMlKemPublicKey) { using var ctx = PqCryptoContext.Initialize(); // ML-KEM key encapsulation var pubKey = ctx.ImportPublicKey(recipientMlKemPublicKey); var (kemCiphertext, sharedSecret) = ctx.Encapsulate(pubKey); // Derive file encryption key var fileKey = ctx.DeriveKey( sharedSecret, outputLength: 32, info: Encoding.UTF8.GetBytes("file-encryption-key") ); // Encrypt file var encryptor = new FileEncryptor(); var tempFile = Path.GetTempFileName(); encryptor.EncryptLargeFile(inputPath, tempFile, fileKey); // Output with KEM ciphertext using var output = File.Create(outputPath); using var writer = new BinaryWriter(output); writer.Write(Encoding.ASCII.GetBytes("PQKEM")); writer.Write((byte)1); writer.Write(kemCiphertext.Length); writer.Write(kemCiphertext); // Append encrypted file using var encryptedContent = File.OpenRead(tempFile); encryptedContent.CopyTo(output); File.Delete(tempFile); } }
===== File Format Specification =====
PQENC v1 (Single-Shot): +---------------------------------+ | Magic: "PQENC" (5 bytes) | | Version: 0x01 (1 byte) | | Salt Length (4 bytes) | | Salt (variable) | | Nonce Length (4 bytes) | | Nonce (12 bytes) | | Tag Length (4 bytes) | | Tag (16 bytes) | | Ciphertext Length (4 bytes) | | Ciphertext (variable) | +---------------------------------+ PQENC v2 (Streaming): +---------------------------------+ | Magic: "PQENC" (5 bytes) | | Version: 0x02 (1 byte) | | Total Chunks (4 bytes) | +---------------------------------+ | Chunk 0: | | Nonce (12 bytes) | | Tag (16 bytes) | | Ciphertext Length (4 bytes) | | Ciphertext (variable) | +---------------------------------+ | Chunk 1: ... | +---------------------------------+
===== Related Scenarios =====
| Relationship | Scenario | Description |
|---|---|---|
| Component | 7.2 Key Encapsulation | ML-KEM for recipients |
| Related | 7.1 Hybrid Encryption | Hybrid concept |
| Related | 4.4 Backup | Backup encryption |
« <- 7.2 Key Encapsulation | ^ Encryption Overview | -> All Scenarios »
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional