05 - Seguridad del Cliente de MedTime¶
Identificador: TECH-SEC-CLIENT-001 Version: 1.0.0 Fecha: 2025-12-07 Ultima Revision: Creacion inicial - IT-02 Autor: SecurityDrone (MTS-DRN-SEC-001) + SpecQueen Refs Funcional: MTS-AUTH-001, MTS-PRI-001 Refs Investigacion: INV-001 (Cifrado E2E), INV-008 (Cifrado de Perfil) Estado: Borrador
- 1. Principios de Seguridad del Cliente
- 1.1. Zero-Knowledge Architecture
- 1.2. Defense in Depth
- 1.3. Principio de Minimo Privilegio
- 1.4. Security by Design
- 2. Gestion de Claves Criptograficas
- 2.1. Jerarquia de Claves
- 2.2. Master Key
- 2.3. Data Encryption Key (DEK)
- 2.4. Recovery Key
- 2.5. Key Wrapping
- 2.6. Rotacion de Claves
- 3. Cifrado End-to-End (E2E)
- 3.1. Algoritmos Criptograficos
- 3.2. Flujo de Cifrado
- 3.3. Flujo de Descifrado
- 3.4. Estructura de Datos Cifrados
- 3.5. Manejo de Nonces
- 3.6. Datos Adicionales Autenticados (AAD)
- 4. Blind Index para Busqueda Segura
- 4.1. Proposito del Blind Index
- 4.2. Implementacion Tecnica
- 4.3. Casos de Uso
- 4.4. Limitaciones y Mitigaciones
- 5. Almacenamiento Seguro por Plataforma
- 5.1. iOS - Keychain Services
- 5.2. Android - Keystore System
- 5.3. Comparativa de Seguridad
- 5.4. Fallback y Compatibilidad
- 6. Autenticacion Local
- 6.1. Biometria
- 6.2. PIN/Passcode
- 6.3. Flujo de Autenticacion Local
- 6.4. Proteccion contra Ataques
- 6.5. Session Management Local
- 7. Proteccion de Datos en Reposo
- 7.1. Base de Datos Local
- 7.2. Archivos y Cache
- 7.3. Clasificacion de Datos
- 7.4. Borrado Seguro
- 8. Proteccion de Datos en Transito
- 8.1. TLS 1.3
- 8.2. Certificate Pinning
- 8.3. Doble Cifrado (TLS + E2E)
- 8.4. Proteccion contra MITM
- 9. Proteccion contra Ataques Comunes
- 9.1. OWASP Mobile Top 10
- 9.2. Protecciones Implementadas
- 9.3. Deteccion de Jailbreak/Root
- 9.4. Anti-Tampering
- 10. Logging y Auditoria de Seguridad
- 10.1. Eventos de Seguridad
- 10.2. Sanitizacion de Logs
- 10.3. Almacenamiento de Audit Trail
- 11. Recuperacion de Desastres
- 11.1. Escenarios de Perdida de Acceso
- 11.2. Procedimientos de Recuperacion
- 11.3. Backup Cifrado
- 12. Cumplimiento Regulatorio
- 12.1. HIPAA Security Rule
- 12.2. LGPD (Brasil)
- 12.3. FDA 21 CFR Part 11
- 13. Testing de Seguridad
- 13.1. Pruebas Requeridas
- 13.2. Checklist de Seguridad por Release
- 14. Referencias
1. Principios de Seguridad del Cliente¶
1.1. Zero-Knowledge Architecture¶
PRINCIPIO ZERO-KNOWLEDGE:
+------------------------------------------------------------------+
| El servidor MedTime NUNCA tiene acceso al contenido |
| de los datos de salud (PHI) del paciente. |
| |
| - TODO el cifrado/descifrado ocurre en el CLIENTE |
| - El servidor almacena BLOBS opacos (indescifrable) |
| - Solo el paciente tiene la clave de descifrado |
| - En caso de brecha del servidor: datos inutiles |
+------------------------------------------------------------------+
1.1.1. Implicaciones Arquitectonicas¶
| Responsabilidad | Ubicacion | Servidor Puede Ver |
|---|---|---|
| Generacion de claves | Cliente | NO |
| Cifrado de datos PHI | Cliente | NO (solo blob) |
| Descifrado de datos | Cliente | NO |
| Almacenamiento de master_key | Cliente (Keychain/Keystore) | NO |
| Calculo de blind indexes | Cliente | Solo el hash |
| Validacion de tokens | Servidor | Metadata de sesion |
1.1.2. Diagrama de Flujo Zero-Knowledge¶
sequenceDiagram
participant U as Usuario
participant C as Cliente (App)
participant K as Keychain/Keystore
participant S as Servidor
Note over C,S: El servidor NUNCA ve datos PHI en claro
U->>C: Ingresa datos medicos
C->>K: Solicita master_key
K->>K: Verifica biometria/PIN
K->>C: master_key
C->>C: Cifrar(datos, master_key) = blob E2E
C->>S: POST /sync {blob_cifrado}
S->>S: Almacena blob opaco
Note over S: Servidor ve: 0x7f8a9b2c...<br/>NO puede descifrar
1.2. Defense in Depth¶
MedTime implementa multiples capas de seguridad para que la falla de una capa no comprometa todo el sistema.
flowchart TB
subgraph L5["CAPA 5: SEGURIDAD DE APLICACION"]
L5A["Autenticacion biometrica/PIN"]
L5B["Session timeout configurable"]
L5C["Screen protection (blur)"]
L5D["Jailbreak/Root detection"]
end
subgraph L4["CAPA 4: CIFRADO E2E (APLICACION)"]
L4A["AES-256-GCM por usuario"]
L4B["Clave derivada de master_key"]
L4C["Servidor NO puede descifrar"]
end
subgraph L3["CAPA 3: ALMACENAMIENTO SEGURO"]
L3A["iOS Keychain (Secure Enclave)"]
L3B["Android Keystore (TEE/StrongBox)"]
L3C["Datos sensibles SOLO aqui"]
end
subgraph L2["CAPA 2: CIFRADO DE DISCO (OS)"]
L2A["iOS Data Protection"]
L2B["Android Full Disk Encryption"]
L2C["Transparente para la app"]
end
subgraph L1["CAPA 1: SEGURIDAD DE TRANSPORTE"]
L1A["TLS 1.3 obligatorio"]
L1B["Certificate Pinning"]
L1C["HSTS"]
end
L5 --> L4 --> L3 --> L2 --> L1
style L5 fill:#e8f5e9,stroke:#2e7d32
style L4 fill:#e3f2fd,stroke:#1565c0
style L3 fill:#fff3e0,stroke:#e65100
style L2 fill:#fce4ec,stroke:#c62828
style L1 fill:#f3e5f5,stroke:#7b1fa2
1.3. Principio de Minimo Privilegio¶
MINIMO PRIVILEGIO EN CLIENTE:
+------------------------------------------------------------------+
1. PERMISOS DEL SISTEMA OPERATIVO
- SOLO solicitar permisos necesarios
- Notificaciones: Requerido para alertas
- Camara: Solo si usa OCR (Pro/Perfect)
- Biometria: Para autenticacion local
- NO solicitar: Ubicacion, Contactos, Microfono
2. ACCESO A DATOS
- Componentes de UI solo ven datos necesarios
- Use Cases reciben entidades, no DTOs completos
- Logs NUNCA contienen PHI
3. ACCESO A CLAVES
- master_key solo accesible por CryptoService
- derived_keys tienen scope especifico
- Claves se liberan de memoria despues de uso
+------------------------------------------------------------------+
1.4. Security by Design¶
| Principio | Implementacion en MedTime |
|---|---|
| Fail Secure | Si falla autenticacion, acceso denegado |
| Secure Defaults | Cifrado E2E por defecto (no opt-in) |
| Complete Mediation | Cada request verifica autorizacion |
| Open Design | Algoritmos estandar (AES, Argon2id) |
| Separation of Privilege | Biometria + PIN para operaciones criticas |
| Least Common Mechanism | Componentes aislados, interfaces minimas |
| Psychological Acceptability | UX amigable sin sacrificar seguridad |
2. Gestion de Claves Criptograficas¶
2.1. Jerarquia de Claves¶
JERARQUIA DE CLAVES DE MEDTIME:
+------------------------------------------------------------------+
+------------------+
| Recovery Key | <-- Usuario guarda offline
| (24 palabras | (papel/caja fuerte)
| BIP39) |
+--------+---------+
|
| Puede regenerar
v
+----------------+ +------------------+ +----------------+
| PIN/Password |->| Master Key |<-| Device Key |
| (Usuario) | | (256 bits) | | (Secure Enclave)|
+----------------+ +--------+---------+ +----------------+
|
+----------------+----------------+
| | |
v v v
+-------+------+ +-------+------+ +-------+------+
| Data | | Sync | | Share |
| Encryption | | Key | | Key |
| Key (DEK) | | | | |
+-------+------+ +-------+------+ +-------+------+
| | |
v v v
[Cifrado de [Cifrado para [Cifrado para
datos PHI] sincronizar] compartir]
+------------------------------------------------------------------+
2.2. Master Key¶
La Master Key es la clave raiz de la que se derivan todas las demas claves de cifrado del usuario.
2.2.1. Especificacion Tecnica¶
| Parametro | Valor | Justificacion |
|---|---|---|
| Tamano | 256 bits | Seguridad maxima AES |
| Generacion | CSPRNG del sistema | iOS: SecRandomCopyBytes, Android: SecureRandom |
| Derivacion | Argon2id | Resistente a GPU y side-channel |
| Almacenamiento | Keychain/Keystore | Hardware-backed cuando disponible |
2.2.2. Parametros de Argon2id (NIST SP 800-63B compliant)¶
ARGON2ID PARAMETERS:
+------------------------------------------------------------------+
| Parametro | Valor | Notas |
|----------------|-----------------|--------------------------------|
| Memory | 64 MiB | Compromiso movil/seguridad |
| Iterations | 3 | Factor tiempo |
| Parallelism | 4 | Threads concurrentes |
| Salt | 128 bits | Aleatorio por usuario |
| Output Length | 256 bits | Para AES-256 |
+------------------------------------------------------------------+
NOTA iOS: Valores > 64 MiB causan warnings en iOS AutoFill
(Referencia: Bitwarden KDF documentation)
2.2.3. Flujo de Generacion de Master Key¶
flowchart TD
A[Usuario crea cuenta] --> B[Generar salt aleatorio]
B --> C[Usuario ingresa password]
C --> D[Argon2id: password + salt]
D --> E[master_key = 256 bits]
E --> F{Secure Enclave disponible?}
F -->|Si| G[Proteger con Device Key]
F -->|No| H[Proteger con PIN hash]
G --> I[Almacenar en Keychain]
H --> I
I --> J[Master Key lista]
2.3. Data Encryption Key (DEK)¶
La DEK se deriva de la Master Key y se usa para cifrar datos PHI especificos.
2.3.1. Derivacion de DEK¶
DEK DERIVATION (HKDF-SHA256):
+------------------------------------------------------------------+
Input Key Material (IKM): master_key
Salt: Constante por dominio (ej: "medtime-phi-v1")
Info: Identificador de dominio + version
Output Length: 256 bits
Dominios de DEK:
- "medtime-phi-medications-v1" -> DEK para medicamentos
- "medtime-phi-doses-v1" -> DEK para dosis tomadas
- "medtime-phi-profile-v1" -> DEK para perfil medico
- "medtime-phi-prescriptions-v1" -> DEK para recetas
- "medtime-phi-notes-v1" -> DEK para notas personales
+------------------------------------------------------------------+
2.3.2. Pseudocodigo de Derivacion¶
# Pseudocodigo - NO usar en produccion sin revision de seguridad
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
def derive_dek(master_key: bytes, domain: str, version: int = 1) -> bytes:
"""
Deriva una Data Encryption Key especifica para un dominio.
Cada dominio tiene su propia clave para aislar el impacto
si una clave es comprometida.
"""
info = f"medtime-phi-{domain}-v{version}".encode('utf-8')
salt = b"medtime-dek-salt-v1" # Constante por version
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32, # 256 bits
salt=salt,
info=info
)
return hkdf.derive(master_key)
2.4. Recovery Key¶
La Recovery Key permite recuperar acceso a los datos si el usuario pierde su dispositivo o contrasena.
2.4.1. Especificacion BIP39¶
RECOVERY KEY SPECIFICATION:
+------------------------------------------------------------------+
Formato: BIP39 Mnemonic (24 palabras)
Entropia: 256 bits
Wordlist: English (2048 palabras)
Checksum: 8 bits (SHA-256)
Total: 264 bits encoded -> 24 palabras
Ejemplo (NO USAR):
"abandon ability able about above absent absorb abstract absurd abuse
access accident account accuse achieve acid acoustic acquire across
act action actor actress actual"
+------------------------------------------------------------------+
2.4.2. Generacion de Recovery Key¶
flowchart TD
A[Usuario activa E2E] --> B[Generar 256 bits entropia]
B --> C[Calcular checksum SHA-256]
C --> D[Concatenar entropia + checksum]
D --> E[Dividir en grupos de 11 bits]
E --> F[Mapear a palabras BIP39]
F --> G[Mostrar 24 palabras al usuario]
G --> H[OBLIGAR confirmacion]
H --> I{Usuario confirmo?}
I -->|No| J[Bloquear hasta confirmar]
I -->|Si| K[Almacenar hash de Recovery Key]
K --> L[Recovery Key activa]
2.4.3. Verificacion de Backup¶
VERIFICACION OBLIGATORIA:
+------------------------------------------------------------------+
El usuario DEBE confirmar que guardo la Recovery Key:
1. Mostrar las 24 palabras (una pantalla, sin scroll)
2. Requerir que el usuario confirme haberlas escrito
3. Pedir 3 palabras aleatorias para verificar:
- "Ingresa la palabra #3: ____"
- "Ingresa la palabra #11: ____"
- "Ingresa la palabra #18: ____"
4. Si falla, repetir desde paso 1
5. Mostrar advertencia legal:
"ADVERTENCIA: Si pierdes tu Recovery Key Y tu password,
tus datos seran IRRECUPERABLES. MedTime no puede ayudarte."
6. Requerir aceptacion explicita
+------------------------------------------------------------------+
2.5. Key Wrapping¶
Las claves se protegen ("wrapping") antes de almacenarse.
KEY WRAPPING STRATEGY:
+------------------------------------------------------------------+
NIVEL 1: Master Key Wrapping
+----------------------------------+
| Wrapped with: |
| - iOS: Secure Enclave key |
| - Android: Hardware-backed key |
| |
| Si no hay hardware: |
| - Derivar wrapping key de PIN |
| - AES-256-KWP (RFC 5649) |
+----------------------------------+
NIVEL 2: DEK Wrapping
+----------------------------------+
| DEKs derivadas en runtime |
| (No se almacenan directamente) |
| Master Key -> HKDF -> DEK |
+----------------------------------+
NIVEL 3: Sync Key Wrapping
+----------------------------------+
| sync_key cifrada con master_key |
| Se sincroniza al servidor |
| Permite multi-dispositivo |
+----------------------------------+
+------------------------------------------------------------------+
2.5.1. Diagrama de Key Wrapping¶
flowchart LR
subgraph SECURE_HARDWARE["Hardware Seguro"]
SE["Secure Enclave Key<br/>(iOS)"]
SB["StrongBox Key<br/>(Android)"]
end
subgraph KEYCHAIN["Keychain/Keystore"]
MK["Master Key<br/>(wrapped)"]
RKH["Recovery Key Hash"]
SK["Sync Key<br/>(wrapped)"]
end
subgraph RUNTIME["Runtime (Memoria)"]
MK_PLAIN["Master Key<br/>(unwrapped)"]
DEK["DEK<br/>(derivada)"]
end
SE --> MK
SB --> MK
MK --> MK_PLAIN
MK_PLAIN --> DEK
2.6. Rotacion de Claves¶
POLITICA DE ROTACION DE CLAVES:
+------------------------------------------------------------------+
| Clave | Frecuencia | Trigger |
|-----------------|-------------------|----------------------------|
| Master Key | Solo si comprometida | Usuario solicita reset |
| DEKs | Con Master Key | Automatico |
| Sync Key | Anual | Automatico (en sync) |
| Session Keys | Por sesion | Login/Logout |
PROCESO DE ROTACION DE MASTER KEY:
1. Generar nueva master_key
2. Descifrar todos los datos con old_master_key
3. Re-cifrar con new_master_key
4. Actualizar blobs en servidor (atomico)
5. Invalidar old_master_key
6. Generar nueva Recovery Key
7. OBLIGAR usuario a guardar nueva Recovery Key
NOTA: Rotacion es operacion critica. Implementar con transacciones atomicas.
+------------------------------------------------------------------+
3. Cifrado End-to-End (E2E)¶
3.1. Algoritmos Criptograficos¶
ESPECIFICACION CRIPTOGRAFICA MEDTIME:
+------------------------------------------------------------------+
CIFRADO SIMETRICO:
+----------------------------------+
| Algoritmo: AES-256-GCM |
| Modo: Galois/Counter |
| Tamano clave: 256 bits |
| Tamano nonce: 96 bits (12 bytes)|
| Tamano tag: 128 bits |
| Padding: No requerido (GCM)|
+----------------------------------+
KEY DERIVATION:
+----------------------------------+
| Algoritmo: Argon2id |
| Alternativa: PBKDF2-SHA256 |
| (si Argon2 no disponible) |
| Iteraciones PBKDF2: 600,000+ |
+----------------------------------+
HASHING:
+----------------------------------+
| Integridad: SHA-256 |
| Blind Index: HMAC-SHA256 |
| Password: Argon2id |
+----------------------------------+
CUMPLIMIENTO:
- NIST SP 800-132 (Key Derivation)
- NIST SP 800-38D (GCM Mode)
- FIPS 140-2 (AES)
- HIPAA Safe Harbor
+------------------------------------------------------------------+
3.1.1. Comparativa de Algoritmos¶
| Algoritmo | Uso en MedTime | Alternativa | Razon |
|---|---|---|---|
| AES-256-GCM | Cifrado datos | ChaCha20-Poly1305 | Hardware acceleration |
| Argon2id | KDF | PBKDF2-SHA256 | Resistencia GPU, side-channel |
| HMAC-SHA256 | Blind Index | - | Estandar, eficiente |
| SHA-256 | Integridad | - | NIST approved |
3.2. Flujo de Cifrado¶
FLUJO DETALLADO DE CIFRADO:
+------------------------------------------------------------------+
ENTRADA:
- plaintext: Datos en claro (ej: JSON de medicamento)
- entity_id: UUID de la entidad
- entity_type: Tipo de entidad ("medication")
PROCESO:
1. SERIALIZAR
json_bytes = utf8_encode(JSON.stringify(plaintext))
1.5. APLICAR PADDING (Prevencion de Metadata Attack)
// Padding a multiplo de 1KB para ocultar tamano real
padded_bytes = pad_to_block(json_bytes, block_size=1024)
// Esto previene que atacante infiera cantidad de items por tamano de blob
2. OBTENER CLAVE
master_key = Keychain.get("master_key")
dek = HKDF(master_key, salt="medtime-dek-salt", info=entity_type)
3. GENERAR NONCE
nonce = SecureRandom.generate(96 bits)
// CRITICO: Nonce DEBE ser unico por operacion
4. PREPARAR AAD (Additional Authenticated Data)
aad = utf8_encode(entity_id + "|" + entity_type + "|" + version)
5. CIFRAR
(ciphertext, tag) = AES-256-GCM.encrypt(
key: dek,
nonce: nonce,
plaintext: padded_bytes, // Usar bytes con padding
aad: aad
)
6. EMPAQUETAR
encrypted_blob = {
"version": "1.0",
"nonce": base64(nonce),
"ciphertext": base64(ciphertext),
"tag": base64(tag),
"aad_hash": sha256(aad) // Para verificacion
}
7. CALCULAR HASH DE INTEGRIDAD
blob_hash = SHA-256(serialize(encrypted_blob))
SALIDA:
- encrypted_blob: Blob cifrado (opaco para servidor)
- blob_hash: Hash para verificar integridad
+------------------------------------------------------------------+
3.2.1. Diagrama de Flujo de Cifrado¶
sequenceDiagram
participant D as Datos PHI
participant C as CryptoService
participant K as Keychain
participant S as SyncService
D->>C: Cifrar(medication_data)
C->>K: Obtener master_key
K->>K: Verificar autenticacion
K->>C: master_key
C->>C: dek = HKDF(master_key, domain)
C->>C: nonce = SecureRandom(96 bits)
C->>C: aad = entity_id|type|version
C->>C: (ciphertext, tag) = AES-GCM(dek, nonce, data, aad)
C->>C: blob = pack(nonce, ciphertext, tag)
C->>C: hash = SHA-256(blob)
C->>S: {encrypted_blob, blob_hash}
Note over S: Servidor almacena<br/>bytes opacos
3.3. Flujo de Descifrado¶
FLUJO DETALLADO DE DESCIFRADO:
+------------------------------------------------------------------+
ENTRADA:
- encrypted_blob: Blob cifrado del servidor
- blob_hash: Hash para verificar integridad
- entity_id: UUID de la entidad (para reconstruir AAD)
- entity_type: Tipo de entidad
PROCESO:
1. VERIFICAR INTEGRIDAD
calculated_hash = SHA-256(serialize(encrypted_blob))
IF calculated_hash != blob_hash:
THROW IntegrityError("Blob corrupted or tampered")
2. DESEMPAQUETAR
nonce = base64_decode(encrypted_blob.nonce)
ciphertext = base64_decode(encrypted_blob.ciphertext)
tag = base64_decode(encrypted_blob.tag)
version = encrypted_blob.version
3. OBTENER CLAVE
master_key = Keychain.get("master_key")
dek = HKDF(master_key, salt="medtime-dek-salt", info=entity_type)
4. RECONSTRUIR AAD
aad = utf8_encode(entity_id + "|" + entity_type + "|" + version)
IF sha256(aad) != encrypted_blob.aad_hash:
THROW IntegrityError("AAD mismatch")
5. DESCIFRAR
plaintext_bytes = AES-256-GCM.decrypt(
key: dek,
nonce: nonce,
ciphertext: ciphertext,
tag: tag,
aad: aad
)
// Si tag no valida, GCM lanza excepcion automaticamente
6. DESERIALIZAR
plaintext = JSON.parse(utf8_decode(plaintext_bytes))
SALIDA:
- plaintext: Datos en claro
MANEJO DE ERRORES:
- IntegrityError: Blob corrupto o manipulado -> Reportar, no usar
- AuthenticationError: Tag GCM invalido -> Posible ataque, alertar
- DecryptionError: Clave incorrecta -> Verificar autenticacion
+------------------------------------------------------------------+
3.4. Estructura de Datos Cifrados¶
{
"version": "1.0",
"algorithm": "AES-256-GCM",
"kdf": "argon2id",
"nonce": "base64_encoded_96bit_nonce",
"ciphertext": "base64_encoded_encrypted_data",
"tag": "base64_encoded_128bit_auth_tag",
"aad_hash": "sha256_of_additional_authenticated_data",
"metadata": {
"created_at": "2025-12-07T10:00:00Z",
"entity_type": "medication",
"key_version": 1
}
}
3.4.1. Campos Obligatorios¶
| Campo | Descripcion | Tamano |
|---|---|---|
version |
Version del formato de cifrado | String |
nonce |
Valor unico por operacion | 96 bits (12 bytes) |
ciphertext |
Datos cifrados | Variable |
tag |
Tag de autenticacion GCM | 128 bits (16 bytes) |
aad_hash |
Hash de datos autenticados | 256 bits (32 bytes) |
3.5. Manejo de Nonces¶
MANEJO SEGURO DE NONCES:
+------------------------------------------------------------------+
REGLA CRITICA:
+----------------------------------+
| NUNCA reutilizar un nonce con |
| la misma clave. Reutilizacion |
| permite ataques de recuperacion. |
+----------------------------------+
ESTRATEGIA DE GENERACION:
1. Usar CSPRNG del sistema operativo
- iOS: SecRandomCopyBytes
- Android: SecureRandom
2. 96 bits = 2^96 combinaciones
3. Probabilidad de colision negligible para uso normal
PROTECCION ADICIONAL:
- Cada entidad tiene su propia DEK (derivada de master_key + dominio)
- Incluso si nonce se repitiera, diferentes DEKs
LIMITES SEGUROS (NIST SP 800-38D):
- Con 96-bit nonce: max 2^32 invocaciones por clave
- MedTime: max ~1000 sincronizaciones/dia por usuario
- Margen de seguridad: millones de anos
VERIFICACION EN TESTS:
- Unit test: verificar que nonces nunca se repiten en 10,000 llamadas
- Integration test: verificar generacion distribuida
+------------------------------------------------------------------+
3.6. Datos Adicionales Autenticados (AAD)¶
ADDITIONAL AUTHENTICATED DATA (AAD):
+------------------------------------------------------------------+
AAD permite autenticar datos que no se cifran pero deben ser
verificados como parte del mensaje.
ESTRUCTURA AAD EN MEDTIME:
aad = entity_id | "|" | entity_type | "|" | version
EJEMPLO:
entity_id: "550e8400-e29b-41d4-a716-446655440000"
entity_type: "medication"
version: "1.0"
aad: "550e8400-e29b-41d4-a716-446655440000|medication|1.0"
PROPOSITO:
1. Vincular ciphertext a entidad especifica
2. Prevenir ataques de replay (mover blob a otra entidad)
3. Detectar modificacion de metadata
VERIFICACION:
- Al descifrar, reconstruir AAD y comparar hash
- Si AAD no coincide, blob fue manipulado o movido
+------------------------------------------------------------------+
3.7. Padding de Blobs (Prevencion de Metadata Attack)¶
PROBLEMA DE METADATA LEAK:
+------------------------------------------------------------------+
| Aunque el contenido este cifrado, el TAMANO del blob puede |
| revelar informacion: |
| - Blob grande = muchos medicamentos = condicion cronica |
| - Cambio de tamano = agregado/eliminado medicamento |
| - Correlacion temporal = patrones de comportamiento |
+------------------------------------------------------------------+
SOLUCION: PADDING A TAMANO FIJO
+------------------------------------------------------------------+
| Aplicar padding para que todos los blobs tengan tamano |
| multiplo de un bloque (1KB), ocultando el tamano real. |
+------------------------------------------------------------------+
IMPLEMENTACION:
+------------------------------------------------------------------+
// Swift (iOS)
func padToBlock(_ data: Data, blockSize: Int = 1024) -> Data {
let paddingNeeded = blockSize - (data.count % blockSize)
// Usar PKCS#7-like padding (ultimo byte = cantidad de padding)
let paddingByte = UInt8(paddingNeeded)
let padding = Data(repeating: paddingByte, count: paddingNeeded)
return data + padding
}
func removePadding(_ data: Data) -> Data {
guard let lastByte = data.last else { return data }
let paddingLength = Int(lastByte)
guard paddingLength > 0, paddingLength <= 1024 else { return data }
return data.prefix(data.count - paddingLength)
}
// Kotlin (Android)
fun padToBlock(data: ByteArray, blockSize: Int = 1024): ByteArray {
val paddingNeeded = blockSize - (data.size % blockSize)
val padding = ByteArray(paddingNeeded) { paddingNeeded.toByte() }
return data + padding
}
fun removePadding(data: ByteArray): ByteArray {
val paddingLength = data.last().toInt() and 0xFF
if (paddingLength <= 0 || paddingLength > 1024) return data
return data.copyOfRange(0, data.size - paddingLength)
}
+------------------------------------------------------------------+
FLUJO CON PADDING:
1. Serializar datos a JSON bytes
2. Aplicar padding a multiplo de 1KB
3. Cifrar con AES-256-GCM
4. Servidor almacena blob de tamano uniforme
AL DESCIFRAR:
1. Descifrar con AES-256-GCM
2. Remover padding (leer ultimo byte para saber cuantos)
3. Deserializar JSON
ENTIDADES QUE REQUIEREN PADDING:
- cli_medications (lista de medicamentos)
- cli_scheduled_alerts (lista de IDs de medicamentos)
- cli_emergency_contacts (lista de contactos)
- Cualquier entidad con listas de tamano variable
+------------------------------------------------------------------+
4. Blind Index para Busqueda Segura¶
4.1. Proposito del Blind Index¶
PROBLEMA:
+------------------------------------------------------------------+
| Si el email esta cifrado E2E, el servidor no puede: |
| - Buscar usuarios por email para login |
| - Verificar unicidad de email |
| - Enviar emails de recuperacion (sin saber el email) |
+------------------------------------------------------------------+
SOLUCION: BLIND INDEX
+------------------------------------------------------------------+
| Hash determinista que permite buscar sin revelar el valor |
| original. El servidor puede buscar pero no puede leer. |
+------------------------------------------------------------------+
4.1.1. Analogia¶
Es como una huella digital:
- Puedes verificar si dos huellas son iguales
- Pero no puedes reconstruir la persona desde la huella
4.2. Implementacion Tecnica¶
BLIND INDEX SPECIFICATION:
+------------------------------------------------------------------+
ALGORITMO: HMAC-SHA256
CLAVE: global_salt (almacenada en HSM del servidor)
INPUT: Valor normalizado (lowercase, trim)
OUTPUT: 256 bits, truncados a 128 bits para storage
NORMALIZACION:
- Email: lowercase, trim, remove dots in local part (gmail)
- Telefono: solo digitos, sin espacios ni guiones
- Nombre: lowercase, trim, normalizar unicode (NFD)
TRUNCAMIENTO:
- HMAC-SHA256 produce 256 bits
- Almacenar solo primeros 128 bits (32 hex chars)
- Reduce storage sin perder seguridad practica
+------------------------------------------------------------------+
4.2.1. Pseudocodigo¶
# Pseudocodigo - NO usar en produccion sin revision de seguridad
import hmac
import hashlib
def create_blind_index(value: str, global_salt: bytes, field_type: str) -> str:
"""
Crea un blind index para busqueda segura.
Args:
value: Valor original (email, telefono, etc.)
global_salt: Salt global (DEBE estar en HSM)
field_type: Tipo de campo para salt adicional
Returns:
Hex string del blind index (32 caracteres)
"""
# Normalizar segun tipo
if field_type == "email":
normalized = normalize_email(value)
elif field_type == "phone":
normalized = normalize_phone(value)
else:
normalized = value.lower().strip()
# Agregar salt especifico del campo
input_data = f"{field_type}:{normalized}".encode('utf-8')
# HMAC con global_salt
mac = hmac.new(
key=global_salt,
msg=input_data,
digestmod=hashlib.sha256
)
# Truncar a 128 bits (32 hex chars)
return mac.hexdigest()[:32]
def normalize_email(email: str) -> str:
"""Normaliza email para blind index."""
email = email.lower().strip()
# Remover dots en parte local (Gmail ignora dots)
local, domain = email.split('@')
if domain in ['gmail.com', 'googlemail.com']:
local = local.replace('.', '')
return f"{local}@{domain}"
def normalize_phone(phone: str) -> str:
"""Normaliza telefono a solo digitos."""
return ''.join(c for c in phone if c.isdigit())
4.3. Casos de Uso¶
| Caso de Uso | Campo | Blind Index Usado |
|---|---|---|
| Login por email | email_blind_index |
|
| Verificar unicidad | email_blind_index |
|
| SMS para MFA | telefono | phone_blind_index |
| Buscar paciente (medico) | nombre | name_blind_index |
4.3.1. Flujo de Login con Blind Index¶
sequenceDiagram
participant U as Usuario
participant C as Cliente
participant S as Servidor
U->>C: Ingresar email: "Juan@Example.com"
C->>C: normalize("Juan@Example.com") = "juan@example.com"
C->>C: blind_idx = HMAC(global_salt, "email:juan@example.com")
C->>S: POST /auth/login {blind_index: "7f8a9b2c..."}
S->>S: SELECT * FROM users WHERE email_blind_index = "7f8a9b2c..."
S->>C: {user_id, encrypted_challenge}
Note over S: Servidor encontro usuario<br/>pero NO sabe email real
4.4. Limitaciones y Mitigaciones¶
LIMITACIONES DE BLIND INDEX:
+------------------------------------------------------------------+
LIMITACION 1: Ataques de Diccionario
- Atacante con global_salt puede probar emails comunes
- MITIGACION:
* global_salt en HSM (acceso auditado)
* Rate limiting en busquedas
* Honeypots con blind indexes falsos
LIMITACION 2: Colisiones
- Dos valores diferentes podrian producir mismo blind index
- MITIGACION:
* 128 bits tiene colision negligible para millones de usuarios
* Verificacion adicional en cliente post-match
LIMITACION 3: Busquedas Parciales
- No permite "LIKE '%ejemplo%'" solo match exacto
- MITIGACION:
* Busquedas complejas se hacen client-side
* Para catalogos, usar datos no cifrados
PROTECCIONES ADICIONALES:
+----------------------------------+
| - global_salt en HSM |
| - Rotacion anual de global_salt |
| - Audit log de cada busqueda |
| - Rate limit: 10 busquedas/min |
| - Monitoreo de patrones anomalos |
+----------------------------------+
+------------------------------------------------------------------+
4.5. Estrategia de Rotacion de global_salt (DV2-P3: SEC-BAJO-002)¶
ROTACION DUAL DE BLIND INDEX KEYS:
+------------------------------------------------------------------+
PROBLEMA:
- Rotar global_salt invalida TODOS los blind indexes existentes
- Re-indexar toda la BD en produccion es operacion de alto riesgo
- Durante rotacion, busquedas pueden fallar
SOLUCION: DUAL KEY SYSTEM
+------------------------------------------------------------------+
FASE 1: ESCRITURA DUAL (Preparacion)
┌─────────────────────────────────────────────────────────────────┐
│ Servidor almacena: │
│ - global_salt_v1 (actual) │
│ - global_salt_v2 (nuevo) │
│ │
│ Para cada campo buscable: │
│ - email_blind_index_v1 = HMAC(global_salt_v1, email) │
│ - email_blind_index_v2 = HMAC(global_salt_v2, email) │
│ │
│ Cliente recibe AMBAS keys en /auth/login response │
│ Cliente calcula y envia AMBOS blind indexes en writes │
│ │
│ Busquedas usan: WHERE (email_blind_index_v1 = X OR │
│ email_blind_index_v2 = Y) │
│ │
│ Duracion: 30 dias (permitir que todos los usuarios activos │
│ escriban ambos indexes) │
└─────────────────────────────────────────────────────────────────┘
FASE 2: MIGRACION BATCH (Background)
┌─────────────────────────────────────────────────────────────────┐
│ Job batch en servidor: │
│ FOR cada usuario en BD: │
│ IF email_blind_index_v2 IS NULL: │
│ # Usuario inactivo, recalcular server-side │
│ # IMPOSIBLE - servidor no tiene valor claro │
│ # SOLUCION: Marcar para re-index en proximo login │
│ set_flag(user_id, "needs_reindex_v2") │
│ │
│ Durante login de usuario con flag: │
│ Cliente recibe global_salt_v2 │
│ Cliente calcula blind_index_v2 localmente │
│ Cliente envia v2 al servidor │
│ Servidor actualiza email_blind_index_v2 │
│ Servidor limpia flag │
│ │
│ Metrica: % de usuarios con v2 populated │
│ Goal: 95% coverage antes de FASE 3 │
└─────────────────────────────────────────────────────────────────┘
FASE 3: CUTOVER (Finalizacion)
┌─────────────────────────────────────────────────────────────────┐
│ Cuando 95%+ usuarios tienen v2: │
│ 1. Cambiar busquedas a solo v2 │
│ WHERE email_blind_index_v2 = Y │
│ │
│ 2. Cliente solo recibe global_salt_v2 │
│ 3. Cliente solo envia v2 en writes │
│ │
│ 4. DROP column email_blind_index_v1 (30 dias despues) │
│ 5. Renombrar v2 -> v1 para proximo ciclo │
│ 6. Archivar global_salt_v1 viejo (NUNCA eliminar) │
│ │
│ Rollback plan: Revertir a busqueda dual si issues │
└─────────────────────────────────────────────────────────────────┘
CALENDARIO DE ROTACION:
+------------------------------------------------------------------+
| Frecuencia: ANUAL (cada 365 dias) |
| Trigger: Automatico via cron job + manual override |
| Notificacion: 30 dias antes a Security Team |
| Duracion: 90 dias total (30 + 30 + 30) |
| Rollback: Posible hasta FASE 3 completada |
+------------------------------------------------------------------+
EJEMPLO DE SCHEMA:
```sql
CREATE TABLE users (
user_id UUID PRIMARY KEY,
-- Email fields
email_encrypted BYTEA NOT NULL,
email_blind_index_v1 CHAR(32), -- HMAC truncado a 128 bits
email_blind_index_v2 CHAR(32), -- Null hasta rotacion
-- Phone fields
phone_encrypted BYTEA,
phone_blind_index_v1 CHAR(32),
phone_blind_index_v2 CHAR(32),
-- Control
needs_reindex_v2 BOOLEAN DEFAULT FALSE,
last_login_at TIMESTAMPTZ
);
CREATE INDEX idx_email_v1 ON users(email_blind_index_v1);
CREATE INDEX idx_email_v2 ON users(email_blind_index_v2);
```text
EJEMPLO DE BUSQUEDA DURANTE ROTACION:
```python
def find_user_by_email(email: str) -> Optional[User]:
# Cliente calcula ambos blind indexes
blind_v1 = hmac_sha256(global_salt_v1, f"email:{email.lower()}")
blind_v2 = hmac_sha256(global_salt_v2, f"email:{email.lower()}")
# Busqueda dual
query = """
SELECT * FROM users
WHERE email_blind_index_v1 = %s
OR email_blind_index_v2 = %s
LIMIT 1
"""
return db.query_one(query, [blind_v1[:32], blind_v2[:32]])
```mermaid
MONITOREO DE ROTACION:
+------------------------------------------------------------------+
| Metrica | Threshold | Accion |
|--------------------------------|------------|--------------------|
| % usuarios con v2 | < 80% @ D60| Extender FASE 2 |
| Busquedas fallidas | > 0.1% | Rollback a dual |
| Latencia de busqueda | > +50ms | Optimizar indexes |
| Errores de HMAC calculation | > 0% | Investigar |
+------------------------------------------------------------------+
SEGURIDAD:
+------------------------------------------------------------------+
| - Ambos salts SIEMPRE en HSM |
| - Acceso a salts solo via API autenticada |
| - Audit log de CADA acceso a global_salt_v1 y v2 |
| - Cliente NUNCA almacena global_salt (solo en memoria) |
| - Rotacion NO requiere re-cifrado de datos (solo blind indexes) |
+------------------------------------------------------------------+
5. Almacenamiento Seguro por Plataforma¶
5.1. iOS - Keychain Services¶
iOS KEYCHAIN CONFIGURATION:
+------------------------------------------------------------------+
CLASE DE PROTECCION:
+----------------------------------+
| kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
| |
| - Datos accesibles solo cuando dispositivo desbloqueado |
| - Datos NO se incluyen en backup de iCloud |
| - Datos eliminados si se borra el dispositivo |
+----------------------------------+
SECURE ENCLAVE (si disponible):
+----------------------------------+
| - iPhone 5S+ / iPad Air+ |
| - Claves criptograficas NUNCA |
| salen del Secure Enclave |
| - Operaciones de cifrado dentro |
| del hardware |
+----------------------------------+
BIOMETRIC PROTECTION:
+----------------------------------+
| kSecAccessControlBiometryCurrentSet |
| |
| - Requiere autenticacion biometrica para acceder |
| - Invalidado si cambia huella/Face ID |
+----------------------------------+
+------------------------------------------------------------------+
5.1.1. Estructura de Keychain Items¶
// Pseudocodigo Swift - Referencia
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.medtime.crypto",
kSecAttrAccount as String: "master_key",
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAttrAccessControl as String: accessControl,
kSecUseAuthenticationContext as String: authContext,
kSecValueData as String: masterKeyData
]
5.1.2. Items en Keychain¶
| Item | Descripcion | Proteccion |
|---|---|---|
master_key |
Clave maestra cifrada | Biometria + SE |
recovery_key_hash |
Hash de Recovery Key | Device Only |
sync_key |
Clave de sincronizacion | Biometria |
session_token |
Token de sesion actual | Device Only |
5.2. Android - Keystore System¶
ANDROID KEYSTORE CONFIGURATION:
+------------------------------------------------------------------+
HARDWARE-BACKED KEYSTORE:
+----------------------------------+
| - Android 9+ con TEE |
| - Pixel 3+ con Titan M |
| - Samsung con Knox |
| - Claves NUNCA exportables |
+----------------------------------+
STRONGBOX (si disponible):
+----------------------------------+
| setIsStrongBoxBacked(true) |
| |
| - Chip dedicado (similar a SE) |
| - Resistente a ataques fisicos |
| - Pixel 3+, Galaxy S10+ |
+----------------------------------+
BIOMETRIC BINDING:
+----------------------------------+
| setUserAuthenticationRequired(true) |
| setUserAuthenticationValidityDurationSeconds(-1) |
| setInvalidatedByBiometricEnrollment(true) |
| |
| - Requiere autenticacion biometrica cada vez |
| - Invalidado si cambia huella/face |
+----------------------------------+
+------------------------------------------------------------------+
5.2.1. Generacion de Clave en Keystore¶
// Pseudocodigo Kotlin - Referencia
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
val parameterSpec = KeyGenParameterSpec.Builder(
"medtime_master_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setKeySize(256)
setUserAuthenticationRequired(true)
setUserAuthenticationValidityDurationSeconds(-1)
setInvalidatedByBiometricEnrollment(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
setIsStrongBoxBacked(true)
}
}.build()
keyGenerator.init(parameterSpec)
keyGenerator.generateKey()
5.3. Comparativa de Seguridad¶
| Caracteristica | iOS Keychain + SE | Android Keystore + StrongBox |
|---|---|---|
| Hardware isolation | Si (Secure Enclave) | Si (TEE/StrongBox) |
| Key never leaves hardware | Si | Si |
| Biometric binding | Si | Si |
| Tamper resistance | Muy alta | Alta (varia por OEM) |
| Backup protection | No backup por defecto | Depende configuracion |
| API consistency | Muy alta | Varia por Android version |
5.4. Fallback y Compatibilidad¶
FALLBACK STRATEGY:
+------------------------------------------------------------------+
iOS:
+----------------------------------+
| Secure Enclave disponible? |
| Si -> Usar SE para master_key |
| No -> (no hay iPhone sin SE) |
| |
| Biometria disponible? |
| Si -> Proteger con Face/Touch |
| No -> Proteger con passcode |
+----------------------------------+
Android:
+----------------------------------+
| StrongBox disponible? |
| Si -> setIsStrongBoxBacked(true)|
| No -> Usar TEE (software-backed)|
| |
| Biometria disponible? |
| Si -> setUserAuthenticationRequired(true)|
| No -> Usar PIN/Pattern |
| |
| Android < 6.0? |
| MedTime no soporta < Android 8 |
+----------------------------------+
MINIMOS REQUERIDOS:
- iOS 15+: Secure Enclave garantizado
- Android 8+: Keystore con TEE disponible
+------------------------------------------------------------------+
6. Batch Encryption Strategy¶
6.1. Contexto¶
El cifrado de multiples entidades durante sync inicial o bulk operations requiere una estrategia que:
- Evite bloquear el thread principal (UI responsiva)
- Proporcione feedback de progreso al usuario
- Maneje errores parciales sin perder datos ya procesados
6.2. Configuracion¶
batch_encryption:
batch_size: 10 # Entidades por batch
parallel_operations: 4 # Operaciones concurrentes dentro del batch
ui_yield_ms: 16 # Yield entre batches (~60fps)
retry_failed: true # Reintentar items fallidos
max_retries_per_item: 3 # Maximo reintentos por entidad
6.3. Patron de Implementacion¶
BATCH ENCRYPTION FLOW:
+------------------------------------------------------------------+
INITIAL SYNC (250 entities)
|
v
+-------------------------------+
| Dividir en batches |
| (25 batches x 10 items) |
+-------------------------------+
|
v
+--------------------------------------------+
| PROCESO POR BATCH |
| +--------------------------------------+ |
| | Batch N (10 entidades) | |
| | | |
| | [E1][E2][E3][E4] <- 4 en paralelo | |
| | [E5][E6][E7][E8] <- 4 en paralelo | |
| | [E9][E10] <- 2 en paralelo | |
| +--------------------------------------+ |
| | |
| v |
| +--------------------------------------+ |
| | Progress Callback | |
| | - encrypted: N * 10 | |
| | - total: 250 | |
| | - percentage: (N*10/250)*100 | |
| +--------------------------------------+ |
| | |
| v |
| +--------------------------------------+ |
| | UI Yield (16ms) | |
| | - Actualizar progress bar | |
| | - Procesar eventos UI | |
| +--------------------------------------+ |
+--------------------------------------------+
|
v
+-------------------------------+
| Siguiente batch |
| (loop hasta completar) |
+-------------------------------+
+------------------------------------------------------------------+
6.4. Implementacion iOS (Swift)¶
actor BatchEncryptor {
private let cryptoManager: CryptoManager
private let batchSize = 10
private let parallelism = 4
func encryptBatch<T: Encryptable>(
entities: [T],
progress: @escaping (Int, Int) -> Void
) async throws -> [EncryptedBlob] {
var results: [EncryptedBlob] = []
results.reserveCapacity(entities.count)
for (batchIndex, batch) in entities.chunked(into: batchSize).enumerated() {
// Procesar batch en paralelo (limitado a 4)
let batchResults = try await withThrowingTaskGroup(of: EncryptedBlob.self) { group in
for entity in batch {
group.addTask {
try await self.cryptoManager.encrypt(entity)
}
}
var batchEncrypted: [EncryptedBlob] = []
for try await result in group {
batchEncrypted.append(result)
}
return batchEncrypted
}
results.append(contentsOf: batchResults)
// Progress callback
let encrypted = (batchIndex + 1) * batchSize
progress(min(encrypted, entities.count), entities.count)
// UI yield
try await Task.sleep(nanoseconds: 16_000_000) // 16ms
}
return results
}
}
6.5. Implementacion Android (Kotlin)¶
class BatchEncryptor(
private val cryptoManager: CryptoManager,
private val dispatcher: CoroutineDispatcher = Dispatchers.Default
) {
companion object {
private const val BATCH_SIZE = 10
private const val PARALLELISM = 4
private const val UI_YIELD_MS = 16L
}
suspend fun <T : Encryptable> encryptBatch(
entities: List<T>,
onProgress: (encrypted: Int, total: Int) -> Unit
): List<EncryptedBlob> = withContext(dispatcher) {
val results = mutableListOf<EncryptedBlob>()
val semaphore = Semaphore(PARALLELISM)
entities.chunked(BATCH_SIZE).forEachIndexed { batchIndex, batch ->
val batchResults = batch.map { entity ->
async {
semaphore.withPermit {
cryptoManager.encrypt(entity)
}
}
}.awaitAll()
results.addAll(batchResults)
// Progress callback (en Main thread)
withContext(Dispatchers.Main) {
val encrypted = minOf((batchIndex + 1) * BATCH_SIZE, entities.size)
onProgress(encrypted, entities.size)
}
// UI yield
delay(UI_YIELD_MS)
}
results
}
}
6.6. Manejo de Errores¶
error_handling:
partial_failure:
behavior: "Continuar con resto del batch"
failed_items: "Agregar a retry queue"
retry_strategy:
max_attempts: 3
backoff: exponential
base_delay_ms: 100
complete_failure:
behavior: "Rollback transaccion"
user_notification: "Error de sincronizacion, reintentar?"
progress_on_error:
show_failed_count: true
allow_skip: false # No permitir skip de items fallidos
6.7. Metricas de Performance¶
| Escenario | Entidades | Tiempo Esperado | Notas |
|---|---|---|---|
| Sync inicial tipico | 50 | < 3s | Usuario nuevo |
| Sync inicial maximo | 250 | < 15s | Multi-perfil |
| Sync incremental | 5-10 | < 500ms | Tipico diario |
| Bulk import | 100 | < 6s | Importar recetas |
7. Autenticacion Local¶
7.1. Biometria¶
CONFIGURACION BIOMETRICA:
+------------------------------------------------------------------+
iOS - LOCAL AUTHENTICATION:
+----------------------------------+
| LAContext con: |
| - .biometryCurrentSet |
| - evaluatePolicy(.deviceOwner |
| Authentication) |
| |
| Fallback: Passcode del sistema |
+----------------------------------+
Android - BIOMETRIC PROMPT:
+----------------------------------+
| BiometricPrompt con: |
| - BIOMETRIC_STRONG |
| - setNegativeButtonText para |
| fallback a PIN |
| |
| Clases soportadas: |
| - Class 3 (Strong) |
| - Fingerprint, Face (3D) |
+----------------------------------+
+------------------------------------------------------------------+
7.1.1. Flujo de Autenticacion Biometrica¶
flowchart TD
A[Usuario abre app] --> B{Sesion activa?}
B -->|No| C[Mostrar pantalla de login]
C --> D[Solicitar biometria]
D --> E{Biometria exitosa?}
E -->|Si| F[Desbloquear Keychain]
E -->|No| G{Intentos < 3?}
G -->|Si| D
G -->|No| H[Fallback a PIN]
H --> I{PIN correcto?}
I -->|Si| F
I -->|No| J{Intentos < 5?}
J -->|Si| H
J -->|No| K[Bloquear app 30min]
F --> L[Obtener master_key]
L --> M[App desbloqueada]
7.2. PIN/Passcode¶
PIN SECURITY:
+------------------------------------------------------------------+
REQUISITOS MINIMOS:
+----------------------------------+
| - 4 digitos minimo (configurable)|
| - 6 digitos recomendado |
| - Alfanumerico opcional |
| - NO patrones simples: |
| * 1234, 0000, 1111 |
| * Fecha nacimiento |
| * Ultimos 4 digitos telefono |
+----------------------------------+
ALMACENAMIENTO:
+----------------------------------+
| pin_hash = Argon2id( |
| pin + device_specific_salt, |
| memory=64MiB, |
| iterations=3 |
| ) |
| |
| Almacenado en Keychain/Keystore |
+----------------------------------+
PROTECCION ANTI-BRUTE-FORCE:
+----------------------------------+
| Intentos | Delay |
|----------|----------------------|
| 1-3 | Sin delay |
| 4-5 | 30 segundos |
| 6-7 | 1 minuto |
| 8-9 | 5 minutos |
| 10 | 30 minutos |
| 11+ | Requiere Recovery Key|
+----------------------------------+
+------------------------------------------------------------------+
7.3. Flujo de Autenticacion Local¶
sequenceDiagram
participant U as Usuario
participant A as App
participant B as BiometricPrompt
participant K as Keychain/Keystore
U->>A: Abrir app
A->>A: Verificar estado de sesion
alt Sesion expirada
A->>B: Solicitar biometria
B->>U: Mostrar prompt biometrico
U->>B: Autenticar (huella/face)
B->>K: Desbloquear acceso
K->>A: master_key accesible
A->>A: Derivar session_key
A->>U: App desbloqueada
else Sesion activa
A->>U: Acceso directo
end
7.4. Proteccion contra Ataques¶
PROTECCIONES DE AUTENTICACION LOCAL:
+------------------------------------------------------------------+
ANTI-BRUTE-FORCE:
- Backoff exponencial despues de 3 intentos
- Lockout temporal despues de 10 intentos
- Wipe opcional despues de 15 intentos (configurable)
ANTI-REPLAY:
- Tokens de sesion tienen timestamp
- Nonce en cada request de autenticacion
- Session binding a device_id
ANTI-TAMPERING:
- Checksum de codigo en runtime (iOS/Android)
- Deteccion de debugging
- Deteccion de instrumentacion (Frida, Xposed)
SCREEN PROTECTION:
- Blur/Cover cuando app va a background
- Prevenir screenshots en pantallas sensibles
- Secure text entry para PIN
+------------------------------------------------------------------+
7.5. Session Management Local¶
SESSION MANAGEMENT:
+------------------------------------------------------------------+
TIPOS DE SESION:
+----------------------------------+
| 1. App Session |
| - Inicia: Login exitoso |
| - Termina: App killed o |
| background > timeout|
| |
| 2. Background Session |
| - Inicia: App a background |
| - Termina: Timeout o foreground|
| - Timeout: 5 min (config) |
| |
| 3. Secure Operation Session |
| - Inicia: Operacion sensible |
| - Termina: Operacion completa|
| - Requiere: Biometria cada vez|
+----------------------------------+
TIMEOUT CONFIGURABLES:
+----------------------------------+
| Setting | Default |
|-------------------|-------------|
| background_timeout| 5 minutos |
| session_max_age | 24 horas |
| pin_entry_timeout | 30 segundos |
| biometric_prompt | 60 segundos |
+----------------------------------+
OPERACIONES QUE REQUIEREN RE-AUTENTICACION:
- Cambiar password/PIN
- Exportar datos
- Compartir con nuevo receptor
- Ver Recovery Key
- Eliminar cuenta
+------------------------------------------------------------------+
8. Proteccion de Datos en Reposo¶
8.1. Base de Datos Local¶
DATABASE ENCRYPTION:
+------------------------------------------------------------------+
iOS - CORE DATA:
+----------------------------------+
| Opcion 1: File Protection |
| - NSFileProtectionComplete |
| - Cifrado transparente por iOS |
| - Datos inaccesibles si locked |
| |
| Opcion 2: SQLCipher (opcional) |
| - Cifrado a nivel de SQLite |
| - Clave derivada de master_key |
| - Proteccion adicional |
+----------------------------------+
Android - ROOM + SQLCipher:
+----------------------------------+
| SQLCipher for Android |
| - AES-256-CBC |
| - Clave derivada de master_key |
| - Integrado con Room |
| |
| Alternativa: EncryptedFile |
| - Jetpack Security library |
+----------------------------------+
RECOMENDACION MEDTIME:
- iOS: File Protection (suficiente dado Secure Enclave)
- Android: SQLCipher (TEE menos uniforme)
+------------------------------------------------------------------+
8.1.1. Estructura de Almacenamiento¶
/app_sandbox/
+------------------------------------------------------------------+
|
+-- databases/
| +-- medtime.db # SQLite (cifrado por OS/SQLCipher)
| +-- medtime.db-wal # Write-ahead log
| +-- medtime.db-shm # Shared memory
|
+-- documents/
| +-- prescriptions/ # Imagenes de recetas (cifradas E2E)
| | +-- rx_uuid1.enc # Blob AES-256-GCM
| | +-- rx_uuid2.enc
| +-- exports/ # Backups temporales (cifrados)
| +-- backup_date.enc
|
+-- caches/
| +-- images/ # Cache de imagenes (efimero)
| +-- catalogs/ # Cache de catalogos (publicos)
|
+-- Library/ # iOS
| +-- Preferences/ # UserDefaults (NO para PHI)
|
+-- shared_prefs/ # Android
+-- app_preferences.xml # NO para PHI
|
+------------------------------------------------------------------+
8.2. Archivos y Cache¶
FILE ENCRYPTION POLICY:
+------------------------------------------------------------------+
ARCHIVOS PHI (recetas, fotos medicas):
+----------------------------------+
| - Cifrar con AES-256-GCM |
| - Clave: DEK especifica de media|
| - Almacenar en documents/ |
| - Extension: .enc |
| - Metadata en DB (nombre, tipo) |
+----------------------------------+
ARCHIVOS TEMPORALES:
+----------------------------------+
| - Usar directorio tmp/ del OS |
| - Eliminar inmediatamente |
| despues de uso |
| - NUNCA almacenar PHI en tmp |
+----------------------------------+
CACHE:
+----------------------------------+
| Permitido en cache: |
| - Imagenes de UI (no PHI) |
| - Catalogos de medicamentos |
| - Assets estaticos |
| |
| PROHIBIDO en cache: |
| - Cualquier dato PHI |
| - Tokens de sesion |
| - Claves criptograficas |
+----------------------------------+
+------------------------------------------------------------------+
8.3. Clasificacion de Datos¶
CLASIFICACION DE ALMACENAMIENTO:
+------------------------------------------------------------------+
| Dato | Ubicacion | Cifrado |
|-----------------------|--------------------|-------------------|
| master_key | Keychain/Keystore | Hardware (SE/TEE) |
| session_token | Keychain/Keystore | Hardware |
| pin_hash | Keychain/Keystore | Hardware |
| recovery_key_hash | Keychain/Keystore | Hardware |
| datos_phi (DB) | SQLite | E2E + Disk |
| imagenes_recetas | Documents/ | E2E + Disk |
| preferencias_no_phi | UserDefaults/SP | Disk (OS) |
| catalogos_publicos | Cache/ | Sin cifrar |
| logs_anonimos | Logs/ | Disk (OS) |
REGLA GENERAL:
- PHI -> Cifrado E2E + Disk encryption
- Claves -> Hardware security
- Publico -> Disk encryption del OS es suficiente
+------------------------------------------------------------------+
8.4. Borrado Seguro¶
SECURE DELETE POLICY:
+------------------------------------------------------------------+
AL CERRAR SESION:
+----------------------------------+
| - Limpiar session_token |
| - Limpiar caches de datos PHI |
| - Mantener: master_key (wrapped)|
| - Mantener: preferencias |
+----------------------------------+
AL ELIMINAR CUENTA:
+----------------------------------+
| 1. Solicitar confirmacion 2x |
| 2. Requerir Recovery Key |
| 3. Enviar DELETE al servidor |
| 4. Eliminar todos los archivos: |
| - Keychain items |
| - Base de datos |
| - Archivos de documentos |
| - Caches |
| 5. Sobrescribir con zeros |
| (donde sea posible) |
| 6. Reinstalar app desde store |
| para borrado completo |
+----------------------------------+
iOS SECURE DELETE:
- SecItemDelete para Keychain
- FileManager.removeItem
- NSFileManager.replaceItemAtURL
Android SECURE DELETE:
- keystore.deleteEntry()
- context.deleteDatabase()
- File.delete() + sanitize
+------------------------------------------------------------------+
9. Proteccion de Datos en Transito¶
9.1. TLS 1.3¶
TLS CONFIGURATION:
+------------------------------------------------------------------+
REQUISITOS MINIMOS:
+----------------------------------+
| Protocolo: TLS 1.3 obligatorio|
| Fallback: TLS 1.2 (temporal) |
| NO soportado: TLS 1.1, 1.0, SSL |
+----------------------------------+
CIPHER SUITES (orden de preferencia):
+----------------------------------+
| 1. TLS_AES_256_GCM_SHA384 |
| 2. TLS_CHACHA20_POLY1305_SHA256 |
| 3. TLS_AES_128_GCM_SHA256 |
+----------------------------------+
KEY EXCHANGE:
+----------------------------------+
| - X25519 (preferido) |
| - secp256r1 (P-256) |
| - secp384r1 (P-384) |
+----------------------------------+
HEADERS DE SEGURIDAD:
+----------------------------------+
| Strict-Transport-Security: |
| max-age=31536000; |
| includeSubDomains |
| |
| X-Content-Type-Options: nosniff |
| X-Frame-Options: DENY |
+----------------------------------+
+------------------------------------------------------------------+
9.2. Certificate Pinning¶
CERTIFICATE PINNING:
+------------------------------------------------------------------+
ESTRATEGIA:
+----------------------------------+
| Pin Type: SPKI (Subject Public Key Info) |
| Algoritmo: SHA-256 |
| Pins: 2 minimo (primary + backup) |
| Rotacion: Anual, con overlap de 30 dias |
+----------------------------------+
iOS - NETWORK SECURITY:
+----------------------------------+
| Info.plist: |
| <key>NSAppTransportSecurity</key>|
| <dict> |
| <key>NSPinnedDomains</key> |
| <dict> |
| <key>api.medtime.com</key> |
| ... |
| </dict> |
| </dict> |
+----------------------------------+
Android - NETWORK SECURITY CONFIG:
+----------------------------------+
| res/xml/network_security_config: |
| <network-security-config> |
| <domain-config> |
| <domain>api.medtime.com</domain>|
| <pin-set> |
| <pin digest="SHA-256"> |
| base64_encoded_hash |
| </pin> |
| </pin-set> |
| </domain-config> |
| </network-security-config> |
+----------------------------------+
DOMINIOS A PINEAR:
- api.medtime.com (API principal)
- auth.medtime.com (autenticacion)
- sync.medtime.com (sincronizacion)
+------------------------------------------------------------------+
9.2.1. SPKI Pins por Dominio (DV2-REMEDIACION)¶
IMPORTANTE: Los pins a continuacion son PLACEHOLDERS. Generar pins reales antes de produccion usando:
openssl s_client -connect domain:443 | openssl x509 -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Certificate Pins (PLACEHOLDERS - GENERAR ANTES DE PROD):
api.medtime.app:
description: "API principal"
primary_pin: "sha256/AAAA_PLACEHOLDER_GENERATE_BEFORE_PRODUCTION_AAAA"
backup_pin: "sha256/BBBB_PLACEHOLDER_BACKUP_CA_ROOT_BBBB"
expiry_date: "2026-01-01"
note: "Primary = leaf cert, Backup = CA intermediate"
sync.medtime.app:
description: "Sincronizacion de blobs"
primary_pin: "sha256/CCCC_PLACEHOLDER_GENERATE_BEFORE_PRODUCTION_CCCC"
backup_pin: "sha256/DDDD_PLACEHOLDER_BACKUP_CA_ROOT_DDDD"
expiry_date: "2026-01-01"
auth.medtime.app:
description: "Autenticacion Firebase"
primary_pin: "sha256/EEEE_PLACEHOLDER_GENERATE_BEFORE_PRODUCTION_EEEE"
backup_pin: "sha256/FFFF_PLACEHOLDER_BACKUP_CA_ROOT_FFFF"
expiry_date: "2026-01-01"
note: "Puede requerir pins de Firebase/Google"
Pin Validation:
on_failure: BLOCK_REQUEST
fallback: none # No fallback, seguridad primero
user_message: "Connection security error. Please try again later."
log_event: security_pin_failure (sin PHI)
Pin Update Mechanism:
method: Remote Config (Firebase)
update_check: On app start
grace_period: 24h # Usar pins cached si remote falla
emergency_bypass: false # NUNCA bypass pins
Rotation Schedule:
frequency: Annual
overlap_period: 30 days # Ambos pins validos durante transicion
notification: 60 days antes de expiracion
responsible: Security Team
9.2.2. Implementacion iOS¶
// Info.plist - NSAppTransportSecurity
/*
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>api.medtime.app</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>AAAA_PLACEHOLDER_PRIMARY</string>
</dict>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>BBBB_PLACEHOLDER_BACKUP</string>
</dict>
</array>
</dict>
<!-- Repetir para sync.medtime.app, auth.medtime.app -->
</dict>
</dict>
*/
// TrustKit alternative for dynamic pins
class CertificatePinningManager {
static let shared = CertificatePinningManager()
func configurePinning() {
let config: [String: Any] = [
kTSKSwizzleNetworkDelegates: true,
kTSKPinnedDomains: [
"api.medtime.app": [
kTSKEnforcePinning: true,
kTSKPublicKeyHashes: [
"AAAA_PLACEHOLDER_PRIMARY",
"BBBB_PLACEHOLDER_BACKUP"
]
]
]
]
TrustKit.initSharedInstance(withConfiguration: config)
}
}
9.2.3. Implementacion Android¶
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">api.medtime.app</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">AAAA_PLACEHOLDER_PRIMARY</pin>
<pin digest="SHA-256">BBBB_PLACEHOLDER_BACKUP</pin>
</pin-set>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">sync.medtime.app</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">CCCC_PLACEHOLDER_PRIMARY</pin>
<pin digest="SHA-256">DDDD_PLACEHOLDER_BACKUP</pin>
</pin-set>
</domain-config>
<domain-config cleartextTrafficPermitted="false">
<domain includeSubdomains="true">auth.medtime.app</domain>
<pin-set expiration="2026-01-01">
<pin digest="SHA-256">EEEE_PLACEHOLDER_PRIMARY</pin>
<pin digest="SHA-256">FFFF_PLACEHOLDER_BACKUP</pin>
</pin-set>
</domain-config>
</network-security-config>
// OkHttp CertificatePinner (alternative for dynamic pins)
val certificatePinner = CertificatePinner.Builder()
.add("api.medtime.app", "sha256/AAAA_PLACEHOLDER_PRIMARY")
.add("api.medtime.app", "sha256/BBBB_PLACEHOLDER_BACKUP")
.add("sync.medtime.app", "sha256/CCCC_PLACEHOLDER_PRIMARY")
.add("sync.medtime.app", "sha256/DDDD_PLACEHOLDER_BACKUP")
.build()
val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
9.2.4. Procedimiento de Rotacion de Certificate Pins (DV2-P2)¶
PROCEDIMIENTO DE ROTACION DE CERTIFICATE PINS:
+------------------------------------------------------------------+
TIMELINE DE ROTACION:
+----------------------------------+
| T-60 dias | Generar nuevos certificados |
| T-45 dias | Agregar nuevo pin como backup en app update |
| T-30 dias | Notificar usuarios de actualizacion requerida |
| T-15 dias | Activar nuevo certificado en servidor |
| T-0 | Nuevo pin es primario, viejo es backup |
| T+30 dias | Remover pin viejo de la app |
+----------------------------------+
FASE 1: PREPARACION (T-60 a T-45)
1. Generar nuevo certificado:
$ openssl req -new -x509 -days 365 -key server.key -out new_cert.pem
2. Extraer SPKI pin del nuevo certificado:
$ openssl x509 -in new_cert.pem -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64
3. Documentar nuevo pin en este documento
4. Crear PR para agregar pin a apps
FASE 2: TRANSICION (T-45 a T-15)
1. Release app con nuevo pin como BACKUP:
- iOS: Agregar a NSPinnedLeafIdentities array
- Android: Agregar a <pin-set>
- TrustKit/OkHttp: Agregar a lista de pins
2. Monitorear adopcion:
- Target: >90% usuarios en version con nuevo pin
- Alertar si adopcion < 70% a T-20
3. Opcional: Push notification recordando actualizar app
FASE 3: ACTIVACION (T-15 a T-0)
1. Instalar nuevo certificado en servidores:
- api.medtime.app
- sync.medtime.app
- auth.medtime.app
2. Verificar conectividad con ambos pins
3. Monitorear errores de conexion
4. Rollback plan: revertir a certificado viejo si >1% errores
FASE 4: LIMPIEZA (T+0 a T+30)
1. Nuevo certificado es primario
2. Certificado viejo sigue valido (backup durante transicion)
3. Release app removiendo pin viejo
4. Actualizar documentacion con nuevas fechas
+------------------------------------------------------------------+
Checklist de Rotacion:
| Paso | Responsable | Verificacion | Fecha |
|---|---|---|---|
| [ ] Generar nuevo certificado | DevOps | SHA256 pin documentado | |
| [ ] PR con nuevo pin (backup) | Mobile Dev | Tests pasan | |
| [ ] Release iOS | Mobile Dev | App Store aprobado | |
| [ ] Release Android | Mobile Dev | Play Store aprobado | |
| [ ] Verificar adopcion >90% | DevOps | Analytics dashboard | |
| [ ] Instalar cert en servers | DevOps | Health checks OK | |
| [ ] Monitorear 48h | DevOps | Error rate <0.1% | |
| [ ] PR removiendo pin viejo | Mobile Dev | Tests pasan | |
| [ ] Documentar rotacion | Security | Este doc actualizado |
Comando para Generar Pin de Certificado Existente:
# Para certificado en archivo
openssl x509 -in cert.pem -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64
# Para certificado de servidor remoto
openssl s_client -connect api.medtime.app:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64
Registro de Rotaciones:
| Fecha | Dominio | Pin Viejo (ultimos 8 chars) | Pin Nuevo (ultimos 8 chars) | Responsable |
|---|---|---|---|---|
| 2025-01-01 | api.medtime.app | (inicial) | AAAA...AAAA | - |
| proxima | - | - | - | - |
9.3. Doble Cifrado (TLS + E2E)¶
DOBLE CIFRADO EN TRANSITO:
+------------------------------------------------------------------+
CAPA 1: TLS 1.3 (Transporte)
+----------------------------------+
| - Protege contra MITM |
| - Cifrado punto a punto |
| - Servidor VE datos dentro |
+----------------------------------+
|
v
CAPA 2: E2E (Aplicacion)
+----------------------------------+
| - Datos ya cifrados antes de TLS|
| - Servidor NO puede descifrar |
| - Solo usuario tiene clave |
+----------------------------------+
FLUJO:
1. Cliente cifra datos PHI con AES-256-GCM (E2E)
2. Cliente envia blob E2E por TLS 1.3
3. TLS descifrado en servidor
4. Servidor ve: blob E2E (opaco)
5. Servidor almacena blob
6. Respuesta por TLS 1.3
BENEFICIO:
- Si TLS comprometido: atacante ve blobs opacos
- Si servidor comprometido: atacante ve blobs opacos
- Solo cliente con master_key puede descifrar
+------------------------------------------------------------------+
9.4. Proteccion contra MITM¶
PROTECCIONES MITM:
+------------------------------------------------------------------+
1. CERTIFICATE PINNING
- Previene certificados fraudulentos
- Detecta proxies corporativos
2. CERTIFICATE TRANSPARENCY
- Verificar CT logs
- Detectar certificados emitidos sin autorizacion
3. DETECCION DE PROXY
- Warning si proxy detectado
- Opcion de continuar (para debug)
- Bloquear en produccion (configurable)
4. DEVICE ATTESTATION
- iOS: App Attest
- Android: Play Integrity API
- Verificar que app es legitima
5. RESPONSE VALIDATION
- Verificar firma de respuestas
- Detectar respuestas modificadas
COMPORTAMIENTO EN DETECCION:
+----------------------------------+
| Proxy/MITM detectado: |
| 1. Log evento de seguridad |
| 2. Mostrar warning al usuario |
| 3. Bloquear operaciones PHI |
| 4. Permitir continuar (debug) |
| 5. Reportar a servidor (metadata)|
+----------------------------------+
+------------------------------------------------------------------+
10. Proteccion contra Ataques Comunes¶
10.1. OWASP Mobile Top 10¶
OWASP MOBILE TOP 10 (2024) - ESTADO MEDTIME:
+------------------------------------------------------------------+
| # | Vulnerabilidad | Estado | Mitigacion |
|----|-----------------------------------|-----------|--------------|
| M1 | Improper Platform Usage | MITIGADO | Ver 9.2 |
| M2 | Insecure Data Storage | MITIGADO | Seccion 7 |
| M3 | Insecure Communication | MITIGADO | Seccion 8 |
| M4 | Insecure Authentication | MITIGADO | Seccion 6 |
| M5 | Insufficient Cryptography | MITIGADO | Seccion 3 |
| M6 | Insecure Authorization | MITIGADO | Ver 9.2 |
| M7 | Client Code Quality | MITIGADO | Ver 9.2 |
| M8 | Code Tampering | MITIGADO | Seccion 9.4 |
| M9 | Reverse Engineering | PARCIAL | Ver 9.2 |
| M10| Extraneous Functionality | MITIGADO | Ver 9.2 |
+------------------------------------------------------------------+
10.2. Protecciones Implementadas¶
PROTECCIONES POR CATEGORIA:
+------------------------------------------------------------------+
M1 - IMPROPER PLATFORM USAGE:
+----------------------------------+
| - Usar APIs de seguridad del OS |
| - No reinventar Keychain/Keystore|
| - Seguir guidelines de Apple/Google|
| - Permisos minimos necesarios |
+----------------------------------+
M6 - INSECURE AUTHORIZATION:
+----------------------------------+
| - Verificar permisos en cada request|
| - No confiar en datos del cliente|
| - Server-side authorization |
| - Role-based access control |
+----------------------------------+
M7 - CLIENT CODE QUALITY:
+----------------------------------+
| - Static analysis (SonarQube) |
| - Dependency scanning |
| - Code review obligatorio |
| - Unit tests de crypto |
+----------------------------------+
M9 - REVERSE ENGINEERING:
+----------------------------------+
| - Obfuscacion de codigo |
| - iOS: Bitcode enabled |
| - Android: ProGuard/R8 |
| - Claves NUNCA hardcodeadas |
| - APIs keys en build secrets |
+----------------------------------+
M10 - EXTRANEOUS FUNCTIONALITY:
+----------------------------------+
| - No debug code en produccion |
| - No test endpoints en prod |
| - Logs sanitizados |
| - Build variants separados |
+----------------------------------+
+------------------------------------------------------------------+
10.3. Deteccion de Jailbreak/Root¶
JAILBREAK/ROOT DETECTION:
+------------------------------------------------------------------+
iOS - DETECCION DE JAILBREAK:
+----------------------------------+
| Checks: |
| 1. Existencia de Cydia/Sileo |
| 2. Paths sospechosos: |
| /Applications/Cydia.app |
| /bin/bash |
| /usr/sbin/sshd |
| 3. Sandbox escape test |
| 4. Fork() allowed (no en iOS) |
| 5. URL schemes de jailbreak |
+----------------------------------+
Android - DETECCION DE ROOT:
+----------------------------------+
| Checks: |
| 1. su binary present |
| 2. Superuser.apk installed |
| 3. Build tags (test-keys) |
| 4. System paths writable |
| 5. Magisk detection |
+----------------------------------+
COMPORTAMIENTO EN DETECCION:
+----------------------------------+
| Opcion 1 (Default): WARNING |
| - Mostrar advertencia |
| - Permitir continuar |
| - Log evento |
| |
| Opcion 2 (Strict): BLOCK |
| - Bloquear acceso a PHI |
| - Solo permitir lectura |
| - Requerir modo seguro |
| |
| Opcion 3 (Enterprise): DENY |
| - No permitir uso de app |
| - Para deployments corporativos |
+----------------------------------+
NOTA: Ningun check es 100% efectivo. Defense in depth es clave.
+------------------------------------------------------------------+
10.4. Anti-Tampering¶
ANTI-TAMPERING MEASURES:
+------------------------------------------------------------------+
INTEGRIDAD DE CODIGO:
+----------------------------------+
| iOS: |
| - Code signing obligatorio |
| - App Attest para validar |
| - Checksum de binarios criticos |
| |
| Android: |
| - APK signing (v2/v3) |
| - Play Integrity API |
| - SafetyNet Attestation |
+----------------------------------+
DETECCION DE DEBUGGING:
+----------------------------------+
| iOS: |
| - sysctl() para detectar debugger|
| - ptrace(PT_DENY_ATTACH) |
| |
| Android: |
| - Debug.isDebuggerConnected() |
| - /proc/self/status (TracerPid) |
+----------------------------------+
DETECCION DE INSTRUMENTACION:
+----------------------------------+
| Detectar: |
| - Frida |
| - Xposed Framework |
| - Substrate |
| - Objection |
| |
| Metodos: |
| - Memory scanning |
| - Process name checks |
| - Library injection detection |
+----------------------------------+
RESPUESTA A TAMPERING:
+----------------------------------+
| 1. Log evento (sin bloquear) |
| 2. Deshabilitar features PHI |
| 3. Notificar al usuario |
| 4. Reportar a servidor (metadata)|
| 5. NO crashear (evitar bypass) |
+----------------------------------+
+------------------------------------------------------------------+
11. Logging y Auditoria de Seguridad¶
11.1. Eventos de Seguridad¶
EVENTOS DE SEGURIDAD A LOGGEAR:
+------------------------------------------------------------------+
AUTENTICACION:
+----------------------------------+
| - Login exitoso |
| - Login fallido (sin password) |
| - Logout |
| - Session timeout |
| - Biometria fallida |
| - PIN incorrecto (count) |
| - Recovery Key usado |
+----------------------------------+
CRIPTOGRAFIA:
+----------------------------------+
| - Master key generada |
| - Key rotation ejecutada |
| - Descifrado fallido |
| - Integridad blob fallida |
+----------------------------------+
SEGURIDAD:
+----------------------------------+
| - Jailbreak/Root detectado |
| - Debugger detectado |
| - Tampering detectado |
| - Proxy/MITM detectado |
| - Certificate pinning fallo |
+----------------------------------+
DATOS:
+----------------------------------+
| - Exportacion de datos |
| - Compartir con cuidador |
| - Eliminacion de cuenta |
| - Backup creado |
+----------------------------------+
+------------------------------------------------------------------+
11.2. Sanitizacion de Logs¶
LOG SANITIZATION:
+------------------------------------------------------------------+
DATOS QUE NUNCA SE LOGGEAN:
+----------------------------------+
| - Passwords/PINs |
| - Claves criptograficas |
| - Recovery Keys |
| - Tokens de sesion completos |
| - Datos PHI (medicamentos, etc) |
| - Nombres de pacientes |
| - Emails completos |
| - Telefonos |
+----------------------------------+
DATOS QUE SE SANITIZAN:
+----------------------------------+
| Original | Logged |
|-----------------------|-----------|
| juan@ejemplo.com | j***@e*** |
| 5551234567 | 555***7 |
| Metformina 500mg | [REDACTED]|
| token_abc123xyz | tok_***z |
| uuid-550e8400-e29b... | uuid_***b |
+----------------------------------+
IMPLEMENTACION:
# Pseudocodigo - Sanitizacion de logs
def sanitize_for_log(value: str, field_type: str) -> str:
"""Sanitiza un valor para logging seguro."""
if field_type == "email":
local, domain = value.split('@')
return f"{local[0]}***@{domain[0]}***"
elif field_type == "phone":
return f"{value[:3]}***{value[-1]}"
elif field_type == "token":
return f"tok_***{value[-1]}"
elif field_type == "phi":
return "[REDACTED]"
else:
if len(value) > 4:
return f"{value[:2]}***{value[-1]}"
return "***"
11.3. Almacenamiento de Audit Trail¶
AUDIT TRAIL LOCAL:
+------------------------------------------------------------------+
ESTRUCTURA:
+----------------------------------+
| AuditEvent { |
| id: UUID |
| timestamp: ISO8601 |
| event_type: String |
| category: ENUM |
| details: Map<String, Any> |
| device_id: String (hashed) |
| session_id: String (partial) |
| } |
+----------------------------------+
RETENCION:
+----------------------------------+
| - Local: 30 dias |
| - Servidor (Pro/Perfect): 1 ano |
| - Rotacion automatica |
+----------------------------------+
SINCRONIZACION:
+----------------------------------+
| - Events se cifran E2E |
| - Servidor ve blobs opacos |
| - Solo usuario puede leer |
| su propio audit trail |
+----------------------------------+
EJEMPLO LOG ENTRY:
{
"id": "evt_550e8400",
"timestamp": "2025-12-07T10:30:00Z",
"event_type": "login_success",
"category": "authentication",
"details": {
"auth_method": "biometric",
"device_type": "iphone"
},
"device_id": "dev_7f8a9b***",
"session_id": "ses_***c3d"
}
+------------------------------------------------------------------+
12. Recuperacion de Desastres¶
12.1. Escenarios de Perdida de Acceso¶
ESCENARIOS DE PERDIDA:
+------------------------------------------------------------------+
ESCENARIO 1: Olvido de Password
+----------------------------------+
| Tiene Recovery Key? |
| SI -> Restaurar con RK |
| NO -> Datos perdidos |
+----------------------------------+
ESCENARIO 2: Dispositivo Perdido/Robado
+----------------------------------+
| Tiene otro dispositivo? |
| SI -> Sync desde servidor |
| NO -> Usar Recovery Key |
| en nuevo dispositivo |
+----------------------------------+
ESCENARIO 3: Recovery Key Perdida
+----------------------------------+
| Tiene acceso a dispositivo? |
| SI -> Generar nueva RK |
| NO -> Datos perdidos |
| (si tambien perdio pass)|
+----------------------------------+
ESCENARIO 4: Todo Perdido
+----------------------------------+
| Password + Recovery Key + Device |
| perdidos: |
| -> DATOS IRRECUPERABLES |
| -> Advertencia durante setup |
+----------------------------------+
+------------------------------------------------------------------+
12.2. Procedimientos de Recuperacion¶
flowchart TD
A[Usuario no puede acceder] --> B{Tiene dispositivo<br/>con sesion activa?}
B -->|Si| C[Abrir app en dispositivo]
C --> D[Generar nueva Recovery Key]
D --> E[Cambiar password si necesario]
E --> F[Acceso restaurado]
B -->|No| G{Tiene Recovery Key<br/>guardada?}
G -->|Si| H[Instalar app en nuevo device]
H --> I[Ingresar Recovery Key]
I --> J[Crear nuevo password]
J --> K[Sync desde servidor]
K --> F
G -->|No| L{Recuerda password?}
L -->|Si| M[Instalar app en nuevo device]
M --> N[Login con password]
N --> K
L -->|No| O[DATOS PERDIDOS<br/>Contactar soporte]
O --> P[Crear cuenta nueva]
12.3. Backup Cifrado¶
BACKUP STRATEGY:
+------------------------------------------------------------------+
BACKUP AUTOMATICO (Pro/Perfect):
+----------------------------------+
| - Sincronizacion continua |
| - Datos cifrados E2E |
| - Servidor almacena blobs |
| - Recuperable con Recovery Key |
+----------------------------------+
BACKUP MANUAL (Free/Pro/Perfect):
+----------------------------------+
| - Exportar archivo .medtime |
| - Cifrado con AES-256-GCM |
| - Clave derivada de password |
| - Usuario guarda archivo |
+----------------------------------+
FORMATO DE BACKUP:
+----------------------------------+
| { |
| "version": "1.0", |
| "created_at": "ISO8601", |
| "encryption": { |
| "algorithm": "AES-256-GCM", |
| "kdf": "argon2id", |
| "kdf_params": {...} |
| }, |
| "data": "base64_encrypted..." |
| } |
+----------------------------------+
RESTORE PROCESS:
1. Usuario proporciona archivo .medtime
2. Usuario ingresa password de backup
3. App deriva clave con Argon2id
4. App descifra contenido
5. App importa datos a DB local
6. App sincroniza con servidor (si aplica)
+------------------------------------------------------------------+
13. Cumplimiento Regulatorio¶
13.1. HIPAA Security Rule¶
HIPAA SECURITY RULE COMPLIANCE:
+------------------------------------------------------------------+
TECHNICAL SAFEGUARDS:
+----------------------------------+
| Requirement | MedTime |
|---------------------|------------|
| Access Control | Biometria + PIN, RBAC |
| Audit Controls | Seccion 10 |
| Integrity | AES-GCM tags |
| Transmission Security| TLS 1.3 + E2E |
| Encryption | AES-256-GCM |
+----------------------------------+
ADDRESSABLE SPECIFICATIONS:
+----------------------------------+
| Automatic Logoff | Session timeout |
| Unique User ID | UUID por usuario |
| Emergency Access | Recovery Key |
| Encryption at Rest | Keychain + E2E |
| Encryption in Transit| TLS 1.3 |
+----------------------------------+
SAFE HARBOR:
+----------------------------------+
| MedTime califica para "safe harbor" bajo HIPAA |
| porque datos PHI estan cifrados con AES-256. |
| En caso de brecha del servidor, datos son indescifrables. |
+----------------------------------+
+------------------------------------------------------------------+
13.2. LGPD (Brasil)¶
LGPD COMPLIANCE:
+------------------------------------------------------------------+
PRINCIPIOS (Art. 6):
+----------------------------------+
| Principio | MedTime |
|---------------------|------------|
| Finalidad | Solo gestion de medicacion |
| Adequacao | Datos minimos necesarios |
| Necessidade | Minimo privilegio |
| Livre Acesso | Usuario ve todos sus datos |
| Qualidade | Datos editables por usuario |
| Transparencia | Aviso de privacidad claro |
| Seguranca | Cifrado E2E |
| Prevencao | Security by design |
+----------------------------------+
DERECHOS DEL TITULAR (Art. 18):
+----------------------------------+
| Derecho | Implementacion |
|----------------------|----------------|
| Acceso | Export de datos |
| Correccion | Edicion en app |
| Eliminacion | Delete account |
| Portabilidad | Export JSON/PDF |
| Revocacion consent | Settings > Privacy |
+----------------------------------+
DATOS SENSIBLES (Art. 11):
+----------------------------------+
| Datos de salud requieren consentimiento explicito. |
| MedTime obtiene consentimiento durante onboarding. |
+----------------------------------+
+------------------------------------------------------------------+
13.3. FDA 21 CFR Part 11¶
FDA 21 CFR PART 11 COMPLIANCE:
+------------------------------------------------------------------+
ELECTRONIC RECORDS:
+----------------------------------+
| Requirement | MedTime |
|--------------------------|---------|
| System validation | Testing strategy |
| Accurate copies | Export function |
| Record retention | Audit trail |
| Limiting system access | Auth + RBAC |
| Operational system checks| Integrity verification |
| Device checks | Jailbreak detection |
| Authority checks | Permission system |
+----------------------------------+
ELECTRONIC SIGNATURES:
+----------------------------------+
| Requirement | MedTime |
|--------------------------|---------|
| Unique to individual | Biometria + UUID |
| Not reusable | One-time tokens |
| Administered by authorized| Admin controls |
| Independent verification | MFA required |
+----------------------------------+
AUDIT TRAIL:
+----------------------------------+
| All PHI modifications are logged with: |
| - Timestamp |
| - User ID |
| - Action type |
| - Previous value hash (not value itself - E2E) |
| - Device ID |
+----------------------------------+
NOTA: FDA 21 CFR Part 11 aplica si MedTime se usa para
clinical trials o como regulated medical device.
Para uso personal, es best practice pero no obligatorio.
+------------------------------------------------------------------+
14. Testing de Seguridad¶
14.1. Pruebas Requeridas¶
SECURITY TESTING MATRIX:
+------------------------------------------------------------------+
STATIC ANALYSIS:
+----------------------------------+
| Tool | Target |
|---------------|------------------|
| SonarQube | Code quality |
| Snyk | Dependencies |
| Semgrep | Security patterns|
| MobSF | Mobile security |
+----------------------------------+
DYNAMIC ANALYSIS:
+----------------------------------+
| Tool | Purpose |
|---------------|------------------|
| OWASP ZAP | API testing |
| Burp Suite | Penetration |
| Frida | Runtime analysis |
| Objection | Mobile testing |
+----------------------------------+
CRYPTOGRAPHIC TESTING:
+----------------------------------+
| Test | Pass Criteria |
|--------------------------|------------------------|
| Nonce uniqueness | 0 collisions in 10K |
| Key derivation time | > 100ms (brute-force) |
| Entropy quality | NIST SP 800-90B |
| Ciphertext randomness | NIST statistical tests |
| Padding consistency | Same size for same tier|
| Padding removal | 100% data integrity |
+----------------------------------+
PADDING UNIT TESTS (DV2-P3: SEC-BAJO-001):
+----------------------------------+
| Test Case | Platform | Required |
|---------------------------------|-----------|----------|
| padToBlock() añade padding | iOS | Si |
| padToBlock() multiplo de 1KB | iOS | Si |
| removePadding() recupera datos | iOS | Si |
| Padding PKCS#7-like valido | iOS | Si |
| padToBlock() añade padding | Android | Si |
| padToBlock() multiplo de 1KB | Android | Si |
| removePadding() recupera datos | Android | Si |
| Padding PKCS#7-like valido | Android | Si |
| Edge case: data size = 1KB | Ambos | Si |
| Edge case: data size = 0 bytes | Ambos | Si |
| Edge case: data size = 1KB - 1 | Ambos | Si |
| Roundtrip: pad -> unpad | Ambos | Si |
| Verificar metadata hiding | Ambos | Si |
+----------------------------------+
EJEMPLO DE UNIT TEST - iOS:
```swift
func testPaddingConsistency() {
let data1 = "Short".data(using: .utf8)!
let data2 = "Medium length data".data(using: .utf8)!
let padded1 = padToBlock(data1, blockSize: 1024)
let padded2 = padToBlock(data2, blockSize: 1024)
// Ambos deben tener el mismo tamaño (1KB)
XCTAssertEqual(padded1.count, 1024)
XCTAssertEqual(padded2.count, 1024)
}
func testPaddingRoundtrip() {
let original = "Test data for encryption".data(using: .utf8)!
let padded = padToBlock(original, blockSize: 1024)
let unpadded = removePadding(padded)
// Debe recuperar datos originales exactos
XCTAssertEqual(original, unpadded)
}
```text
EJEMPLO DE UNIT TEST - Android:
```kotlin
@Test
fun testPaddingConsistency() {
val data1 = "Short".toByteArray()
val data2 = "Medium length data".toByteArray()
val padded1 = padToBlock(data1, blockSize = 1024)
val padded2 = padToBlock(data2, blockSize = 1024)
// Ambos deben tener el mismo tamaño (1KB)
assertEquals(1024, padded1.size)
assertEquals(1024, padded2.size)
}
@Test
fun testPaddingRoundtrip() {
val original = "Test data for encryption".toByteArray()
val padded = padToBlock(original, blockSize = 1024)
val unpadded = removePadding(padded)
// Debe recuperar datos originales exactos
assertArrayEquals(original, unpadded)
}
```mermaid
+----------------------------------+
PENETRATION TESTING:
+----------------------------------+
| Frecuencia: Anual + cada major release |
| Scope: App mobile + APIs + Infraestructura |
| Proveedor: Tercero independiente |
+----------------------------------+
+------------------------------------------------------------------+
14.2. Checklist de Seguridad por Release¶
RELEASE SECURITY CHECKLIST:
+------------------------------------------------------------------+
PRE-RELEASE:
[ ] 1. Static analysis ejecutado (0 critical, 0 high)
[ ] 2. Dependency scan (0 known vulnerabilities)
[ ] 3. Code review de cambios crypto completado
[ ] 4. Unit tests de crypto passing (100%)
[ ] 5. Integration tests de auth passing
[ ] 6. No secrets hardcodeados
[ ] 7. Debug code removido
[ ] 8. Logs sanitizados (no PHI)
[ ] 9. Certificate pins actualizados
[ ] 10. Jailbreak/root detection funcional
POST-RELEASE:
[ ] 11. Smoke test en produccion
[ ] 12. Monitoreo de errores de crypto
[ ] 13. Monitoreo de auth failures
[ ] 14. Review de crash reports
[ ] 15. Verificar certificate pinning en red
QUARTERLY:
[ ] Penetration test (si major release)
[ ] Review de access controls
[ ] Rotacion de server secrets
[ ] Review de audit logs
ANNUAL:
[ ] Penetration test completo
[ ] Review de arquitectura de seguridad
[ ] Actualizacion de threat model
[ ] Renovacion de certificados
[ ] Rotacion de global_salt (blind index)
+------------------------------------------------------------------+
15. Referencias¶
15.1. Documentos MedTime¶
- INV-001: Cifrado E2E Zero Knowledge
- INV-008: Cifrado de Perfil e Identificacion
- 02-arquitectura-cliente-servidor.md
- MTS-AUTH-001: Autenticacion
- MTS-PRI-001: Privacidad
15.2. Estandares NIST¶
- NIST SP 800-132: Recommendation for Password-Based Key Derivation
- NIST SP 800-38D: Recommendation for GCM Mode
- NIST SP 800-63B: Digital Identity Guidelines
15.3. OWASP¶
15.4. Plataformas¶
15.5. Regulatorios¶
15.6. Algoritmos¶
Documento generado por SecurityDrone (MTS-DRN-SEC-001) + SpecQueen - IT-02 "Zero-Knowledge no es una feature, es una promesa. Tus datos seran asimilados... en un bunker criptografico que ni nosotros podemos abrir."