Skip to content

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

ComponenteUbicaciónResponsabilidad
Payment Serviceapps/payment-service/Solicita tokenización
Tokenization Serviceapps/tokenization-service/Encripta y genera tokens
Vault Databasepayments_vault schemaAlmacena 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
ComponenteDescripciónEjemplo
PrefijoSiempre "tok_"tok_
dataTypeTipo de datopan, ssn, account
uuidIdentificador único (corto)a1b2c3d4e5f6
timestampUnix timestamp1704067200

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 logging

Encriptación

Algoritmo

yaml
Encryption:
  Algorithm: AES-256-GCM
  Key Derivation: HKDF from master key
  IV: Random 12 bytes per encryption
  Auth Tag: 16 bytes

Flujo 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ón

Autenticació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_uuid

API 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 identity

Ejemplo 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 migration

Key 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 version

Performance

Benchmarks

OperaciónTiempo PromedioP99
Tokenize5ms15ms
Detokenize3ms10ms
Batch Tokenize (100)150ms300ms

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 rotation

Disaster 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 distribution

Recovery 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 records

Compliance Mapping

ControlPCI DSS ReqImplementation
Encryption at rest3.4.1AES-256-GCM
Key management3.6HashiCorp Vault
Access logging10.2Audit triggers
Encryption in transit4.1TLS 1.2+
Access control7.1RLS + Service auth
Data minimization3.1Token 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_...';

Documentación Confidencial — Solo para uso interno y auditoría PCI DSS