End-to-End Encryption

A zero-knowledge encryption architecture for a Netherlands-based invoicing platform — protecting sensitive financial data and PII at rest and in transit across a web and mobile application, without compromising usability.

ASP.NET Core Blazor .NET MAUI Azure Key Vault Hybrid RSA + AES Zero-Knowledge Design
Industry SaaS Invoicing / FinTech
Region Western Europe
Platforms Web (Blazor) · iOS · Android
Security Model Zero-Knowledge at Rest
Stack C# · ASP.NET Core · Blazor · .NET MAUI · Azure

Protecting Financial Data End to End

A Netherlands-based B.V. operating a SaaS invoicing platform needed a robust data protection strategy for their web and mobile application. The platform stores sensitive financial records and personal information for small businesses — data that had to be shielded from exposure at every layer of the stack.

The core requirement was clear: invoice data and PII must be encrypted both in transit and at rest. But usability could not be sacrificed. Users needed seamless multi-device access — logging in from a phone or a new laptop and seeing the same data — without ever transmitting plaintext through the system.

A particularly nuanced challenge emerged around the invoice email delivery feature. When a user requests that an invoice be sent to their client as a PDF attachment, the backend must momentarily decrypt the data to generate the document — then immediately eliminate all traces of unencrypted material, including the email itself.

Financial data and PII encrypted at rest — server must never store or access plaintext
Secure multi-device access without re-uploading credentials or keys
Ephemeral decryption for PDF invoice generation — zero persistence of plaintext
Auto-deletion of sent emails to prevent unencrypted records accumulating in mailboxes
Consistent security model across Blazor web and .NET MAUI mobile (iOS & Android)

"The server should never be able to read a user's invoice data — not even us. If we can't see it, it can't be leaked."

— Engineering Brief
0 Plaintext Stored Server-Side
2 Encryption Layers
3 Platforms Protected

A Hybrid RSA + AES Architecture

Asymmetric keys handle identity and key exchange. Symmetric keys handle bulk data. Neither ever lands on the server in a form it can use.

Per-User RSA Key Pairs

Each user account owns an RSA key pair generated on the client during sign-up. The public key is stored on the server unencrypted. The private key is encrypted with the user's own password and stored encrypted — the server never holds a usable copy.

AES for Data, RSA for Keys

Invoice data is encrypted with a fresh AES key generated client-side for each write. The AES key itself is then encrypted with the server's RSA public key for transit, then re-encrypted at rest using the client's RSA public key — so only the client can ever decrypt it back.

Global Key in Azure Key Vault

A server-level RSA key pair is stored exclusively in Azure Key Vault. It is used for two purposes: distributing the server's public key to clients during registration, and — under strict conditions — enabling ephemeral server-side decryption for PDF invoice generation.

Where Keys Live and Travel

Every component in the system holds only what it needs — and nothing it shouldn't.

Encryption Architecture — Key Placement & Data Flow

Client Blazor Web · iOS · Android (.NET MAUI) RSA Private Key In memory only · never persisted locally AES Keys + IV Generated per write · ephemeral RSA Public Key (Server) Used to encrypt AES keys for transit Encrypts all data before sending Decrypts all data upon receipt ASP.NET Core API Azure App Service · Stateless RSA Private Key (Transit) Used to unwrap AES keys on write Client RSA Public Key Used to re-wrap AES keys at rest No Plaintext Access Cannot decrypt stored data at rest Key re-encryption on every write AES → RSA transit wrap → RSA rest wrap Azure Key Vault Global RSA Key Pair · HSM-backed Global Public Key Distributed during registration Global Private Key — PDF generation only Azure Database All data encrypted at rest Client RSA Public Key (clear) Public by design Client RSA Private Key (encrypted) Locked by user's password Invoice Data (AES-encrypted) Ciphertext only AES Key + IV (RSA-encrypted, client key) Encrypted Payload Re-wrapped AES Keys Key fetch Store Retrieve PDF flow only Private key loaded into memory at login · cleared on logout Zero knowledge of stored data · re-encryption relay only

How Each Operation Works

Every user action follows a deterministic cryptographic path — designed so the server remains blind to plaintext at every step.

01

Registration

Client-side key generation — server receives only encrypted material

Client

Fetches the server's global public key from Azure Key Vault via the API.

Client

Generates a fresh RSA key pair locally. Encrypts the private key using the user's chosen password. Encrypts the password itself using the server's global public key.

Transit

Sends to the API: encrypted private key, client public key (clear), encrypted password, and user metadata. No plaintext credentials or keys leave the device.

Server

Stores the client RSA public key (clear), stores the encrypted private key as-is, and stores the user's encrypted password. The server cannot derive the private key — it does not know the user's raw password.

02

Login & Key Activation

Private key reconstructed in memory — never cached to disk

Client

User enters credentials. The API authenticates and returns the encrypted RSA private key from the database.

Client

Decrypts the private key using the user's password, entirely in memory on the device. The decrypted key is held in-session for data operations and discarded on logout.

Multi-device

Because the same encrypted private key is stored server-side and the same password unlocks it, users logging in on a new device get an identical in-memory private key — enabling seamless access to all encrypted data without any additional key management.

03

Saving Invoice Data

Double-wrapped keys ensure only the client can ever read their own data

Client

Generates a fresh AES key and IV for this write operation. Encrypts the invoice data with AES. Encrypts the AES key and IV with the server's RSA public key (for transit protection).

Transit

Sends encrypted data, RSA-wrapped AES key, and RSA-wrapped IV. The payload is opaque in transit — HTTPS provides an additional transport layer.

Server

Decrypts the AES key and IV using the server's RSA private key (this is the one moment the server sees the AES key). Immediately re-encrypts the AES key and IV using the client's RSA public key. Stores the encrypted data unchanged, and stores the re-wrapped AES key. The server then forgets the plaintext AES key.

04

Reading Invoice Data

Server returns ciphertext — client performs all decryption locally

Server

Returns the encrypted invoice data together with the RSA-wrapped AES key and IV (all in client-encrypted form). No decryption occurs server-side.

Client

Uses the in-memory RSA private key to unwrap the AES key and IV. Uses the AES key and IV to decrypt the invoice data. Renders the plaintext only in the UI — it is never written to local storage or cache.

Invoice Email Delivery — Ephemeral Decryption

When a user sends an invoice to their client via email, the backend must briefly decrypt the data to generate a PDF. Every step was designed to leave no trace.

The invoicing application allows users to send a generated PDF invoice as an email attachment to their clients — with a CC to their own address. This is where the zero-knowledge model faces its hardest test: generating a PDF requires plaintext data, but the server must not retain any unencrypted material.

The solution uses the global RSA key pair in Azure Key Vault as a one-time bridge. When the user requests PDF delivery, the client encrypts the user's password with the global public key and sends it alongside the request. The backend then performs a tightly scoped ephemeral decryption sequence — and aggressively clears all sensitive material from memory once the email is sent.

The outgoing email is sent via the platform's transactional email account. Immediately after sending, an automated process deletes the message from the sent folder — ensuring no copy of the unencrypted invoice persists in any mailbox.

1
Client sends PDF request + user password encrypted with global public key
2
Server fetches global private key from Azure Key Vault and decrypts the password
3
Server retrieves encrypted client private key from DB and decrypts it using the recovered password
4
Uses client private key to unwrap the AES key, then AES key to decrypt the invoice data
5
Generates the PDF in memory, attaches it to an email, and sends it to the client (CC: user)
6
All decrypted keys, the plaintext data, and the PDF are cleared from memory. The sent email is automatically deleted from the outbox — no unencrypted record persists anywhere.

Why encrypt the password for transit?

The user's password itself is the unlock key for their RSA private key. Even over HTTPS, the system was designed so that no component — not even the API layer — sees the raw password in transit. Encrypting it with the global public key means only Azure Key Vault can produce the plaintext password, and only within the tightly scoped PDF generation flow.

The delete-from-sent rule

Using a shared platform email account to send invoices means sent messages accumulate in a mailbox — a mailbox that may be accessible to infrastructure staff or backed up in plaintext. Auto-deleting every sent message immediately after dispatch eliminates this exposure vector entirely.

Memory hygiene after generation

The ephemeral decryption sequence is wrapped in a scoped operation. On completion — or on any exception path — all derived keys (password, client private key, AES key, IV) and the generated PDF bytes are explicitly zeroed and released. No garbage collection pass is relied upon for security-sensitive material.

Key Security Design Choices

Every architectural decision was stress-tested against one question: what happens if the server is compromised?

01

Client-Side Key Generation

The user's RSA key pair is generated on the device, not on the server. The server never holds an unencrypted private key at any point — not even momentarily during registration. If the server is breached, stored private keys remain locked by the user's password.

02

Key Re-Encryption on Write

AES keys arrive at the server wrapped in the server's RSA public key (transit protection). The server unwraps them, immediately re-wraps them in the client's RSA public key, and stores only the re-wrapped form. The AES key is never stored in a form the server can independently decrypt.

03

Private Key in Memory Only

After login, the decrypted RSA private key lives exclusively in application memory. It is never written to local storage, browser cache, or device disk. Logout explicitly zeros the in-memory reference, limiting exposure to the active session window.

04

Azure Key Vault for Global Keys

The global RSA key pair used for registration bootstrap and PDF decryption flow lives exclusively in Azure Key Vault. Private key material never leaves the vault — all operations that require it call into the vault's API, keeping the key in a hardware-backed secure boundary.

05

Fresh AES Key Per Write

Each data write generates a new AES key and IV. This means a compromise of one AES key does not expose any other record. The cost is marginally higher cryptographic overhead per write — a trade-off accepted early in the design.

06

Email Sent-Folder Auto-Delete

Invoice PDFs are generated ephemerally and sent immediately. The outgoing email is automatically deleted from the sent folder post-dispatch. This ensures that even the email infrastructure — which has no knowledge of the encryption model — does not accumulate plaintext invoice records.

Built With Precision

Web Client
Blazor C# WebAssembly / Server
Mobile Client
.NET MAUI C# iOS Android
Backend
ASP.NET Core C# REST API Azure App Service
Security & Storage
Azure Key Vault Azure Database Hybrid RSA + AES HTTPS / TLS
Zero plaintext stored server-side — data is unreadable without the user's private key
Multi-device access without key synchronisation complexity — password is the key
Invoice email delivery leaves no unencrypted trace in any mailbox or server memory

Need encryption done right?

Whether you're building a zero-knowledge SaaS, securing sensitive records, or navigating data-at-rest requirements — we've designed and shipped this in production.