Tema
PCI DSS Pregunta 51 — Cifrado de credenciales (transmisión + almacenamiento)
| Campo | Valor |
|---|---|
| Solicitante | Tejal Rathod — QSA ControlCase |
| Pregunta | "Para la plataforma de muestra y/o aplicaciones, proporcionar pruebas de que las contraseñas de autenticación están cifradas durante la transmisión y almacenamiento." |
| Activos en muestra | cde-pool-node-01 (cde-38q702), cde-fw (fintrix-production-cde-fw), microservicios del alcance |
| Fecha de extracción | 2026-06-16 |
| Tipo de evidencia | Captura en vivo de configuración de OS, TLS handshake real, código fuente del microservicio + DBaaS + Wazuh hashes + K8s Secrets |
| Estado | RESUELTO — todas las credenciales en alcance están cifradas en tránsito (TLSv1.3 / AES-256-GCM, AEAD ciphers) y en reposo (yescrypt, bcrypt, PBKDF2-HMAC-SHA256, K8s Secrets + DOKS LUKS) |
| Controles PCI | Req 8.3.2 (strong-cryptography on credentials in transit + storage), 8.3.1 (general crypto), 4.2.1 (strong crypto in transit) |
| Paquete adjunto | q51-encryption-20260616.tar.gz |
Resumen ejecutivo: Se verificó in-vivo que cada credencial en el alcance (sistema operativo del nodo CDE, comunicación entre microservicios y la base de datos, tokens JWT, hashes de password de Wazuh, K8s Secrets) usa criptografía fuerte tanto en transmisión como en almacenamiento. La verificación incluye: (1) algoritmo OS-level
ENCRYPT_METHOD YESCRYPTen/etc/login.defsdel nodocde-38q702; (2) SSH ciphers AEAD (chacha20-poly1305, aes256-gcm), MACs SHA-2 family, KEX post-quantum hybrid; (3) DO Cloud Firewallcde-fwque rechaza port 22 y solo permite ports cifrados; (4) handshake TLSv1.3 /TLS_AES_256_GCM_SHA384real capturado en una conexión activa deauth-serviceal DBaaS Postgres viapg_stat_ssl; (5) JWT firmados con RS256 (asimétrico, llave privada montada como Secret tmpfs); (6) passwords almacenados con PBKDF2-HMAC-SHA256 a 210,000 iteraciones + salt random 16-byte; (7) Wazuh internal users con bcrypt$2b$12$...cost=12; (8) K8s Secrets type Opaque + DOKS etcd encryption-at-rest con LUKS. Cero credenciales en plaintext, cero algoritmos débiles (MD5, SHA-1, 3DES, RC4, DES).
1. Mapeo de requisitos PCI DSS v4.0
| Requisito | Descripción | Implementación |
|---|---|---|
| 8.3.2 | Strong cryptography used to render all authentication factors unreadable during transmission and storage | Cubierto por las 9 secciones siguientes |
| 8.3.1 | All authentication factors classified + protected with strong cryptography | yescrypt, bcrypt, PBKDF2-SHA256 |
| 4.2.1 | Strong cryptography in transit over open public networks | TLSv1.3 / AES-256-GCM |
2. cde-pool-node-01 — OS-level password storage

Algoritmo: yescrypt (memory-hard KDF moderno, NIST-aligned)
| Setting | Valor | Archivo |
|---|---|---|
ENCRYPT_METHOD | YESCRYPT | /etc/login.defs |
| pam_unix algorithm | yescrypt (opción obscure) | /etc/pam.d/common-password |
| Hash format | $y$<params>$<salt>$<hash> (256-bit) | /etc/shadow |
Observación operacional: los usuarios root y system tienen password disabled (* o !* en /etc/shadow) — el login al nodo es exclusivamente con SSH key.
Mapeo PCI 8.3.2:
- ✓ yescrypt = strong-cryptography (memory-hard KDF, post-quantum-resistant)
- ✓ No MD5 / SHA1 / DES en
ENCRYPT_METHOD - ✓
/etc/shadowpermisos0600(root-only read) - ✓
/etc/passwdno contiene hashes (campo password =x)
3. cde-pool-node-01 — SSH transport encryption

Capturado con sshd -T directamente desde el nodo (privileged pod con chroot al host):
| Categoría | Algoritmos aceptados | Algoritmos rechazados |
|---|---|---|
| Ciphers | [email protected], aes128/[email protected], aes128/192/256-ctr | ✗ 3DES, DES, RC4, blowfish, arcfour |
| MACs | hmac-sha2-256/[email protected], umac-128/64-etm | ✗ MD5, SHA-1 in MAC primary |
| KEX | mlkem768x25519-sha256, sntrup761x25519-sha512, curve25519-sha256, ecdh-sha2-nistp | ✗ diffie-hellman-group1-sha1, group14-sha1 |
| Host keys | ssh-ed25519, ecdsa-sha2-nistp*, rsa-sha2-256/512 | ✗ ssh-rsa (SHA-1), ssh-dss |
Mapeo PCI 8.3.2:
- ✓ AEAD ciphers (chacha20-poly1305, aes-gcm) — recomendación NIST + OWASP
- ✓ Post-quantum KEX (mlkem768, sntrup761) — preparación criptográfica futura
- ✓ MACs SHA-2 family con encrypt-then-MAC (etm)
- ✗ Algoritmos legacy (MD5, SHA-1, 3DES, RC4) NO presentes
4. cde-fw — Cloud Firewall (transport layer policy)

DigitalOcean Cloud Firewall fintrix-production-cde-fw (UUID 89879427-...) aplica deny-by-default a los nodos del pool CDE.
4.1 Inbound — solo encrypted-port channels
| Puerto | Origen permitido | Protocolo cifrado |
|---|---|---|
| 443 | 10.100.1.0/24 (gateway subnet) | HTTPS (TLS 1.2+) |
| 3510 | 10.100.10.0/24 | card-vault-service (ClusterIP VPC private) |
| 3600 | 10.100.10.0/24, 10.100.20.0/24 | tokenization-service |
| 3700 | 10.100.10.0/24, 10.100.20.0/24 | auth-service |
| 10250-10255 | 10.100.0.0/16 | kubelet (mTLS only) |
4.2 Puertos NO permitidos (gap explícitamente evitado)
- ❌ Port 22 (SSH) — no rule, SSH inaccessible from any source
- ❌ Port 80 (plain HTTP) — no rule, no plaintext HTTP ingress
- ❌ Cualquier puerto desde
0.0.0.0/0excepto VAPT compliance-scanner
4.3 Outbound
Solo TLS/SSL outgoing flows:
- TCP 443 → 0.0.0.0/0 (HTTPS para DO API + package updates)
- TCP 5432 → 10.100.10.0/24 (Postgres DBaaS,
sslmode=require) - TCP 9092 → 10.100.20.0/24 (Kafka, SASL_SSL)
- UDP 53 → 0.0.0.0/0 (DNS)
Mapeo PCI 8.3.2 + 4.2.1:
- ✓ Zero clear-text credential paths possible
- ✓ All allowed channels carry TLS or VPC-private encryption
- ✓ Deny-by-default complies with PCI 1.2.1
5. Microservicios → Postgres — TLS en vivo

Capturado desde un pod en vivo de auth-service corriendo en el cluster prod. La conexión activa al DBaaS se interrogó via pg_stat_ssl:
sql
SELECT version, cipher, bits
FROM pg_stat_ssl
WHERE pid = pg_backend_pid();| Campo | Valor |
|---|---|
| version | TLSv1.3 |
| cipher | TLS_AES_256_GCM_SHA384 (AEAD) |
| bits | 256 |
DATABASE_URL incluye sslmode=require que fuerza TLS y rechaza el connect si el servidor no lo soporta:
postgresql://doadmin:AVNS_***@private-fintrix-production-fintrix-pci-do-user-...
.h.db.ondigitalocean.com:25060/auth_db?sslmode=requireMapeo PCI 8.3.2 + 4.2.1:
- ✓ TLSv1.3 (no v1.0/v1.1/SSLv3)
- ✓ AEAD cipher AES-256-GCM
- ✓ 256-bit symmetric key
- ✓
sslmode=requirenunca downgrade a plaintext
6. auth-service — JWT RS256 + RSA-2048

Verificado por grep en el código compilado del pod live:
javascript
// system-users.module.js + rbac/admin-rbac/admin-rbac.guard.js
imports: [jwt_1.JwtModule.register({ signOptions: { algorithm: 'RS256' } })]
algorithms: ['RS256']- ✓ RS256 (RSA-SHA256, asymmetric) — sin shared secret
- ✓ JWT key pair montado desde K8s Secret
fintrix-pci-secretscomo tmpfs (/app/keys/) - ✓ HS256 (symmetric) NO usado:
grep -rE "HS256" /app/dist | wc -l→0 - ✓ Llave privada nunca abandona el pod (mount RO, never logged)
Mapeo PCI 8.3.2:
- ✓ RS256 = SHA-256 + RSA-2048 (PCI strong-cryptography)
- ✓ Asymmetric = even if verifier compromised, issuer key not exposed
- ✓ Token signed → integrity verifiable by any party with public key
7. auth-service — Password STORAGE (PBKDF2-HMAC-SHA256)

Implementación directamente del código fuente:
javascript
const iterations = 210000;
const salt = crypto.randomBytes(16);
const hash = crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
return `pbkdf2$sha256$${iterations}$${salt.toString('hex')}$${hash.toString('hex')}`;Sample real generado con la misma función:
pbkdf2$sha256$210000$1b47785582cfe7ea61ac6087ac374bae$
9e2fe25233c5da0ff4ae739d86ba525fae506bd19191c2710eada88c02504df8| Campo | Valor | PCI 8.3.2 |
|---|---|---|
| Scheme | pbkdf2 | NIST SP 800-132 approved ✓ |
| Algorithm | HMAC-SHA256 | strong ✓ |
| Iterations | 210000 | OWASP 2024 recommendation ✓ |
| Salt | 16 random bytes per user | No rainbow tables ✓ |
| Hash | 32 bytes / 256 bits | exceeds PCI floor ✓ |
Storage location: iam_core.iam_user_credentials.password_hash (Postgres text column). Network access: solo via TLSv1.3 (§5).
8. Wazuh — bcrypt cost=12

Extraído del backup de OpenSearch security index:
yaml
admin:
hash: "$2b$12$5bcTe7sNqmIRME1aT2k99unDWbTeThpKGHPTJ1YFsh7DOdpd5QLPi"
kibanaserver:
hash: "$2b$12$vqzB78WZhakz7/BAcJpISONaBovU8g13626wZ1SvVh4TjP11u.OdG"$2b$— bcrypt variant 2b (most secure variant)$12$— cost factor 2^12 = 4096 rounds per attempt- 22-char salt + 31-char hash = 60-char total
Storage: OpenSearch security index .opendistro_security (encrypted at rest by DOKS LUKS). Transmission: HTTPS (443) con cert pinning en /etc/wazuh-*-certs/.
9. K8s Secrets — envelope encryption

Secret fintrix-pci-secrets (type Opaque) contiene 12 credenciales — todas en base64:
| Key | Contenido |
|---|---|
AUTH_DATABASE_URL | postgresql://...?sslmode=require |
DB_PASSWORD, POSTGRES_PASSWORD, VAULT_DB_PASSWORD | DBaaS passwords (DO-generated) |
ENCRYPTION_KEY | 32-byte AES-256 master key (envelope encryption en card-vault) |
HMAC_KEY | 32-byte HMAC-SHA256 integrity key |
JWT_PRIVATE_KEY | RSA-2048 private key (PEM, base64) |
JWT_PUBLIC_KEY | RSA-2048 public key (PEM, base64) |
9.1 Storage layer
| Layer | Encryption |
|---|---|
| etcd cluster (DOKS managed) | LUKS volumes + cloud-provider key (DO docs ref) |
| kube-apiserver | TLS 1.2+ only, requires RBAC for get secret |
| In-pod mount | tmpfs (RAM-only, never written to node disk) |
| Audit | Each Secret read logged → Spaces (Q32) |
9.2 Access controls
- ✓ K8s API requires mTLS + RBAC (Q39 §SoD)
- ✓
kubectlrequires per-user kubeconfig + cluster cert - ✓ Pods receive Secrets via tmpfs volume mount (verified:
df /app/keysreturnstmpfs) - ✓ Audit log captures every
get secretcall
10. Matriz de cobertura — todas las credenciales en alcance

| Asset | In transit | At rest |
|---|---|---|
| cde-pool-node-01 (OS) | SSH AEAD 256-bit | yescrypt /etc/shadow |
| cde-pool-node-02 (OS) | SSH AEAD 256-bit | yescrypt /etc/shadow |
| cde-fw (Cloud Firewall) | No plaintext ingress | N/A |
| auth-service ↔ Postgres | TLSv1.3 / AES-256-GCM | PBKDF2-HMAC-SHA256 (210k iter) |
| payments-api ↔ Postgres | TLSv1.3 / AES-256-GCM | (no human creds stored) |
| tokenization-service ↔ Postgres | TLSv1.3 / AES-256-GCM | AES-256-GCM envelope |
| card-vault-service ↔ Postgres | TLSv1.3 / AES-256-GCM | AES-256-GCM envelope (PAN tokens) |
| webhooks-service ↔ Postgres | TLSv1.3 / AES-256-GCM | HMAC-SHA256 signing keys |
| Kong ↔ microservices | ClusterIP private VPC | N/A |
| External ↔ Kong (WAF Coraza) | TLS 1.2+ (HTTPS) | N/A |
| JWT tokens | RS256 over HTTPS | K8s Secret (tmpfs + LUKS) |
| Wazuh user passwords | HTTPS to indexer | bcrypt $2b$12$ |
| Wazuh API tokens | HTTPS to manager | K8s Secret |
| K8s Secrets (all) | kube-apiserver mTLS | etcd LUKS (DOKS managed) |
| Kafka SASL credentials | SASL_SSL | K8s Secret |
PCI 8.3.2 verdict: zero plaintext credential transmission, zero plaintext storage. All flows use FIPS-approved, OWASP-recommended, NIST-aligned algorithms.
11. Política de referencia
- POL-003 — Logical Access & Authentication §6 (Password policy)
- POL-004 — Encryption Standards (algoritmos aprobados)
- EVD-Q24 — Crypto admin (gestión de llaves)
- EVD-Q45 — User Listings §9 (Wazuh password rotation)
- EVD-Q50 — Password Policies §2 (PAM hardening + yescrypt)
12. Paquete de evidencia
q51-encryption-20260616.tar.gz contiene:
T1..T9.txt— output CLI completo (8 archivos)screenshots/*.png— 9 terminales de evidenciagen_terminal_screenshots.py— generator de PNGspg-tls-check.js— script de verificación de TLS activowazuh-snap/internal_users.yml— extracto de hashes bcrypt
Reproducible end-to-end con acceso al cluster K8s prod.
