You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: Docs/Services/CLAVES_SISTEMA.md
+28-17Lines changed: 28 additions & 17 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,8 +12,8 @@ Ravage maneja varios tipos de claves criptográficas con propósitos distintos.
12
12
|`SECRET_KEY_COKKIE`| Variable de entorno (hex) |`Buffer`|`.env.secret`| Cifra archivos locales de sesión y configuración de la app |
13
13
|`SECRET_KEY_PRIVATE`| Variable de entorno (hex) |`Buffer`|`.env.secret`| Cifra el archivo de identidad E2EE (clave privada RSA) en disco |
14
14
|`INTERNAL_ENCRYPTION_KEY`| Variable de entorno (hex) |`Buffer`|`.env.secret`| Cifra datos sensibles almacenados en MongoDB (buzón, etc.) |
15
-
| Chain key (ratchet E2EE) |`randomBytes(32)` al crear chat | Hex string | Hex string (RSA-wrapped en `ratchet_keys`) | Clave raíz del ratchet de mensajes por chat |
16
-
| Clave privada RSA| Generada con `generateKeyPairSync`| PEM string | Cifrada con `SECRET_KEY_PRIVATE` en `identityFile`| Descifra chain keys y mensajes E2EE |
15
+
| Chain key (ratchet E2EE) |`randomBytes(32)` al crear chat | Hex string | Hex string (X25519-wrapped en `ratchet_keys`) | Clave raíz del ratchet de mensajes por chat |
16
+
| Clave privada X25519| Generada con `generateKeyPairSync('x25519')`| PEM string | Cifrada con `SECRET_KEY_PRIVATE` en `identityFile`| Descifra chain keys E2EE (ECDH)|
17
17
18
18
---
19
19
@@ -83,10 +83,7 @@ Las chain keys implementan un ratchet de tipo Sender Key, similar al protocolo S
83
83
84
84
### Formato y convención
85
85
86
-
Las chain keys se almacenan y transportan siempre como **cadenas hexadecimales de 64 caracteres** (representación de 32 bytes). Este convenio es intencional:
87
-
88
-
-`descifrarConPrivada()` en `cryptoService.js` devuelve `.toString('utf8')`. Una cadena hex sobrevive sin corrupción a este round-trip (todos sus caracteres son ASCII imprimibles).
89
-
- Si se almacenaran como bytes binarios, el `.toString('utf8')` los corrompería al encontrar secuencias de bytes no válidas UTF-8.
86
+
Las chain keys se almacenan y transportan siempre como **cadenas hexadecimales de 64 caracteres** (representación de 32 bytes, todos caracteres ASCII). Esta convención es segura frente al round-trip de AES-GCM porque UTF-8 no corrompe ASCII.
90
87
91
88
### Flujo de la chain key
92
89
@@ -95,31 +92,45 @@ Las chain keys se almacenan y transportan siempre como **cadenas hexadecimales d
ratchet_keys[].clave_envuelta ← almacenada cifrada en MongoDB por par emisor→receptor
98
+
ratchet_keys[].clave_envuelta ← subdocumento { ephPub, iv, data, tag } en MongoDB
102
99
103
100
[Enviar / recibir mensaje]
104
-
descifrarConPrivada(clave_envuelta) ← devuelve la chain key como hex string
101
+
descifrarConX25519(clave_envuelta, privateKey) ← devuelve la chain key como hex string
105
102
│
106
103
▼
107
104
ratchetChainKey(chainKeyHex)
108
105
├─ messageKey = HMAC-SHA256(Buffer.from(hex, 'hex'), 0x01) → Buffer (clave AES para este mensaje)
109
106
└─ nextChainKey = HMAC-SHA256(Buffer.from(hex, 'hex'), 0x02) → hex string (siguiente estado del ratchet)
110
107
```
111
108
112
-
El `nextChainKey` vuelve a ser hex para mantener el contrato y poder pasarse a la siguiente iteración de `ratchetChainKey`.
109
+
### Por qué X25519 en lugar de RSA-OAEP
110
+
111
+
Con RSA-OAEP, comprometer una clave privada permite descifrar **todos** los `clave_envuelta` pasados almacenados en la DB. Con X25519 efímero, cada `clave_envuelta` se cifró con una clave efímera de un solo uso: comprometer la clave privada de identidad no permite descifrar mensajes anteriores (**forward secrecy** en la distribución de chain keys).
112
+
113
+
### Cómo funciona el cifrado X25519
114
+
115
+
Para cada par (emisor, receptor) al crear o rotar claves de chat:
116
+
117
+
1. Se genera un par X25519 **efímero** (vive solo durante la operación).
118
+
2.`ECDH(ephPriv, recipientPub)` → 32 bytes de secreto compartido.
119
+
3.`HKDF-SHA256(sharedSecret, info='ravage-ck-wrap')` → 32 bytes de wrapping key.
120
+
4.`AES-256-GCM(chainKeyHex, wrappingKey)` → `{ iv, data, tag }`.
121
+
5. Se almacena `{ ephPub (raw hex 32B), iv, data, tag }` en `ratchet_keys[].clave_envuelta`.
122
+
123
+
Para descifrar: receptor hace `ECDH(privKey, ephPub)` → misma wrapping key → descifra con AES-GCM.
113
124
114
125
### Archivos relevantes
115
126
116
127
| Archivo | Rol |
117
128
|---|---|
118
-
|`ChatRepository.js`| Genera la chain key inicial y la cifra con RSA pública de cada miembro |
Copy file name to clipboardExpand all lines: Docs/Services/seguridad/LOGIN_SESION.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@ Este documento describe el flujo completo de autenticación en Ravage: registro,
13
13
3. Se comprueba en DB que el correo no esté ya registrado (`correo_hash`).
14
14
4. Si pasa, en background se ejecutan en paralelo:
15
15
-**Hash de contraseña** con Argon2id (ver `Docs/Services/seguridad/HASHING_CONTRASENAS.md`).
16
-
-**Generación de par de claves RSA** para cifrado E2EE.
16
+
-**Generación de par de claves X25519** para cifrado E2EE.
17
17
5. Se genera un código de verificación numérico aleatorio y se guarda en la colección `validationcodes` cifrado, vinculado al correo y al dispositivo.
18
18
6. Se envía el código por correo. El usuario tiene **10 minutos** y **5 intentos** para introducirlo.
19
19
@@ -154,7 +154,7 @@ autoLoginUsuario()
154
154
Tras cualquier login exitoso (manual o autologin) se verifica que existe la clave privada RSA local.
155
155
156
156
- Si existe → sin acción.
157
-
- Si no existe (p.ej. primer login en un dispositivo nuevo, o archivo corrupto) → se regeneran las claves RSA automáticamente, se actualiza la clave pública en DB y se guarda la privada en el archivo local.
157
+
- Si no existe (p.ej. primer login en un dispositivo nuevo, o archivo corrupto) → se regeneran las claves X25519 automáticamente, se actualiza la clave pública en DB y se guarda la privada en el archivo local.
158
158
159
159
> **Atención**: regenerar la identidad rompe el descifrado de mensajes anteriores en todos los chats, ya que fueron cifrados con la clave pública antigua. Es un escenario de último recurso.
160
160
@@ -197,5 +197,5 @@ Todos los archivos se guardan cifrados en el directorio de datos de la app (fuer
197
197
|`sessionFile`|`{ username, token }`| Login con `mantenerSesion`| Cierre de sesión / token inválido |
198
198
|`omitirVerificacionCuentaFile`|`{ username, token }`| Tras validar código de login | Token expirado / inválido |
199
199
|`dispositivoConfianza`|`{ username, token }`| Al marcar como de confianza | Al revocar confianza |
200
-
|`identityFile`| Claves RSA privada + pública | Registro / regeneración | Nunca (persiste entre sesiones) |
200
+
|`identityFile`| Claves X25519 privada + pública | Registro / regeneración | Nunca (persiste entre sesiones) |
201
201
|`securityPin`|`{ correo, pinHash }`| Al configurar el PIN | Al borrar el PIN |
0 commit comments