MATIH Platform is in active MVP development. Documentation reflects current implementation status.
3. Security & Multi-Tenancy
Data Encryption

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

DimensionImplementation
AlgorithmAES-256-GCM (Galois/Counter Mode)
Key size256 bits
IV length12 bytes (96 bits), randomly generated per encryption
Authentication tag128 bits
EncodingBase64 for string transport
Key managementKeyManagementService with multi-tenant support
Transit encryptionTLS 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 EncryptionException

This 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)
StatusCan EncryptCan DecryptDescription
ACTIVEYesYesCurrent key for new encryption operations
ROTATEDNoYesPrevious key, kept for decrypting old data
INACTIVENoNoDecommissioned 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-corp

Finding 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 successful

External 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:

ProviderServiceUse Case
AzureAzure Key VaultKey storage and HSM-backed operations
AWSAWS KMSKey storage with envelope encryption
GCPCloud KMSKey storage with IAM-controlled access
HashiCorpVault Transit EngineSelf-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:

ComponentTLS Implementation
Ingresscert-manager with Let's Encrypt (DNS01 challenge via Azure DNS)
Service meshmTLS between pods (when service mesh is enabled)
Database connectionsTLS required, certificate verification
KafkaTLS for broker connections, SASL authentication
RedisTLS 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

SettingValue
Minimum TLS versionTLS 1.2
Preferred TLS versionTLS 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:

ClassificationAt RestIn TransitKey TypeRetention
PublicOptionalTLSDefault keyNo restriction
InternalRequiredTLSDefault keyPer policy
ConfidentialRequiredTLSTenant-specific keyPer regulation
RestrictedRequired + AADmTLSDedicated key + HSMPer 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:

ErrorCauseResolution
"Encryption failed"Invalid key or algorithm errorVerify key size and format
"Decryption failed"Wrong key, corrupted data, or AAD mismatchCheck key version and tenant ID
"Key generation failed"JCA provider not availableVerify Java Cryptography Architecture setup
"No encryption key available"No active key in KMSRegister 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.