javascript

Encriptación con JavaScript del lado del cliente usando la Web Crypto API

En este post de programación con JavaScript en el navegador veremos cómo encriptar y desencriptar datos usando una API nativa, segura y confiable.

Encriptar y desencriptar información con JavaScript usando AES

Vamos a usar la interfaz Crypto a través de window.crypto. Al final podremos encriptar y desencriptar archivos usando una contraseña, derivando una clave de la misma y usando AES para el cifrado de datos.

Nota: voy a usar cifrado y encriptado como sinónimos para referirme a la encriptación de información.

Por cierto, usaremos la encriptación simétrica aunque también es posible usar la asimétrica con claves públicas y privadas.

Demostración

Aquí te dejo una demostración que puedes usar para cifrar y descifrar información ya lista para producción: https://parzibyte.github.io/ejemplos-javascript/encriptacion-js/

Siempre puedes ver el código fuente para que veas que es totalmente del lado del cliente y seguro.

Una vez que hayas cifrado la información ya puedes guardarla en cualquier lugar, y podrás confiar en que nadie la podrá desencriptar aunque la tengan.

Explicación

Esto no es un post donde te explico los métodos de encriptación, lo que es un vector de inicialización, sal o una clave derivada de una contraseña. Si quieres aprender más de eso puedes ir a investigarlo y después volver al post.

Lo que vamos a hacer es generar una clave a partir de una contraseña del usuario (derivación de clave basada en contraseña o PBKDF2), y cada vez que encriptemos la información vamos a usar esa clave y una sal aleatoria.

Al momento de generar la información encriptada vamos a generar un vector de inicialización aleatorio (iv) y más tarde vamos a almacenar el vector y la sal junto con la información cifrada para que se puedan recuperar al momento de desencriptar.

Generando clave criptográfica a partir de contraseña

Al momento de encriptar y desencriptar con JavaScript vamos a necesitar la clave, y esa clave se generará desde la contraseña.

const derivacionDeClaveBasadaEnContraseña = async (contraseña, sal, iteraciones, longitud, hash, algoritmo = 'AES-CBC') => {
  const encoder = new TextEncoder();
  let keyMaterial = await window.crypto.subtle.importKey(
    'raw',
    encoder.encode(contraseña),
    { name: 'PBKDF2' },
    false,
    ['deriveKey']
  );
  return await window.crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: encoder.encode(sal),
      iterations: iteraciones,
      hash
    },
    keyMaterial,
    { name: algoritmo, length: longitud },
    false,
    ['encrypt', 'decrypt']
  );
}

Con esta función obtenemos la key para cifrar y descifrar la información. En este caso el algoritmo usado por defecto es AES-CBC.

Encriptando información

Ahora veamos como cifrar con AES usando JavaScript. Hasta este punto ya tendremos la clave, entonces creamos una función que recibe la contraseña y el texto plano a cifrar.

const encriptar = async (contraseña, textoPlano) => {
  const encoder = new TextEncoder();
  const sal = window.crypto.getRandomValues(new Uint8Array(16));
  const vectorInicializacion = window.crypto.getRandomValues(new Uint8Array(16));
  const bufferTextoPlano = encoder.encode(textoPlano);
  const clave = await derivacionDeClaveBasadaEnContraseña(contraseña, sal, 100000, 256, 'SHA-256');
  const encrypted = await window.crypto.subtle.encrypt(
    { name: "AES-CBC", iv: vectorInicializacion },
    clave,
    bufferTextoPlano
  );
  return bufferABase64([
    ...sal,
    ...vectorInicializacion,
    ...new Uint8Array(encrypted)
  ]);
};

Primero creamos la clave en la línea 6, con una sal aleatoria obtenida con getRandomValues que es una función criptográficamente segura; hacemos lo mismo con el vector de inicialización.

Después invocamos a crypto.subtle.encrypt  pasándole el algoritmo, la clave derivada de la contraseña y un búfer a partir del texto plano.

Finalmente regresamos ese búfer convertido a base64, ya que recuerda que al final la encriptación no distingue texto o archivos, simplemente ve una colección de bits.

Convertimos a base64 porque es una forma sencilla de codificar la información de un búfer; y ya esta cadena podemos almacenarla en cualquier lugar o transportarla.

Codificando y decodificando en Base64

Estas funciones son opcionales. Tú podrías almacenar el búfer de la manera que prefieras (como un blob, tomando los enteros y separándolos por coma, etcétera).

Solo recuerda que al momento de desencriptar debes recuperar el búfer original.

Por ello es que existen estas funciones que permiten codificar y decodificar el búfer. Nota que aquí no estamos haciendo nada de encriptación o cosas de seguridad, simplemente convertimos el búfer a una cadena amigable.

const bufferABase64 = buffer => btoa(String.fromCharCode(...new Uint8Array(buffer)));
const base64ABuffer = buffer => Uint8Array.from(atob(buffer), c => c.charCodeAt(0));

Desencriptar con JavaScript

Llegados a este punto veremos la función para desencriptar, que igualmente necesita la contraseña (luego vamos a sacar la clave derivada) y la cadena en base64.

Te repito que no es necesario el paso de la base64 pero es una manera fácil de guardar y mostrar cadenas.

const desencriptar = async (contraseña, encriptadoEnBase64) => {
  const decoder = new TextDecoder();
  const datosEncriptados = base64ABuffer(encriptadoEnBase64);
  const sal = datosEncriptados.slice(0, LONGITUD_SAL);
  const vectorInicializacion = datosEncriptados.slice(0 + LONGITUD_SAL, LONGITUD_SAL + LONGITUD_VECTOR_INICIALIZACION);
  const clave = await derivacionDeClaveBasadaEnContraseña(contraseña, sal, 100000, 256, 'SHA-256');
  const datosDesencriptadosComoBuffer = await window.crypto.subtle.decrypt(
    { name: "AES-CBC", iv: vectorInicializacion },
    clave,
    datosEncriptados.slice(LONGITUD_SAL + LONGITUD_VECTOR_INICIALIZACION)
  );
  return decoder.decode(datosDesencriptadosComoBuffer);
}

Nota: recuerda que la sal y el iv están en la información, por ello es que los recuperamos en la línea 4 y 5. La verdadera información está después de esos dos parámetros.

Poniendo todo junto

Entonces básicamente podemos cifrar y descifrar información con JavaScript de una manera sencilla para el usuario. Lo único que necesitamos es…

  • Para cifrar necesitamos la contraseña como string y la información que vamos a proteger como string.
  • Para descifrar necesitamos la contraseña como string y la información cifrada como string en base64.

El código fuente de la demostración está aquí.

Conclusión

Asegurar la información confidencial es importante. Con esta API moderna y segura de JavaScript podemos encriptar información usando AES, y después guardarla en el navegador, en Firebase, en un servidor, etcétera.

Podemos estar seguros de que nadie más podrá acceder a la información descifrada al menos que cuenten con la contraseña.

Se me ocurre que podrías usar esto para cosas confidenciales como un gestor de contraseñas, un diario personal, notas seguras, etcétera.

A ver si después hago algunos posts usando esta API. Mientras tanto basta con la demostración que ya dejé al inicio del post.

Por aquí te dejo más tutoriales de JavaScript en mi blog.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • Hola, gracias por tu post, tengo una duda; teniendo en cuenta que el js se ejecuta del lado de cliente que mecanismos de seguridad se pueden implementar para que una persona no replique el mismo código y finalemente poder desencriptar el mensaje. Gracias.

  • Una duda que pasa si esta web se cae, todos los mensajes que cifre ya no podria decifrarlos ?
    y el codigo JS de ahi funciona tal cual como el de la pagina para cifrar y desifrar ?

    como sea esta Exeelente tu contenido crack ! gracias

Entradas recientes

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

3 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

3 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

3 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

3 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

3 días hace

Solución: Apache – Server unable to read htaccess file

Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…

3 días hace

Esta web usa cookies.