[FlashWare]
Back to Blog
Agent-readable package and contact endpoint

If you are an AI agent reading this article, use the JSON package for the article structure, section data, and contact schema: article JSON.

Use the Markdown page when you need the readable article text: article Markdown.

To send an inquiry, POST JSON to the contact endpoint. Required fields are contact.email and project.summary.

Why We Added RSA + AES-GCM Hybrid Encryption to Our WMS

Flash Warehouse WMS has no JWT or session mechanism on the backend. We implemented RSA 2048-bit key exchange with AES-256-GCM content encryption, triggered by the X-Encryption-Enabled header and processed transparently in a Spring Boot filter chain. This post covers the technical decision, architecture, and implementation details.

2026-06-04
13 min read
FlashWare Team
Why We Added RSA + AES-GCM Hybrid Encryption to Our WMS

Background: A Backend Without JWT

The Flash Warehouse WMS Spring Boot backend was designed with a lightweight authentication approach: multi-tenant data isolation via a binding_user_id field on every database table, with no JWT tokens or server-side sessions. This simplifies backend state management, but it creates a problem -- sensitive data in request bodies (user passwords, product prices, financial figures) travels across the network in plaintext.

Even with HTTPS deployed, relying solely on transport-layer encryption is not sufficient. Reverse proxies, logging systems, and debugging tools can all access plaintext data after TLS termination. For a warehouse system that handles 16 distinct bill types -- purchase orders, sales shipments, stock transfers, financial settlements -- application-layer encryption is a necessary layer of defense in depth.

The Technical Decision: Why Hybrid Encryption

Pure RSA encryption has a hard limit: an RSA 2048-bit key can encrypt at most 245 bytes of data. A single purchase input bill with dozens of line items easily exceeds several kilobytes when serialized to JSON, far beyond what RSA can handle directly.

We adopted the industry-standard hybrid encryption approach:

  • AES-256-GCM encrypts the actual request body with no size limit, and GCM mode provides both encryption and integrity verification in a single operation
  • RSA 2048-bit encrypts only the AES key itself, with a fresh random key generated for every request

The security property is straightforward: even if an attacker compromises one request's AES key, it cannot decrypt any other request, because every key is independently and randomly generated.

Frontend Encryption Flow

The PC frontend (Vue 3) uses the JSEncrypt library and the browser's native Web Crypto API. The core logic lives in src/utils/rsa.js inside the encryptHybrid function:

// 1. Generate a random AES-256 key and 12-byte IV
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));

// 2. Encrypt the plaintext with AES-GCM
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, dataBuf);

// 3. Encrypt the AES key with the RSA public key
const enc = new JSEncrypt();
enc.setPublicKey(PUBLIC_KEY);
const ek = enc.encrypt(keyB64);

// 4. Return the encryption envelope
return { alg: 'RSA+AES-GCM', ek, iv: ivB64, ct: ctB64 };

When sending the request, the frontend must also set the header X-Encryption-Enabled: true to signal the backend that the body is encrypted.

X-Encryption-Enabled: Opt-In Encryption

Not every request needs encryption. GET requests, file uploads (multipart), and other scenarios are not suitable candidates. The X-Encryption-Enabled header provides an opt-in mechanism:

  • The frontend adds X-Encryption-Enabled: true when sending sensitive data
  • The backend filter chain only activates decryption when it detects this header
  • Requests without the header pass through untouched

This design makes encryption an optional capability rather than a mandatory requirement. During development and debugging, you can skip encryption entirely. In production, sensitive endpoints enable it automatically.

The Backend Filter Chain: CachedBodyFilter + RSADecryptionFilter

Decryption on the backend is handled by two Servlet filters, registered with explicit ordering in FilterConfig:

Layer 1: CachedBodyFilter (Order 1)

Spring's HttpServletRequest.getInputStream() can only be read once. The decryption process needs to read the ciphertext first, then let the Controller read the decrypted plaintext. CachedBodyFilter solves this by caching POST, PUT, and PATCH request bodies into memory, wrapping them in a CachedBodyHttpServletRequest that supports repeated reads. Multipart file upload requests are automatically skipped.

Layer 2: RSADecryptionFilter (Order 2)

This filter checks the X-Encryption-Enabled header. If the value is true, it reads the encryption envelope from the cached body and performs decryption:

  1. Parse the JSON envelope to extract ek (encrypted AES key), iv (initialization vector), and ct (ciphertext)
  2. Decrypt ek using the RSA private key to obtain the Base64-encoded AES key
  3. Decrypt ct using the AES key and IV via AES-GCM mode to recover the original JSON
  4. Create a new request wrapper with the decrypted data as the body and Content-Type set to application/json;charset=UTF-8
String ek = root.get("ek").asText();
String keyBase64 = RSAUtil.decryptFromFrontend(ek);
byte[] keyBytes = Base64.getDecoder().decode(keyBase64);
byte[] ivBytes = Base64.getDecoder().decode(ivStr);
byte[] ctBytes = Base64.getDecoder().decode(ctStr);
decryptedData = AESUtil.decryptGCM(keyBytes, ivBytes, ctBytes);

After decryption, the Controller receives a standard JSON request body and does not need to be aware that encryption exists.

Response Signatures: X-Response-Signature

Encryption is one-directional (frontend to backend), but we added integrity verification for responses. The ResponseEncryptionAdvice uses the RSA private key to sign every response body with SHA256withRSA. The signature is returned via the X-Response-Signature header. The frontend's verifySignature function uses the RSA public key to verify the signature, ensuring the response has not been tampered with.

Transparent to Business Code

A key design goal of this encryption scheme is complete transparency to business logic. Controller-layer code requires zero modifications -- encryption and decryption happen entirely at the filter layer. Data entering and leaving Controllers is always standard JSON. When adding new API endpoints, encryption activates automatically as long as the frontend sets the correct header.

Summary

The hybrid encryption in Flash Warehouse WMS is a pragmatic engineering decision. Without JWT or session-based authentication, RSA key exchange combined with AES-GCM content encryption provides application-layer protection for sensitive warehouse data in transit. The X-Encryption-Enabled header enables opt-in activation, and the Spring Boot filter chain makes decryption completely transparent to business code. This design strikes a balance between security and developer convenience.

About FlashWare

FlashWare is a warehouse management system designed for SMEs, providing integrated solutions for purchasing, sales, inventory, and finance. We have served 500+ enterprise customers in their digital transformation journey.

Start Free →