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.
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.
"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 BriefA 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
How Each Operation Works
Every user action follows a deterministic cryptographic path — designed so the server remains blind to plaintext at every step.
Registration
Client-side key generation — server receives only encrypted material
Fetches the server's global public key from Azure Key Vault via the API.
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.
Sends to the API: encrypted private key, client public key (clear), encrypted password, and user metadata. No plaintext credentials or keys leave the device.
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.
Login & Key Activation
Private key reconstructed in memory — never cached to disk
User enters credentials. The API authenticates and returns the encrypted RSA private key from the database.
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.
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.
Saving Invoice Data
Double-wrapped keys ensure only the client can ever read their own data
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).
Sends encrypted data, RSA-wrapped AES key, and RSA-wrapped IV. The payload is opaque in transit — HTTPS provides an additional transport layer.
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.
Reading Invoice Data
Server returns ciphertext — client performs all decryption locally
Returns the encrypted invoice data together with the RSA-wrapped AES key and IV (all in client-encrypted form). No decryption occurs server-side.
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.
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?
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.
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.
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.
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.
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.
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
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.
