Warning
Ravage actualmente está sufriendo grandes cambios constantemente. Debido a la falta de un release estable no podemos garantizar que este readme documente todo lo necesario para poder usar la ultima versión disponible. El proyecto actualmente apenas tiene documentacion e issues claros, para no retrasar el desarrollo, debido a que no hay participación externa.Si alguien quiere contribuir se actualizará todo Puede visitar https://deepwiki.com/MateoGonzalezLourido/Ravage/ , que genera un readme con ia del proyecto
RavageRavage es una aplicación de mensajería desktop diseñada como alternativa privada a las apps comerciales. En lugar de depender de servidores de terceros, cada instancia corre en tu propia infraestructura — basta con configurar un .env. Técnicamente, implementa cifrado extremo a extremo con un protocolo Sender Key Ratchet, transferencia de archivos cifrada en streaming con AES-256-GCM, autenticación multifactor con verificación por email, y un sistema de caché adaptativo que ajusta su estrategia según los recursos del sistema.
Ravage no está diseñada para competir con WhatsApp, Telegram o Signal. No tiene servidores globales, no incluye videollamadas ni llamadas de voz, y no está optimizada para un volumen muy alto de usuarios concurrentes.
Está pensada para un uso concreto: grupos pequeños o medianos que quieren comunicarse de forma privada sin depender de ninguna empresa. Funciona en producción — despliegas tu propio servidor y base de datos, y tienes una app de mensajería con E2EE real bajo tu control total.
Lo que implica este enfoque en la práctica:
- Infraestructura propia — necesitas desplegar tu propio servidor (local con express o uno propio) y base de datos. Tú controlas todo, pero también eres responsable de mantenerlo.
- No apta para gran escala — funciona bien para equipos pequeños; no está diseñada para miles de usuarios concurrentes.
- Sin llamadas — solo mensajes de texto y transferencia de archivos cifrada.
- Caché configurable — el sistema multinivel se ajusta automáticamente a los recursos disponibles, y puedes limitar manualmente el uso de RAM y disco.
Si buscas algo para millones de usuarios, Ravage no es la herramienta. Si buscas privacidad real sin intermediarios y no te importa gestionar tu propia infraestructura, sí lo es.
Instalación · Arquitectura · Cifrado · Servidor · Caché
- Características Principales
- Stack Tecnológico
- Instalación
- Configuración
- Ejecución
- Modos de Servidor
- Arquitectura
- Estructura de Archivos
- Sistema de Cifrado en Profundidad
- Sistema de Caché Multinivel
- Sistema de Notificaciones en Tiempo Real
- Monitorización Estructurada
- Seguridad Adicional
| Categoría | Detalle |
|---|---|
| Cifrado E2EE | Protocolo de Sender Key Ratchet con derivación HMAC-SHA256 y cifrado AES-256-GCM para mensajes y archivos |
| Distribución de claves | RSA-2048 con OAEP padding para intercambio seguro del ChainKey entre participantes |
| Cifrado de archivos en streaming | Pipeline ReadStream → CipherStream → GridFS sin cargar el archivo completo en memoria |
| Caché adaptativo | Sistema multinivel RAM/Disco con detección de recursos del sistema (CPU, RAM, disco) y ajuste automático de estrategia |
| Persistencia de caché | Minificación de objetos y serialización periódica cifrada a disco para recuperación rápida entre sesiones |
| Notificaciones en tiempo real | MongoDB Change Streams + Socket.IO para push instantáneo de mensajes y eventos |
| Servidor dual | Modo local (HTTP/HTTPS) para desarrollo y modo Railway (producción) con puerto dinámico y health checks |
| Autenticación multifactor | Login con verificación por código de 6 dígitos enviado por email (Brevo API), JWT con rotación, dispositivos de confianza |
| Datos at-rest cifrados | Toda la información sensible en MongoDB se almacena cifrada con AES-256-GCM (campos EncryptedData) |
| Almacenamiento local cifrado | Archivos de sesión, identidad y caché cifrados en disco con AES-256-GCM usando clave de sistema |
| Aislamiento de procesos | contextIsolation: true, sandbox: true, nodeIntegration: false — comunicación solo vía IPC tipado |
| Validación exhaustiva | Capa de validación server-side para correos, apodos, contraseñas, IDs, códigos y nombres de archivo |
| Logging Estructurado | Logs de alto rendimiento en JSON con Pino para monitorizar eventos E2EE, DB y Auth |
Frontend → Electron 41 · HTML5 · CSS3 · JavaScript ES Modules
Backend → Node.js 18+ · Express 5 · Mongoose 9
Base de datos → MongoDB Atlas (TLS) · GridFS (archivos)
Tiempo real → Socket.IO 4.x · MongoDB Change Streams
Criptografía → AES-256-GCM · RSA-2048 (OAEP) · HMAC-SHA256 · bcrypt (14 rondas) · SHA-256
Auth → JSON Web Tokens · Brevo API (email transaccional)
Sistema → systeminformation · node-machine-id
Monitor → pino · pino-pretty
| Requisito | Versión mínima |
|---|---|
| Node.js | 18+ (necesario para fetch nativo y ESM) |
| npm | 9+ |
| Git | Cualquiera |
| MongoDB | Atlas (cloud) o instancia local |
# 1. Clonar el repositorio
git clone https://github.com/MateoGonzalezLourido/Ravage.git
cd Ravage
# 2. Instalar dependencias
npm installNote
Electron se instala como devDependency. En conexiones lentas puede tardar unos minutos porque descarga los binarios del motor Chromium.
Crea un archivo .env en la raíz del proyecto (puedes usar .env.example como plantilla):
cp .env.example .env# ── Base de datos ──
URI_MONGODB=mongodb+srv://<usuario>:<password>@<cluster>.mongodb.net/<db>?appName=RAVAGE
# ── Autenticación ──
SECRET_KEY_JWT=<clave_hex_64_chars> # Firma de JSON Web Tokens
SECRET_KEY_COKKIE=<clave_hex_64_chars> # Cifrado de archivos locales (AES-256)
SALTOS_ENCRIPTAR_CONTRASENA=14 # Rondas de bcrypt
# ── Cifrado interno ──
INTERNAL_ENCRYPTION_KEY=<clave_hex_64_chars> # Cifrado at-rest de datos en MongoDB
# ── Servicio de email (Brevo) ──
BREVO_API_KEY=<tu_api_key>
BREVO_SENDER_EMAIL=<tu_email_verificado>Para generar claves hexadecimales seguras de 32 bytes (256 bits):
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Caution
Nunca subas el archivo .env a un repositorio público. Contiene claves criptográficas y credenciales de base de datos. El .gitignore del proyecto ya lo excluye.
npm start
# Equivale a: electron .Al arrancar, la aplicación ejecuta la siguiente secuencia:
- Inicia el servidor Express (localhost:3000) con Socket.IO
- Conecta a MongoDB Atlas con TLS habilitado
- Intenta auto-login leyendo el archivo de sesión cifrado local
- Crea la ventana principal de Electron con sandbox activado
npm run start-railway
# Equivale a: node backend/servidores/serverRailway.jsTip
Ver la sección Modos de Servidor para una explicación detallada de cada modo.
La aplicación soporta diferentes configuraciones de servidor según el entorno de ejecución. Los archivos de configuración se encuentran en backend/servidores/.
Archivo: serverLocalHost.js
Cliente Electron ←→ http://localhost:3000 ←→ MongoDB Atlas
↕
Socket.IO (ws://)
- Puerto fijo:
3000 - CORS restringido a
localhost:3000,localhost:8080yfile:// - Ideal para desarrollo y testing
- Arranque automático al lanzar Electron (
npm start)
El archivo serverLocalHost.js incluye una plantilla comentada para activar HTTPS con certificados propios:
# Generar certificados autofirmados para desarrollo
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodesPara activarlo, descomenta la sección HTTPS en serverLocalHost.js y apunta a tus archivos .pem. Todos los archivos de certificados (*.pem, *.key, *.crt) están excluidos por .gitignore.
Archivo: serverRailway.js
Clientes ←→ https://<tu-app>.up.railway.app ←→ MongoDB Atlas
↕
Socket.IO (wss://)
- Puerto dinámico vía
process.env.PORT(Railway lo inyecta automáticamente) - Escucha en
0.0.0.0para aceptar conexiones externas - CORS configurable vía
process.env.CLIENT_URL - Health check endpoint en
/— Railway lo usa para verificar que el servicio está vivo - Transportes Socket.IO:
websocket+polling(fallback para redes restrictivas) - Ejecución independiente de Electron: conecta a MongoDB por su cuenta
┌─────────────────────────────────────────────────────────────────────┐
│ ELECTRON (Proceso Main) │
│ │
│ main.js │
│ ├── Servidor Express + Socket.IO (serverLocalHost / serverRailway) │
│ ├── MongoDB (Mongoose TLS) │
│ ├── Auto-login / Sesión │
│ └── IPC Handlers (session, chat, social, validadores, cache) │
│ │
│ preload.cjs (contextBridge) │
│ ├── sesion_usuario → Login, registro, verificación │
│ ├── cuenta_usuario → Datos de perfil, cambios de cuenta │
│ ├── social_usuario → Contactos, bloqueados, búsqueda │
│ ├── chats → Mensajería, archivos, E2EE │
│ ├── buzonAPI → Notificaciones push en tiempo real │
│ ├── validadores → Validación de inputs │
│ ├── cache_persistente → Caché chats/usuarios (RAM + disco) │
│ └── ajustes_app → Configuración de la app │
│ │
├─────────────────────────────────────────────────────────────────────┤
│ ELECTRON (Proceso Renderer) │
│ │
│ frontend/sesion-log/ → Pantallas de login y registro │
│ frontend/home/ → Interfaz principal (chats, ajustes) │
│ frontend/soporte/ → Página de soporte │
│ │
│ Comunicación: window.sesion_usuario.LOGIN_USUARIO(...) │
│ (Solo APIs expuestas por contextBridge — sin acceso a Node.js) │
└─────────────────────────────────────────────────────────────────────┘
Renderer (UI) → preload.cjs (Bridge) → IPC Handlers → Repositories → Mongoose Models → MongoDB
↓
Services
(crypto, sesión, buzón,
archivos, validadores)
↓
STORAGE / CACHE
(variables de sesión en
memoria + caché multinivel)
| Capa | Responsabilidad |
|---|---|
| Renderer | Interfaz de usuario. Sin acceso a Node.js ni al backend |
| Preload | Puente contextBridge. Filtra y expone solo las APIs necesarias |
| IPC Handlers | Orquestan la lógica de cada dominio (sesión, chat, social, caché) |
| Services | Lógica de negocio: cifrado, sesión, buzón, archivos, validación |
| Repositories | Acceso a datos. Encapsulación de queries a Mongoose |
| Models | Esquemas Mongoose con tipos EncryptedData para cifrado at-rest |
| STORAGE | Variables de sesión en memoria y sistema de caché multinivel |
Ravage/
├── main.js # Entry point. Inicia servidor, DB, ventana Electron
├── preload.cjs # contextBridge — APIs expuestas al renderer
├── package.json # Dependencias y scripts
├── .env # Variables de entorno (no versionado)
│
├── backend/
│ ├── db/
│ │ └── mongo.js # Conexión a MongoDB Atlas con TLS
│ │
│ ├── models/
│ │ ├── User.js # Esquema usuario (campos cifrados: apodo, correo, idamigo)
│ │ ├── Chat.js # Esquema chat (ratchet_keys para E2EE)
│ │ ├── Message.js # Esquema mensaje (encriptado E2EE + copia sistema)
│ │ ├── Buzon.js # Esquema buzón (notificaciones cifradas)
│ │ └── Security.js # Códigos de validación, tokens JWT, dispositivos bloqueados
│ │
│ ├── repositories/
│ │ ├── UserRepository.js # CRUD de usuarios con descifrado automático
│ │ ├── ChatRepository.js # Gestión de chats, rotación de claves ratchet
│ │ ├── MessageRepository.js # Envío/descarga de mensajes E2EE + archivos GridFS
│ │ ├── BuzonRepository.js # Inserción de entradas en el buzón
│ │ └── SecurityRepository.js # Tokens JWT, códigos de verificación
│ │
│ ├── services/
│ │ ├── cryptoService.js # Core criptográfico: AES-256-GCM, RSA, Ratchet, hashing
│ │ ├── messageCryptoService.js # Descifrado de listas de mensajes con ratchet forward
│ │ ├── sesionUsuario.js # Login, registro, auto-login, verificación por email
│ │ ├── controladorArchivos.js # Lectura/escritura cifrada de archivos locales
│ │ ├── buzon.js # MongoDB Change Streams para notificaciones push
│ │ ├── validadores.js # Validación de correo, apodo, contraseña, etc.
│ │ ├── CreadorTokens.js # Generación y validación de JWT
│ │ └── MENSAJERIA/
│ │ ├── Servicio_mensajeria_correo.js # Envío de emails vía Brevo API
│ │ └── Estructuras_correos.js # Templates HTML de emails
│ │
│ ├── ipc/
│ │ ├── session_ipc.js # Handlers IPC de sesión (login, registro, logout)
│ │ ├── chat_ipc.js # Handlers IPC de chats (enviar, descargar, crear)
│ │ ├── social_ipc.js # Handlers IPC social (contactos, bloqueos)
│ │ ├── validadores_ipc.js # Handlers IPC de validación
│ │ ├── cache_persistent_ipc.js # Handlers IPC de caché persistente
│ │ ├── cache_img_extension_ipc.js # Handlers IPC caché de iconos
│ │ └── cache_archivos_descargados_ipc.js # Handlers IPC caché de descargas
│ │
│ ├── servidores/
│ │ ├── serverLocalHost.js # Servidor HTTP/HTTPS para desarrollo local
│ │ └── serverRailway.js # Servidor de producción para Railway
│ │
│ ├── STORAGE/
│ │ ├── Variables_sesion.js # Variables de sesión en memoria (getters/setters)
│ │ ├── ajustes_defecto.js # Configuración por defecto de la app
│ │ └── CACHE/
│ │ ├── _cache_usuarios.js # Caché de usuarios (RAM + disco, LFU + protección temporal)
│ │ ├── _cache_img_extensiones.js # Caché de iconos de extensiones de archivo
│ │ └── _cache_archivos_descargados.js # Caché de historial de descargas
│ │
│ └── utils/
│ ├── libs.js # Hub centralizado de imports (Node, Electron, npm)
│ ├── systemInfo.js # Detección de RAM/Disco y recomendación de estrategia de caché
│ └── conversores.js # Utilidades de conversión de tipos
│
└── frontend/
├── sesion-log/ # UI de login, registro y verificación
│ ├── sesion.html
│ ├── log.js
│ └── style.css
│
├── home/ # UI principal
│ ├── home.html # Layout principal de la app
│ ├── renderer.js # Lógica principal del renderer
│ ├── styles/ # Hojas de estilo
│ └── ui/
│ ├── chat.js # Renderizado de mensajes y chat
│ ├── ajustes.js # Panel de ajustes
│ ├── añadir_chats_usuarios.js # Crear chats, buscar usuarios
│ ├── historial_archivos_descargados.js # UI del historial de descargas
│ └── url_icono_extensiones_archivos.js # Iconos dinámicos por extensión
│
├── soporte/ # Página de soporte
├── notificaciones/ # Sistema de notificaciones
└── recursos/ # Assets estáticos (imágenes, iconos)
Ravage implementa un sistema de cifrado en múltiples capas que protege los datos tanto en tránsito como en reposo. A continuación se describe cada capa en detalle técnico.
┌─────────────────────────────────────────────────────────┐
│ CAPA 5: Transporte │
│ TLS (MongoDB Atlas) + WSS (Socket.IO en producción) │
├─────────────────────────────────────────────────────────┤
│ CAPA 4: Cifrado E2EE de mensajes │
│ AES-256-GCM con clave derivada por Sender Key Ratchet │
├─────────────────────────────────────────────────────────┤
│ CAPA 3: Cifrado de archivos en streaming │
│ AES-256-GCM en pipeline (ReadStream → Cipher → GridFS) │
├─────────────────────────────────────────────────────────┤
│ CAPA 2: Cifrado at-rest en MongoDB │
│ AES-256-GCM con INTERNAL_ENCRYPTION_KEY │
├─────────────────────────────────────────────────────────┤
│ CAPA 1: Cifrado de almacenamiento local │
│ AES-256-GCM con SECRET_KEY_COKKIE │
├─────────────────────────────────────────────────────────┤
│ CAPA 0: Hashing de credenciales │
│ bcrypt (14 rondas) para contraseñas │
│ SHA-256 para búsquedas deterministas │
└─────────────────────────────────────────────────────────┘
contraseña_usuario → bcrypt(contraseña, 14 rondas) → hash almacenado
- Algoritmo: bcrypt con 14 rondas de salt (
SALTOS_ENCRIPTAR_CONTRASENA=14) - Cada ronda duplica el coste computacional, haciendo ataques de fuerza bruta prohibitivos
- El salt se genera automáticamente y se incorpora en el hash resultante
- La verificación usa
bcrypt.compare()para timing-safe comparison
Para poder realizar búsquedas en la base de datos sobre campos cifrados (como el correo) sin exponerlos, se almacena un hash SHA-256 junto al dato cifrado:
correo_usuario → SHA-256(correo) → correo_hash (indexado en MongoDB)
Esto permite buscar por correo_hash sin necesidad de descifrar todos los registros. El campo correo en sí se almacena cifrado con AES-256-GCM (Capa 2).
Todos los archivos locales sensibles (sesión, identidad, caché, ajustes) se cifran antes de escribirlos a disco.
Archivo: controladorArchivos.js
Datos → JSON.stringify → AES-256-GCM(SECRET_KEY_COKKIE, IV_aleatorio) → {iv, tag, data} → archivo.json
| Componente | Detalle |
|---|---|
| Algoritmo | AES-256-GCM (autenticado) |
| Clave | SECRET_KEY_COKKIE (256 bits, del .env) |
| IV | 12 bytes generados con crypto.randomBytes() por cada escritura |
| Authentication Tag | 128 bits, verificado en cada lectura |
| Formato en disco | { iv: hex, tag: hex, data: hex } |
Archivos protegidos en la carpeta <appData>/.APP_DATA/:
| Archivo | Contenido |
|---|---|
sesionfile.json |
Correo + JWT de sesión |
identity.json |
Par de claves RSA (pública + privada) del usuario |
auto_login.json |
Token para omitir verificación futura |
dp_confi.json |
Token de dispositivo de confianza |
ajustes_app.json |
Configuración de la app (rutas de descarga, límites de caché) |
cache_archivos.json |
Historial de archivos descargados |
cache_chats_frec.json |
Chats frecuentes minificados |
cache_users_frec.json |
Usuarios frecuentes minificados |
Si el descifrado falla (clave incorrecta o archivo corrupto), el archivo se elimina automáticamente para evitar errores persistentes en cascada.
Todos los datos sensibles almacenados en MongoDB usan un esquema reutilizable EncryptedData:
// Esquema que se repite en User, Chat, Message, Buzon, Security
const EncryptedDataSchema = {
data: String, // Contenido cifrado (hex)
iv: String, // Vector de inicialización (hex)
tag: String // Authentication tag (hex)
}Archivo: cryptoService.js → funciones encriptarDatosSistema() / desencriptarDatosSistema()
| Componente | Detalle |
|---|---|
| Algoritmo | AES-256-GCM |
| Clave | INTERNAL_ENCRYPTION_KEY (256 bits, del .env) |
| IV | 12 bytes, crypto.randomBytes(12), único por campo |
Campos cifrados en cada modelo:
| Modelo | Campos cifrados |
|---|---|
| User | apodo, correo, idamigo, cada contacto (apodo), cada chat (ultimomensaje, nombre_bloqueo) |
| Message | contenido[].asunto, contenido[].archivos[].nombre |
| Chat | nombre |
| Buzon | entrada[].data |
| Security | correo, id_dp, data (en ValidationCode, Tokens, etc.) |
Los archivos se cifran en streaming durante la subida a GridFS, sin cargar el archivo completo en memoria:
fs.createReadStream(archivo)
│
▼
CipherStream (AES-256-GCM) ← MessageKey derivada del Ratchet
│ ← IV: crypto.randomBytes(12)
▼
GridFS.openUploadStream() → MongoDB (bucket "ArchivosChats")
│
▼
Auth Tag almacenado en el mensaje
La descarga invierte el pipeline:
GridFS.openDownloadStream(id)
│
▼
DecipherStream (AES-256-GCM) ← MessageKey re-derivada del Ratchet
│ ← IV y Tag del registro del mensaje
▼
fs.createWriteStream(destino)
| Aspecto | Detalle |
|---|---|
| Clave | MessageKey — la misma que cifra el contenido del mensaje (derivada del Ratchet) |
| Ventaja | Archivos de cualquier tamaño se procesan en chunks, sin pico de memoria |
| Nombre protegido | El nombre real del archivo se cifra con encriptarDatosSistema() y el nombre en GridFS es solo su ObjectId |
| Duplicados | Si ya existe un archivo con el mismo nombre en la carpeta de descargas, se renombra automáticamente: archivo (1).txt, archivo (2).txt, etc. |
Esta es la capa más compleja y la que protege la comunicación entre usuarios de punta a punta.
Cuando un usuario se registra, se genera un par de claves RSA-2048:
generarLlavesRSA()
↓
publicKey → Se sube a MongoDB (campo User.publicKey)
privateKey → Se guarda cifrada localmente (identity.json vía Capa 1)
- Algoritmo: RSA-2048 con codificación SPKI (pública) y PKCS#8 (privada) en formato PEM
- La clave privada nunca sale del dispositivo del usuario
- Si el usuario inicia sesión en otro dispositivo, puede regenerar su identidad (con la consecuencia de perder lectura de mensajes antiguos)
Al crear un nuevo chat, se genera un ChainKey simétrico único para cada emisor y se distribuye a todos los participantes usando RSA:
Para cada emisor E en el chat:
chainKey = crypto.randomBytes(32) # 256 bits de entropía
Para cada receptor R en el chat (incluido E):
clave_envuelta = RSA_OAEP_Encrypt(chainKey, R.publicKey)
Almacenar en chat.ratchet_keys: {
emisor_id: E._id,
receptor_id: R._id,
clave_envuelta: clave_envuelta, // ChainKey cifrada con RSA pública
counter: 0
}
| Componente | Detalle |
|---|---|
| Envoltura | RSA-OAEP con hash SHA-1 (implementado con crypto.publicEncrypt) |
| Por qué RSA | Permite que el emisor cifre el ChainKey para cada receptor sin necesidad de estar online simultáneamente |
ChainKey (CK_n)
│
├──── HMAC-SHA256(CK_n, 0x01) ──→ MessageKey ──→ AES-256-GCM(mensaje)
│
└──── HMAC-SHA256(CK_n, 0x02) ──→ CK_{n+1} ──→ Reemplaza CK_n
- El emisor lee su
clave_envueltapropia y la descifra con su clave privada RSA - Aplica HMAC-SHA256 con la constante
0x01para derivar elMessageKey - Aplica HMAC-SHA256 con la constante
0x02para derivar el siguienteChainKey - Cifra el payload completo del mensaje (asunto + archivos + emisor + fecha) con AES-256-GCM usando el
MessageKey - Almacena el
iterationcounter enratchet_infodel mensaje - Re-cifra el nuevo ChainKey con su propia clave pública RSA y actualiza en la DB
El receptor:
- Lee la
clave_envueltaque el emisor cifró para él y la descifra con su clave privada RSA - Si su
counterlocal es menor que laiterationdel mensaje, avanza el ratchet:while (counter < m.ratchet_info.iteration): CK = HMAC-SHA256(CK, 0x02) counter++ - Deriva el
MessageKeyconHMAC-SHA256(CK, 0x01)y descifra el mensaje - Avanza una posición más para el siguiente mensaje
- Persiste el nuevo estado del ratchet en la DB
Cuando el counter de un emisor supera 100 iteraciones, se dispara automáticamente una rotación completa:
- Se genera un nuevo
ChainKeyaleatorio - Se re-distribuye a todos los participantes usando RSA
- El
counterse reinicia a 0
Esto limita la ventana de exposición: incluso si un atacante comprometiera un ChainKey, solo podría leer como máximo 100 mensajes futuros.
El sistema implementa una cascada de recuperación de 3 niveles:
Intento 1: Descifrar con la clave actual
↓ (fallo)
Intento 2: Rotar claves del chat y reintentar
↓ (fallo)
Intento 3: Regenerar identidad completa (NUCLEAR) y rotar
↓ (fallo)
Mostrar "[Error al descifrar: posible clave de dispositivo obsoleta]"
EMISOR RECEPTOR
│ │
│ 1. RSA_Decrypt(clave_envuelta_propia) │
│ → ChainKey_n │
│ │
│ 2. MessageKey = HMAC(CK_n, 0x01) │
│ NextCK = HMAC(CK_n, 0x02) │
│ │
│ 3. E2EE_payload = AES-GCM(mensaje, MessageKey) │
│ Archivos: ReadStream → CipherStream(MK) → GridFS│
│ │
│ 4. RSA_Encrypt(NextCK, self.publicKey) │
│ → Actualizar ratchet_keys en DB │
│ │
│ 5. Guardar mensaje con ratchet_info.iteration = n │
│ Notificar vía Buzón + Socket.IO │
│──────────────────────────────────────────────────────▶│
│ │
│ 6. RSA_Decrypt(clave_envuelta_para_receptor)
│ → ChainKey_m │
│ │
│ 7. Ratchet forward: m → n │
│ CK = HMAC(CK, 0x02) (repetir) │
│ │
│ 8. MessageKey = HMAC(CK_n, 0x01) │
│ Descifrar payload AES-GCM │
│ │
│ 9. Persistir nuevo estado ratchet │
| Canal | Protección |
|---|---|
| MongoDB Atlas | TLS obligatorio (tls: true en la configuración de Mongoose) |
| Socket.IO (producción) | WSS cuando se despliega detrás de un proxy con TLS (Railway proporciona TLS automáticamente) |
| Socket.IO (local) | WS (sin cifrar), protegido por el hecho de que es localhost |
| API Brevo | HTTPS para envío de emails |
Ravage implementa un sistema de caché inteligente que adapta su estrategia según los recursos disponibles del sistema.
Archivo: systemInfo.js
Al iniciar, el sistema consulta RAM total y disco libre usando systeminformation:
| RAM del sistema | Estrategia recomendada | Tamaño de caché |
|---|---|---|
| ≥ 32 GB | RAM | 4096 MB |
| ≥ 16 GB | RAM | 2048 MB |
| ≥ 8 GB | RAM | 1024 MB |
| < 8 GB + disco > 50 GB | Disco | 2048 MB |
| < 8 GB + disco < 50 GB | RAM (mínima) | 256 MB |
┌─────────────────────────────┐
│ Map() — Caché en RAM │
CACHE HIT ───────│ (acceso < 1ms) │
│ Limitada por recursos │
└──────────┬──────────────────┘
│ miss
▼
┌─────────────────────────────┐
│ Disco — Caché persistente │
CACHE HIT ───────│ (archivo .json cifrado) │
│ Datos minificados │
└──────────┬──────────────────┘
│ miss
▼
┌─────────────────────────────┐
│ MongoDB Atlas — Fuente │
FETCH + CACHE ───│ (query remota + descifrado) │
│ Resultado guardado en caché │
└─────────────────────────────┘
Cuando la caché excede su límite en MB, se aplica:
- Ordenación por frecuencia (Least Frequently Used) — los elementos menos accedidos se evalúan primero
- Desempate por antigüedad — entre elementos con la misma frecuencia, se evicta el que no se ha usado hace más tiempo
- Protección temporal — los elementos accedidos en los últimos 5 minutos están protegidos y se saltan en la evicción (a menos que sea necesario por falta total de espacio)
Antes de serializar a disco, los objetos se minifican para reducir el tamaño del archivo JSON:
// Objeto completo en RAM (~200 bytes)
{ _id: "abc", nombre: "Chat", usuarios: [...], _frequency: 5, _last_used: 1711700000000 }
// Objeto minificado en disco (~80 bytes)
{ i: "abc", n: "Chat", u: [...], f: 5, t: 1711700000000 }La escritura a disco se ejecuta con un debounce de 10-15 segundos para evitar escrituras excesivas:
setChatEnCache() → _gestionar_persistencia_frecuentes() → setTimeout(10000ms) → saveCacheChatsFile()
| Caché | Almacena | Persistencia | Configurable |
|---|---|---|---|
| Chats | Datos de chats con ratchet_keys | ✅ Disco (cifrado) | Límite RAM/Disco + forzar disco |
| Usuarios | Perfiles descifrados (apodo, publicKey) | ✅ Disco (cifrado) | Límite RAM/Disco + forzar disco |
| Iconos de extensiones | URLs de iconos SVG por extensión de archivo | ✅ En memoria | Límite de entradas |
| Archivos descargados | Historial de descargas (nombre, ruta, fecha) | ✅ Disco (cifrado) | Límite de entradas |
MongoDB (Colección "buzon")
│
▼
Change Stream (watch) ─── filtra por userId ──→ Socket.IO room
│ │
▼ ▼
mainWindow.webContents.send() io.to(userId).emit()
│ │
▼ ▼
Renderer (frontend) Otros clientes (futuro)
- Usa MongoDB Change Streams con
fullDocument: "updateLookup"para observar cambios en la colecciónbuzon - Filtra por usuario propio para no procesar notificaciones ajenas
- Cierre graceful: al cerrar la app, se ejecuta
detenerBuzon()que cierra el Change Stream antes de desconectar MongoDB
Para garantizar la fiabilidad y observabilidad de los procesos críticos (E2EE, Auth, DB), el sistema integra herramientas profesionales de registro y pruebas.
- Desarrollo: Salida legible y coloreada con
pino-pretty. - Producción: Logs JSON estructurados listos para ser ingeridos por plataformas de monitorización (Grafana, Datadog).
Suite de pruebas exhaustivas para el núcleo de seguridad y validación:
- Criptografía: Verificación de AES-GCM, RSA, SHA-256 y protocolos de Ratchet.
- Validadores: Pruebas de seguridad para inputs, correos y prevención de inyección de rutas.
# Ejecutar la suite de pruebas
npm run test// main.js — Configuración de la ventana
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
nodeIntegration: false, // El renderer NO tiene acceso a Node.js
contextIsolation: true, // Contextos separados (previene prototype pollution)
sandbox: true // Sandboxing del proceso renderer
}- Se identifica cada dispositivo con
node-machine-id(ID de hardware único) - Los dispositivos pueden ser bloqueados permanentemente para una cuenta
- En cada login y auto-login se verifica el blacklist
Registro/Login → Código de 6 dígitos → Email vía Brevo API → Hash SHA-256 del código en MongoDB
↓
5 intentos máximo
10 min de expiración (TTL index)
| Token | Duración | Propósito |
|---|---|---|
| Sesión | 7 días | Mantener sesión iniciada |
| Verificación de cuenta | 90 minutos | Omitir verificación por código en logins futuros |
| Dispositivo de confianza | Permanente | Saltar verificación en dispositivos conocidos |
Los tokens se almacenan como hash SHA-256 en MongoDB y en formato JWT cifrado en disco local. En logout, se eliminan tanto del servidor como del cliente.
| Dato | Cooldown |
|---|---|
| Apodo | 1 hora desde la creación |
| Correo | 72 horas desde la creación |
| Contraseña | 24 horas desde la creación |
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) app.quit();Solo se permite una instancia de la aplicación por máquina. Si el usuario intenta abrir otra, la ventana existente se enfoca.
Desarrollado por Mateo González Lourido