Tema
Tokenization Flow Documentation
Overview
Este documento describe el flujo completo de tokenización implementado en Fintrixs para proteger datos sensibles de tarjetas de pago (PANs) según los estándares PCI DSS.
Arquitectura de Tokenización
Diagrama de Componentes
Componentes
| Componente | Ubicación | Responsabilidad |
|---|---|---|
| Payment Service | apps/payment-service/ | Solicita tokenización |
| Tokenization Service | apps/tokenization-service/ | Encripta y genera tokens |
| Vault Database | payments_vault schema | Almacena datos encriptados |
Flujo de Tokenización
1. Tokenize (Almacenar Dato Sensible)
2. Detokenize (Recuperar Dato Sensible)
Formato del Token
Estructura
tok_{dataType}_{uuid}_{timestamp}
Ejemplo: tok_pan_a1b2c3d4e5f6_1704067200| Componente | Descripción | Ejemplo |
|---|---|---|
| Prefijo | Siempre "tok_" | tok_ |
| dataType | Tipo de dato | pan, ssn, account |
| uuid | Identificador único (corto) | a1b2c3d4e5f6 |
| timestamp | Unix timestamp | 1704067200 |
Características
yaml
Propiedades del Token:
- Irreversible: No se puede derivar el PAN del token
- Único: Cada dato genera un token diferente
- No sensible: Puede almacenarse sin encriptación
- Formato fijo: Fácil de validar con regex
- No contiene PII: Safe for loggingEncriptación
Algoritmo
yaml
Encryption:
Algorithm: AES-256-GCM
Key Derivation: HKDF from master key
IV: Random 12 bytes per encryption
Auth Tag: 16 bytesFlujo de Encriptación
typescript
// apps/tokenization-service/src/tokenization.service.ts
private encrypt(plaintext: string): { encrypted: Buffer, iv: Buffer, authTag: Buffer } {
const iv = crypto.randomBytes(12); // 96-bit IV for GCM
const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv);
let encrypted = cipher.update(plaintext, 'utf8');
encrypted = Buffer.concat([encrypted, cipher.final()]);
const authTag = cipher.getAuthTag();
return { encrypted, iv, authTag };
}HMAC para Integridad
typescript
private generateHmac(data: Buffer): Buffer {
return crypto.createHmac('sha256', this.hmacKey)
.update(data)
.digest();
}Esquema de Base de Datos
Vault Schema
sql
-- db/payments_db/vault/migrations/001_create_vault_schema.sql
CREATE SCHEMA IF NOT EXISTS payments_vault;
CREATE EXTENSION IF NOT EXISTS pgcrypto;Tabla tokenized_data
sql
-- db/payments_db/vault/migrations/002_create_tokenized_data.sql
CREATE TABLE payments_vault.tokenized_data (
-- Primary Key
token VARCHAR(100) PRIMARY KEY,
-- Encrypted Data
encrypted_data BYTEA NOT NULL,
hmac_hash BYTEA NOT NULL,
-- Metadata (no sensitive data)
data_type VARCHAR(50) NOT NULL,
format_preserved BOOLEAN DEFAULT FALSE,
-- Multi-tenant
merchant_id UUID NOT NULL,
created_by VARCHAR(255),
-- Lifecycle
created_at TIMESTAMPTZ DEFAULT NOW(),
expires_at TIMESTAMPTZ,
last_accessed_at TIMESTAMPTZ,
access_count INTEGER DEFAULT 0,
-- Versioning
key_version INTEGER DEFAULT 1,
-- Constraints
CONSTRAINT valid_data_type CHECK (data_type IN ('pan', 'ssn', 'account_number', 'routing_number', 'custom'))
);Row Level Security
sql
-- db/payments_db/vault/migrations/003_create_vault_rls.sql
ALTER TABLE payments_vault.tokenized_data ENABLE ROW LEVEL SECURITY;
-- Solo vault_user puede acceder
CREATE POLICY vault_access_policy ON payments_vault.tokenized_data
FOR ALL
TO vault_user
USING (
merchant_id::text = current_setting('app.current_merchant_id', true)
);Seguridad
Principios
yaml
1. Least Privilege:
- Solo tokenization-service accede al vault
- Otros servicios nunca ven datos en claro
2. Defense in Depth:
- TLS en tránsito
- AES-256 en reposo
- RLS por merchant
- Audit logging
3. Data Minimization:
- PANs solo en memoria durante procesamiento
- Nunca en logs
- TTL configurable para expiraciónAutenticación del Servicio
typescript
// Solo servicios internos pueden llamar al tokenization service
@Controller()
export class TokenizationController {
@Post('tokenize')
@UseGuards(InternalServiceGuard) // Verifica X-Service-Key
async tokenize(@Body() dto: TokenizeDto) {
// ...
}
}Headers Requeridos
yaml
X-Service-Key: ${INTERNAL_SERVICE_KEY}
X-Request-ID: uuid
X-Merchant-ID: merchant_uuidAPI Reference
POST /tokenize
Request:
json
{
"data": "4111111111111111",
"dataType": "pan",
"merchantId": "550e8400-e29b-41d4-a716-446655440000",
"metadata": {
"cardBrand": "visa",
"lastFour": "1111"
}
}Response (201):
json
{
"token": "tok_pan_a1b2c3d4e5f6_1704067200",
"dataType": "pan",
"createdAt": "2024-01-01T00:00:00.000Z",
"expiresAt": null
}Errors:
- 400: Invalid input format
- 401: Missing/invalid service key
- 403: Merchant not authorized
- 500: Encryption failure
POST /detokenize
Request:
json
{
"token": "tok_pan_a1b2c3d4e5f6_1704067200",
"merchantId": "550e8400-e29b-41d4-a716-446655440000",
"reason": "payment_processing"
}Response (200):
json
{
"data": "4111111111111111",
"dataType": "pan",
"accessedAt": "2024-01-01T00:00:00.000Z"
}Errors:
- 400: Invalid token format
- 401: Missing/invalid service key
- 403: Token belongs to different merchant
- 404: Token not found or expired
- 500: Decryption failure (integrity check failed)
Logging y Auditoría
Eventos Logueados
yaml
Tokenize:
- token (sin datos sensibles)
- dataType
- merchantId
- timestamp
- success/failure
Detokenize:
- token
- merchantId
- reason
- timestamp
- success/failure
- accessor identityEjemplo de Log
json
{
"timestamp": "2024-01-01T00:00:00.000Z",
"level": "info",
"service": "tokenization-service",
"event": "tokenize",
"requestId": "req-12345",
"merchantId": "550e8400-e29b-41d4-a716-446655440000",
"dataType": "pan",
"token": "tok_pan_***",
"duration": 15,
"success": true
}Audit Trail en Database
sql
-- Automáticamente logueado por audit_trigger_function()
-- Ver: db/payments_db/core/migrations/006_create_audit_triggers.sql
SELECT * FROM payments_core.audit_log
WHERE schema_name = 'payments_vault'
AND table_name = 'tokenized_data'
ORDER BY created_at DESC;Key Management
Key Hierarchy
┌─────────────────────────────────────────────────────────────────────────────┐
│ KEY HIERARCHY │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐
│ Master Key │ ← Stored in HashiCorp Vault / KMS
│ (KEK - Key │ ← Rotated annually
│ Encrypting │
│ Key) │
└────────┬────────┘
│
│ Derives
▼
┌─────────────────┐
│ Data Key │ ← Used for AES-256-GCM
│ (DEK - Data │ ← Rotated monthly
│ Encrypting │ ← Version tracked in DB
│ Key) │
└────────┬────────┘
│
│ Derives
▼
┌─────────────────┐
│ HMAC Key │ ← Used for integrity
│ │ ← Derived from DEK
└─────────────────┘Key Rotation
yaml
Rotation Process:
1. Generate new DEK
2. Update key_version in config
3. New tokens use new key
4. Old tokens still decryptable (multi-version support)
5. Re-encrypt old tokens (batch job, optional)
6. Deprecate old key after migrationKey Versioning
sql
-- Column key_version tracks which key encrypted the data
SELECT token, key_version FROM payments_vault.tokenized_data;
-- Decryption selects correct key based on versionPerformance
Benchmarks
| Operación | Tiempo Promedio | P99 |
|---|---|---|
| Tokenize | 5ms | 15ms |
| Detokenize | 3ms | 10ms |
| Batch Tokenize (100) | 150ms | 300ms |
Optimizaciones
yaml
Caching:
- NO caching of decrypted data
- Token metadata can be cached (TTL: 5min)
Connection Pooling:
- PostgreSQL: 10-50 connections
- Dedicated pool for vault operations
Async Processing:
- Batch tokenization for bulk imports
- Background re-encryption during key rotationDisaster Recovery
Backup Strategy
yaml
Vault Backups:
- Frequency: Every 6 hours
- Encryption: AES-256 with separate backup key
- Storage: Encrypted S3 with versioning
- Retention: 90 days
Key Backups:
- Stored separately from data
- Multi-person access control
- Geographic distributionRecovery Procedure
yaml
1. Restore vault database from backup
2. Retrieve encryption keys from secure storage
3. Verify HMAC integrity of all records
4. Run audit query to identify any gaps
5. Re-sync tokens with payment recordsCompliance Mapping
| Control | PCI DSS Req | Implementation |
|---|---|---|
| Encryption at rest | 3.4.1 | AES-256-GCM |
| Key management | 3.6 | HashiCorp Vault |
| Access logging | 10.2 | Audit triggers |
| Encryption in transit | 4.1 | TLS 1.2+ |
| Access control | 7.1 | RLS + Service auth |
| Data minimization | 3.1 | Token expiration |
Troubleshooting
Common Issues
Token Not Found:
bash
# Check if token exists
SELECT token, expires_at FROM payments_vault.tokenized_data WHERE token = 'tok_...';
# Check RLS context
SET app.current_merchant_id = 'merchant-uuid';Decryption Failed:
bash
# Verify HMAC
# If HMAC mismatch, data may be corrupted
# Check key version
SELECT key_version FROM payments_vault.tokenized_data WHERE token = 'tok_...';Performance Issues:
bash
# Check connection pool
SELECT count(*) FROM pg_stat_activity WHERE datname = 'fintrix_payments';
# Check index usage
EXPLAIN ANALYZE SELECT * FROM payments_vault.tokenized_data WHERE token = 'tok_...';