services/api/src/utils/encryption.js
Overview
Este archivo define utilidades criptográficas para proteger datos sensibles en el backend. Su función principal es cifrar, descifrar y derivar identificadores deterministas a partir de valores como retentionContact, email o telefono.
El módulo trabaja con env.retentionEncryptionKey desde services/api/src/config/env.js y lanza ApiError desde services/api/src/utils/apiError.js cuando detecta formatos inválidos o fallos de descifrado. Usa crypto de Node.js como base criptográfica.
[!IMPORTANT]
encryptFieldydecryptFieldtratan las entradas vacías como caso especial y devuelven''sin lanzar error.
How it works
flowchart TB
EnvKey[env retentionEncryptionKey] --> GetKey[getKey]
GetKey --> Encrypt[encryptField]
GetKey --> Decrypt[decryptField]
GetKey --> Digest[derivePurgedIdentifierDigest]
Encrypt --> Iv[Random IV]
Encrypt --> Tag[Auth tag]
Encrypt --> Ciphertext[Ciphertext hex]
Iv --> Packed[Packed value]
Tag --> Packed
Ciphertext --> Packed
Decrypt --> Parts[Split packed value]
Parts -->|3 parts| Valid[Decrypt and validate]
Parts -->|invalid format| FormatError[ApiError 500]
Valid --> Plaintext[Plaintext utf8]
Valid -->|auth fail| DecryptError[ApiError 500]
Digest --> Hmac[HMAC SHA 256]
Hmac --> HexDigest[Hex digest]
El flujo parte de una clave hexagonal definida en env.retentionEncryptionKey. Esa clave se convierte en un Buffer y se usa tanto para cifrado AES-256-GCM como para HMAC-SHA256. encryptField empaqueta el resultado en un formato de tres segmentos separados por :. decryptField espera exactamente ese formato y valida la autenticidad antes de devolver texto plano.
Key components
{
"title": "getKey",
"content": "Funci\u00f3n interna que convierte `env.retentionEncryptionKey` desde hexadecimal a `Buffer`. Sirve como fuente com\u00fan de clave para cifrado, descifrado y digest determinista.",
"type": "function",
"filePath": "services/api/src/utils/encryption.js",
"badges": []
}
{
"title": "encryptField",
"content": "Funci\u00f3n p\u00fablica que cifra un valor sensible con `aes-256-gcm`. Devuelve una cadena en formato `ivHex:authTagHex:ciphertextHex` o `''` si la entrada est\u00e1 vac\u00eda.",
"type": "function",
"filePath": "services/api/src/utils/encryption.js",
"badges": [
"public"
]
}
{
"title": "decryptField",
"content": "Funci\u00f3n p\u00fablica que invierte el valor producido por `encryptField`. Devuelve `''` para entradas vac\u00edas y lanza `ApiError` si el formato es inv\u00e1lido o la autenticidad falla.",
"type": "function",
"filePath": "services/api/src/utils/encryption.js",
"badges": [
"public"
]
}
{
"title": "derivePurgedIdentifierDigest",
"content": "Funci\u00f3n p\u00fablica que genera un digest determinista de un solo sentido con HMAC-SHA256. Se usa para reemplazar identificadores como `email` y `telefono` durante la purga definitiva.",
"type": "function",
"filePath": "services/api/src/utils/encryption.js",
"badges": [
"public"
]
}
Data & fields
Este módulo no define clases ni estructuras persistentes. Su estado relevante se reduce a constantes internas y al formato de los valores cifrados.
[
{
"name": "env.retentionEncryptionKey",
"type": "string",
"required": true,
"description": "Clave criptográfica en hexadecimal. `getKey()` la transforma en `Buffer` y la reutiliza para cifrado, descifrado y HMAC."
},
{
"name": "ALGORITHM",
"type": "string",
"required": true,
"description": "Algoritmo fijo definido como `aes-256-gcm`."
},
{
"name": "IV_LENGTH",
"type": "number",
"required": true,
"description": "Longitud fija del vector de inicialización. El valor es `12` bytes."
}
]
encryptField produce una cadena con tres partes separadas por dos puntos.
[
{
"name": "packedValue",
"type": "string",
"required": true,
"description": "Cadena devuelta por `encryptField`."
},
{
"name": "ivHex",
"type": "string",
"required": true,
"description": "Vector de inicialización codificado en hexadecimal."
},
{
"name": "authTagHex",
"type": "string",
"required": true,
"description": "Etiqueta de autenticación generada por GCM y codificada en hexadecimal."
},
{
"name": "ciphertextHex",
"type": "string",
"required": true,
"description": "Texto cifrado codificado en hexadecimal."
}
]
El orden es importante. decryptField divide el valor por : y exige exactamente tres segmentos.
derivePurgedIdentifierDigest(userId) usa userId como entrada y devuelve un digest hexadecimal. El comentario del archivo indica que este valor se usa en la purga definitiva del día 60 para sustituir email y telefono.
[
{
"name": "userId",
"type": "string",
"required": true,
"description": "Identificador del usuario que alimenta el HMAC."
},
{
"name": "digest",
"type": "string",
"required": true,
"description": "Salida hexadecimal del HMAC-SHA256."
}
]
Behaviors
getKey— Convierteenv.retentionEncryptionKeydesde hexadecimal aBuffery la devuelve.encryptField— Cifra un valor conaes-256-gcmy devuelve el paquete hexadecimal.decryptField— Valida y descifra el paquete generado porencryptField, o lanzaApiError.derivePurgedIdentifierDigest— Genera un digest HMAC-SHA256 determinista a partir deuserId.
How the encryption path behaves
encryptField crea un IV aleatorio de 12 bytes con crypto.randomBytes. Después construye un cipher con crypto.createCipheriv, cifra el texto en UTF-8 y obtiene la auth tag. El resultado final concatena iv, authTag y ciphertext, todos en hexadecimal.
decryptField primero valida el formato. Si la cadena no tiene tres partes, lanza ApiError con el mensaje retentionContact tiene un formato invalido. Si el formato es correcto, reconstruye el decipher, aplica la auth tag y devuelve el texto plano. Si algo falla, lanza ApiError con code: 'DECRYPTION_FAILED'.
derivePurgedIdentifierDigest no cifra datos. En su lugar, genera un digest de un solo sentido con crypto.createHmac('sha256', getKey()). Esto produce una salida determinista para el mismo userId.
[!NOTE] El mismo secreto
env.retentionEncryptionKeyalimenta tanto el cifrado AES como el HMAC.
Gotchas & edge cases
[!WARNING]
decryptFieldno devuelve un valor parcial ni silencioso cuando el paquete está manipulado. Si la auth tag no valida, lanzaApiError.
[!WARNING] Si el valor de entrada es vacío,
encryptFieldydecryptFielddevuelven''sin error. Ese comportamiento permite tratar el campo como opcional.
[!CAUTION]
decryptFieldexige exactamente tres segmentos separados por:. Cualquier otro formato produce un error explícito.
[!IMPORTANT] El archivo asume que
env.retentionEncryptionKeyya está disponible y en formato hexadecimal.getKey()no hace validación adicional en este módulo.
Related
Este archivo se integra con componentes concretos del backend y no introduce dependencias externas adicionales.
services/api/src/config/env.js— Proporcionaenv.retentionEncryptionKey.services/api/src/utils/apiError.js— DefineApiError, usado para errores de formato y descifrado.cryptode Node.js — AportarandomBytes,createCipheriv,createDecipherivycreateHmac.
[!NOTE] El archivo solo contiene utilidades criptográficas. No expone endpoints HTTP ni lógica de controladores.