Data Encryption
MATIH encrypts data both in transit and at rest. The encryption subsystem is built on AES-256-GCM authenticated encryption, with a key management service that supports per-tenant keys, key rotation, key versioning, and integration with cloud key management services (Azure Key Vault, AWS KMS, GCP Cloud KMS).
Encryption at a Glance
| Dimension | Implementation |
|---|---|
| Algorithm | AES-256-GCM (Galois/Counter Mode) |
| Key size | 256 bits |
| IV length | 12 bytes (96 bits), randomly generated per encryption |
| Authentication tag | 128 bits |
| Encoding | Base64 for string transport |
| Key management | KeyManagementService with multi-tenant support |
| Transit encryption | TLS 1.2+ via cert-manager |
Encryption at Rest
The EncryptionService
The EncryptionService class provides the core encryption and decryption operations. It uses AES-256 in GCM mode, which provides both confidentiality and integrity through authenticated encryption.
Encryption format:
[IV (12 bytes)] [Ciphertext + Authentication Tag (variable length)]The IV is prepended to the ciphertext so that a single byte array (or Base64 string) contains everything needed for decryption.
String Encryption
// Create an encryption service with a base64-encoded key
EncryptionService encryption = new EncryptionService(base64Key);
// Encrypt a string
String ciphertext = encryption.encrypt("sensitive-data");
// Result: base64-encoded string containing IV + ciphertext + auth tag
// Decrypt a string
String plaintext = encryption.decrypt(ciphertext);
// Result: "sensitive-data"Byte Array Encryption
For binary data (files, serialized objects):
byte[] encrypted = encryption.encryptBytes(originalBytes);
byte[] decrypted = encryption.decryptBytes(encrypted);Tenant-Scoped Encryption
The EncryptionService supports encryption with Additional Authenticated Data (AAD). When encrypting tenant data, the tenant ID is used as AAD, which cryptographically binds the ciphertext to the tenant:
// Encrypt data for a specific tenant
String encrypted = encryption.encryptForTenant("sensitive-data", "acme-corp");
// Decrypt with the correct tenant ID
String decrypted = encryption.decryptForTenant(encrypted, "acme-corp");
// Attempting to decrypt with a different tenant ID FAILS
// (GCM authentication tag verification fails)
encryption.decryptForTenant(encrypted, "wrong-tenant"); // Throws EncryptionExceptionThis is a critical security feature. Even if an attacker obtains the encryption key, they cannot decrypt data by spoofing the tenant ID because the AAD is verified as part of GCM's authentication tag.
Encryption with Custom AAD
For use cases beyond tenant isolation, arbitrary AAD can be provided:
byte[] aad = "context-specific-data".getBytes(StandardCharsets.UTF_8);
byte[] encrypted = encryption.encryptWithAad(plaintext.getBytes(), aad);
byte[] decrypted = encryption.decryptWithAad(encrypted, aad);Key Management
The KeyManagementService manages the lifecycle of encryption keys, including generation, registration, rotation, per-tenant key assignment, and integration with external key providers.
Key Lifecycle
ACTIVE ─────────> ROTATED ─────────> INACTIVE ─────────> DELETED
| | |
| (new key | (grace period | (safe to
| registered) | expired) | delete)
| | |
Can encrypt Can only decrypt Cannot be used
and decrypt (backward compat)| Status | Can Encrypt | Can Decrypt | Description |
|---|---|---|---|
ACTIVE | Yes | Yes | Current key for new encryption operations |
ROTATED | No | Yes | Previous key, kept for decrypting old data |
INACTIVE | No | No | Decommissioned key, pending deletion |
Registering Keys
KeyManagementService kms = new KeyManagementService();
// Register a default encryption key
kms.registerDefaultKey("key-v1", base64EncodedKey);
// Register a tenant-specific key
kms.registerKey("acme-key-v1", base64EncodedKey, "acme-corp");
// Generate and register a new key
String newKey = kms.generateAndRegisterKey("key-v2");
// Generate and register a tenant-specific key
String tenantKey = kms.generateAndRegisterKey("acme-key-v2", "acme-corp");Per-Tenant Keys
Each tenant can have its own encryption key. When a tenant-specific key is registered, it takes precedence over the default key for that tenant:
// Register default key (fallback)
kms.registerDefaultKey("default-v1", defaultKeyBase64);
// Register tenant-specific key
kms.registerKey("acme-v1", acmeKeyBase64, "acme-corp");
// Get current key for ACME (returns acme-v1)
Optional<SecretKey> acmeKey = kms.getCurrentKeyForTenant("acme-corp");
// Get current key for Globex (falls back to default-v1)
Optional<SecretKey> globexKey = kms.getCurrentKeyForTenant("globex");Creating Encryption Services
The KeyManagementService acts as a factory for EncryptionService instances:
// Create encryption service with default key
EncryptionService defaultEncryption = kms.createEncryptionService();
// Create encryption service for a tenant
EncryptionService tenantEncryption = kms.createEncryptionServiceForTenant("acme-corp");
// Create encryption service with a specific key
EncryptionService specificEncryption = kms.createEncryptionService("key-v1");Key Rotation
Key rotation replaces the active encryption key with a new one. The old key transitions to ROTATED status and remains available for decryption of existing data.
Rotation Process
// Rotate the default key
kms.rotateKey("key-v2", newKeyBase64);
// key-v1 is now ROTATED (decrypt only)
// key-v2 is now ACTIVE (encrypt + decrypt)
// Rotate a tenant-specific key
kms.rotateKeyForTenant("acme-corp", "acme-v2", newAcmeKeyBase64);
// acme-v1 is now ROTATED
// acme-v2 is now ACTIVE for acme-corpFinding Keys Needing Rotation
The KMS can identify keys that have exceeded a maximum age:
// Find keys older than 90 days
List<KeyInfo> oldKeys = kms.getKeysNeedingRotation(Duration.ofDays(90));
for (KeyInfo key : oldKeys) {
System.out.printf("Key %s (tenant: %s) created at %s needs rotation%n",
key.keyId(), key.tenantId(), key.createdAt());
}Key Deactivation and Deletion
// Deactivate a rotated key (after all data re-encrypted)
kms.deactivateKey("key-v1");
// Delete an inactive key (only works if status is INACTIVE)
boolean deleted = kms.deleteKey("key-v1"); // Returns true if successfulExternal Key Providers
The KeyManagementService supports external key providers through the KeyProvider interface:
public interface KeyProvider {
Optional<SecretKey> getKey(String keyId);
Optional<String> getCurrentKeyId();
}This interface allows integration with cloud KMS services:
| Provider | Service | Use Case |
|---|---|---|
| Azure | Azure Key Vault | Key storage and HSM-backed operations |
| AWS | AWS KMS | Key storage with envelope encryption |
| GCP | Cloud KMS | Key storage with IAM-controlled access |
| HashiCorp | Vault Transit Engine | Self-hosted key management |
When a key is not found in the local registry, the KMS queries the external provider as a fallback:
KeyManagementService kms = new KeyManagementService(azureKeyProvider);
// If key not found locally, queries Azure Key Vault
Optional<SecretKey> key = kms.getKey("remote-key-id");Encryption in Transit
All communication within the MATIH Platform is encrypted with TLS.
Service-to-Service TLS
Internal services communicate over TLS, managed by cert-manager:
| Component | TLS Implementation |
|---|---|
| Ingress | cert-manager with Let's Encrypt (DNS01 challenge via Azure DNS) |
| Service mesh | mTLS between pods (when service mesh is enabled) |
| Database connections | TLS required, certificate verification |
| Kafka | TLS for broker connections, SASL authentication |
| Redis | TLS with client certificate authentication |
cert-manager Configuration
TLS certificates are automatically provisioned and renewed:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: ai-service-tls
namespace: tenant-acme-corp
spec:
secretName: ai-service-tls
issuerRef:
name: letsencrypt-prod-dns01
kind: ClusterIssuer
dnsNames:
- ai-service.acme.matih.ai
- "*.acme.matih.ai"TLS Versions and Cipher Suites
| Setting | Value |
|---|---|
| Minimum TLS version | TLS 1.2 |
| Preferred TLS version | TLS 1.3 |
| Cipher suites (TLS 1.2) | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 |
| Cipher suites (TLS 1.3) | TLS_AES_256_GCM_SHA384, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256 |
Key Generation
The EncryptionService includes a static utility for generating AES-256 keys:
// Generate a new AES-256 key
SecretKey key = EncryptionService.generateKey();
// Generate and get as base64 string (for storage in key vault)
String base64Key = EncryptionService.generateKeyBase64();
// Export current key for backup/rotation
String currentKeyBase64 = encryption.getKeyBase64();Generated keys use Java's KeyGenerator with a SecureRandom source, ensuring cryptographically strong randomness.
Data Classification and Encryption Policy
Different data classifications may require different encryption treatments:
| Classification | At Rest | In Transit | Key Type | Retention |
|---|---|---|---|---|
| Public | Optional | TLS | Default key | No restriction |
| Internal | Required | TLS | Default key | Per policy |
| Confidential | Required | TLS | Tenant-specific key | Per regulation |
| Restricted | Required + AAD | mTLS | Dedicated key + HSM | Per regulation |
Error Handling
Encryption failures are reported through the EncryptionException class:
public static class EncryptionException extends RuntimeException {
public EncryptionException(String message) { ... }
public EncryptionException(String message, Throwable cause) { ... }
}Common failure scenarios:
| Error | Cause | Resolution |
|---|---|---|
| "Encryption failed" | Invalid key or algorithm error | Verify key size and format |
| "Decryption failed" | Wrong key, corrupted data, or AAD mismatch | Check key version and tenant ID |
| "Key generation failed" | JCA provider not available | Verify Java Cryptography Architecture setup |
| "No encryption key available" | No active key in KMS | Register or rotate encryption keys |
Next Steps
Continue to Compliance Patterns to understand how the MATIH Platform's security controls map to regulatory requirements including SOC 2, HIPAA, and GDPR.