Editor de notas con encriptación extremo a extremo construido con Web APIs nativas del navegador. Sin dependencias externas. Sin servidor. Sin base de datos remota.
Este proyecto está hecho para explicar el post Web APIs de nueva generación en JavaScript: más allá del localStorage | femCoders Club.
Proyecto de la serie JavaScript Avanzado de FemCoders Club.
- Encripta cada nota con AES-GCM-256 antes de guardarla — el texto plano nunca toca IndexedDB
- Deriva la clave a partir de tu contraseña usando PBKDF2 (310.000 iteraciones, recomendación OWASP 2023)
- Persiste las notas encriptadas en IndexedDB — funcionan offline, sin servidor
- Exporta e importa notas como ficheros
.encusando File System Access API - La contraseña vive solo en memoria — se borra al cerrar o recargar la página
| API | Para qué |
|---|---|
crypto.subtle (Web Crypto API) |
Derivación de clave PBKDF2 + encriptación AES-GCM |
indexedDB |
Persistencia de notas encriptadas |
showSaveFilePicker / showOpenFilePicker |
Export/import de ficheros .enc |
encrypted-private-notes-js/
├── src/
│ ├── crypto.js # Web Crypto API: deriva clave, encripta, desencripta
│ ├── storage.js # IndexedDB: CRUD de notas encriptadas
│ ├── fileSystem.js # File System Access API: export/import .enc
│ └── app.js # Orquestador: sesión, notas, export/import
├── index.html # UI funcional lista para usar y publicar en GitHub Pages
├── tests/
│ └── crypto.test.js # 16 tests con assert nativo de Node.js
└── package.json
Así se ve la app en acción:
Pantalla de bloqueo: Ingresa tu contraseña maestra para desbloquear tus notas. La contraseña nunca se almacena — solo sirve para derivar la clave de encriptación.
Editor: Una vez desbloqueado, edita, crea, importa o exporta notas encriptadas. Todo el contenido se encripta con AES-GCM-256 antes de guardarse en IndexedDB.
App en navegador
npm run startAbre http://localhost:3000 en Chrome o Edge (Firefox tiene soporte parcial de File System Access API). También puedes abrir index.html directamente, pero para probar File System Access API es mejor servirlo por HTTP.
El proyecto ya está preparado para publicarse desde la raíz del repositorio. El HTML principal carga ./src/app.js, así que puedes desplegar la rama main o usar la carpeta raíz como origen de Pages sin necesidad de una carpeta demo.
Tests
node tests/crypto.test.jsRequiere Node.js 19+. La Web Crypto API está disponible como global desde esa versión.
contraseña + salt aleatorio
↓
PBKDF2 (SHA-256, 310.000 iteraciones)
↓
CryptoKey AES-GCM-256
↓
encrypt(texto + IV aleatorio)
↓
{ salt, iv, ciphertext } → IndexedDB
Cada nota tiene su propio salt e IV generados aleatoriamente. Esto significa que dos notas encriptadas con la misma contraseña producen ciphertexts completamente distintos — no hay patrones que filtren información.
{
"version": 1,
"salt": "base64...",
"iv": "base64...",
"ciphertext": "base64...",
"title": "Mi nota",
"exportedAt": "2025-01-15T10:30:00.000Z"
}El fichero no contiene el texto plano. Para leerlo hace falta la contraseña original.
¿Por qué no guardar la contraseña en sessionStorage? sessionStorage es texto plano accesible desde JavaScript. Guardarla ahí anula la encriptación. La contraseña vive en una variable de módulo en memoria — se pierde al recargar, que es exactamente el comportamiento correcto.
¿Por qué PBKDF2 y no bcrypt/argon2? Web Crypto API no implementa bcrypt ni argon2. PBKDF2 con 310.000 iteraciones (recomendación OWASP 2023 para SHA-256) es la opción segura disponible de forma nativa en el navegador.
¿Por qué un IV nuevo por cada encriptación?
Reutilizar el mismo IV con la misma clave en AES-GCM es una vulnerabilidad crítica. Cada llamada a encryptNote() genera un IV aleatorio de 12 bytes.
FemCoders Club · femcodersclub.com
