Lección 15 de 1512 min
Identidad, criptografía y hardware
Credential Management, WebAuthn y Web Crypto para la seguridad; Web Serial, WebUSB, Web Bluetooth y Web MIDI para hablar con dispositivos.
Inicias sesión en tu banco con la huella, sin teclear contraseña. Una web cifra tus notas antes de guardarlas, de forma que ni siquiera la empresa puede leerlas. Conectas un Arduino por USB y una página muestra en directo la temperatura que mide. Hace unos años todo eso exigía instalar un programa; hoy lo hace el navegador.
Esta lección cubre dos familias de APIs con mucho en común: las de identidad y criptografía, que protegen al usuario, y las de hardware, que dejan a una web hablar con dispositivos. Ambas tocan cosas delicadas, así que el navegador las rodea de protecciones que conviene entender.
Identidad y criptografía
Las contraseñas tienen dos problemas: la gente las repite y las teclea en webs falsas (el phishing). Estas APIs permiten iniciar sesión sin contraseña y hacer criptografía seria en el navegador, sin librerías. El funcionamiento completo de la autenticación lo verás en su propio módulo.
Credential Management API
Una interfaz común para pedir, crear y guardar credenciales en el navegador.
- Objeto:
- navigator.credentials
- Métodos:
- get, create, store
El objeto navigator.credentials reúne en un solo sitio los distintos tipos de credencial: get() pide al navegador una credencial guardada, create() crea una nueva y store() la guarda. El tipo depende de lo que pidas: publicKey para passkeys (WebAuthn), password para contraseñas o identity para iniciar sesión con un proveedor externo.
El parámetro mediation controla cuánto interviene el usuario: desde iniciar sesión sin preguntar ("silent") hasta mostrar siempre el selector de cuentas ("required"). Y preventSilentAccess() impide que el navegador vuelva a entrar solo tras cerrar sesión.
Úsalo cuando
Siempre que uses passkeys, porque WebAuthn funciona a través de ella, y para mejorar el inicio de sesión con credenciales guardadas o al cerrar sesión.
Ten en cuenta
navigator.credentials existe en todos los navegadores, pero no todos los tipos: las contraseñas (PasswordCredential) solo funcionan en navegadores basados en Chromium. Detecta cada tipo antes de usarlo y deja que el gestor de contraseñas haga su trabajo con un formulario bien etiquetado.
// Al cerrar sesión: que el navegador no vuelva a entrar solo
await navigator.credentials.preventSilentAccess();
// Guardar una contraseña tras el registro (solo Chromium)
if ("PasswordCredential" in window) {
await navigator.credentials.store(new PasswordCredential(formulario));
}
// Iniciar sesión con una passkey ya guardada
const credencial = await navigator.credentials.get({
publicKey: { challenge: retoDelServidor },
mediation: "optional",
});Web Authentication (WebAuthn) y passkeys
Inicio de sesión con criptografía de clave pública, sin contraseñas y a prueba de phishing.
- Estándar:
- W3C y FIDO Alliance
- Soporte:
- Todos los navegadores
Al registrarte, tu dispositivo genera un par de claves: la privada se queda en el dispositivo (o en tu gestor de contraseñas) y nunca sale de ahí; la pública se envía al servidor. Para iniciar sesión, el servidor manda un reto aleatorio, el dispositivo lo firma con la clave privada tras comprobar tu huella, tu cara o tu PIN, y el servidor verifica la firma con la clave pública.
Una passkey es una credencial WebAuthn que se puede sincronizar entre tus dispositivos, por ejemplo con el llavero de Apple o el gestor de contraseñas de Google. Es inmune al phishing porque cada credencial queda ligada al dominio donde se creó: en una copia falsa como rnitienda.com (con «rn» en lugar de «m»), el navegador ni siquiera la ofrece.
Úsalo cuando
Como forma principal de iniciar sesión o como segundo factor. Los navegadores y sistemas principales ya admiten passkeys.
Ten en cuenta
La parte del servidor (generar retos, verificar firmas, guardar claves) es delicada: usa una librería probada en lugar de programarla tú. Y ofrece una forma de recuperar la cuenta si el usuario pierde todos sus dispositivos.
const credencial = await navigator.credentials.create({
publicKey: {
challenge: retoDelServidor, // bytes aleatorios generados por el servidor
rp: { name: "Mi Tienda", id: "mitienda.com" },
user: { id: idUsuario, name: "ana@correo.com", displayName: "Ana" },
pubKeyCredParams: [{ type: "public-key", alg: -7 }], // ES256
authenticatorSelection: { residentKey: "required", userVerification: "preferred" },
},
});
// Envía la respuesta al servidor: guardará la clave pública, nunca la privada
await enviarAlServidor(credencial);Web Crypto API
Criptografía nativa y rápida en el navegador: hashes, cifrado, firmas y claves.
- Requiere:
- HTTPS
- Objeto:
- crypto.subtle
crypto.subtle ofrece las operaciones criptográficas clásicas implementadas por el propio navegador: digest() calcula un hash (una huella de tamaño fijo, como SHA-256), encrypt() y decrypt() cifran con algoritmos como AES-GCM, sign() y verify() firman, y generateKey() o deriveKey() crean claves. Todo es asíncrono y trabaja con bytes, no con texto.
Además, crypto.getRandomValues() genera números aleatorios seguros y crypto.randomUUID() crea identificadores únicos. Nunca uses Math.random() para nada que tenga que ver con seguridad.
Úsalo cuando
Para cifrar datos en el navegador antes de enviarlos (cifrado de extremo a extremo), verificar la integridad de un archivo, firmar mensajes o generar identificadores y tokens aleatorios.
Ten en cuenta
crypto.subtle solo existe en contextos seguros (HTTPS o localhost). Es una API de bajo nivel: elegir mal un parámetro, como reutilizar el vector de inicialización en AES-GCM, rompe la seguridad. Y para guardar contraseñas no sirve un SHA-256: se usan algoritmos lentos como Argon2 o bcrypt, en el servidor.
// Hash SHA-256 de un texto, en hexadecimal
const bytes = new TextEncoder().encode("hola");
const hash = await crypto.subtle.digest("SHA-256", bytes);
const hex = [...new Uint8Array(hash)]
.map((b) => b.toString(16).padStart(2, "0"))
.join(""); // "b221d9db…"
// Cifrar con AES-GCM: un vector de inicialización nuevo cada vez
const clave = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const cifrado = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv }, clave, new TextEncoder().encode("Mi nota secreta"));No inventes tu propia criptografía
Usa algoritmos estándar a través de la Web Crypto API o de una librería auditada. Un esquema «casero», por ingenioso que parezca, casi siempre tiene un fallo que un atacante sabrá encontrar.
Interactivo
Laboratorio de hashes SHA-256
Escribe un texto: tu navegador calcula su SHA-256 en directo con la Web Crypto API y lo compara con el hash anterior.
Calculando el hash…
Pulsa «Cambiar un solo bit» o edita el texto: verás en amarillo qué caracteres del hash cambian respecto al anterior.
Hablar con dispositivos
Estas cuatro APIs permiten que una web se comunique con hardware conectado al equipo. Tienen las mismas reglas: solo funcionan con HTTPS, solo se activan tras un gesto del usuario (un clic) y es el usuario quien elige el dispositivo en un selector del navegador: la página nunca ve la lista completa de lo que tienes conectado.
Web Serial API
Lee y escribe en un puerto serie: placas Arduino, ESP32, impresoras 3D.
- Soporte:
- Chromium
- Requiere:
- HTTPS y un gesto
Muchos dispositivos se comunican por un puerto serie: un canal sencillo por el que viajan bytes uno tras otro, casi siempre a través de un cable USB. navigator.serial.requestPort() muestra el selector, port.open({ baudRate }) abre la conexión a la velocidad acordada y port.readable y port.writable son streams para leer y escribir.
Es lo que permite programar una placa de desarrollo o configurar un dispositivo desde una web, sin instalar ningún programa.
Úsalo cuando
Para paneles que leen datos de microcontroladores, instaladores de firmware en el navegador o herramientas para impresoras 3D y equipos de laboratorio.
Ten en cuenta
Solo está disponible en navegadores basados en Chromium, sobre todo en escritorio; Firefox y Safari no la ofrecen de forma general. Las dos partes deben usar la misma velocidad (baudRate) o solo leerás caracteres sin sentido.
botonConectar.addEventListener("click", async () => {
const puerto = await navigator.serial.requestPort(); // el usuario elige
await puerto.open({ baudRate: 9600 });
const lector = puerto.readable
.pipeThrough(new TextDecoderStream())
.getReader();
while (true) {
const { value, done } = await lector.read();
if (done) break;
mostrarTemperatura(value); // lo que envía Serial.println() en el Arduino
}
});WebUSB API
Habla directamente con un dispositivo USB que no tiene driver en el sistema.
- Soporte:
- Chromium
- Requiere:
- HTTPS y un gesto
WebUSB va un nivel más abajo que Web Serial: te comunicas con el dispositivo USB enviando y recibiendo transferencias por sus endpoints, los canales que define el propio dispositivo. Con navigator.usb.requestDevice({ filters }) el usuario elige el dispositivo, y después lo abres, reclamas una interfaz y transfieres datos con transferIn() y transferOut().
Está pensada para dispositivos con un protocolo propio, de forma que el fabricante pueda ofrecer una web en lugar de un driver y un programa de escritorio.
Úsalo cuando
Para configurar o actualizar el firmware de dispositivos propios, o para hablar con hardware especializado cuyo protocolo USB conoces.
Ten en cuenta
Solo funciona en navegadores basados en Chromium: Firefox y Safari han declinado implementarla por motivos de seguridad y privacidad. Además, el navegador bloquea las clases de dispositivo que ya gestiona el sistema, como teclados, ratones, memorias USB, cámaras o audio.
const dispositivo = await navigator.usb.requestDevice({
filters: [{ vendorId: 0x2341 }], // dispositivos de Arduino
});
await dispositivo.open();
await dispositivo.selectConfiguration(1);
await dispositivo.claimInterface(2); // los números dependen del dispositivo
await dispositivo.transferOut(4, new TextEncoder().encode("LED=1"));
const respuesta = await dispositivo.transferIn(5, 64);
console.log(new TextDecoder().decode(respuesta.data));Web Bluetooth API
Conecta con dispositivos Bluetooth de bajo consumo: pulsómetros, sensores, bombillas.
- Soporte:
- Chromium
- Protocolo:
- Bluetooth LE (GATT)
Los dispositivos Bluetooth Low Energy (BLE) organizan su información en servicios y características (el modelo GATT): un pulsómetro ofrece el servicio heart_rate con una característica que contiene las pulsaciones. Con navigator.bluetooth.requestDevice() el usuario elige el dispositivo, te conectas con gatt.connect() y lees, escribes o te suscribes a cambios.
Úsalo cuando
Para apps de deporte o salud que leen sensores, configurar dispositivos domésticos inteligentes o controlar juguetes y robots educativos.
Ten en cuenta
Solo habla con Bluetooth de bajo consumo, no con el Bluetooth clásico de unos auriculares. Funciona en navegadores basados en Chromium; ni Firefox ni Safari la ofrecen, tampoco en iPhone.
const dispositivo = await navigator.bluetooth.requestDevice({
filters: [{ services: ["heart_rate"] }],
});
const servidor = await dispositivo.gatt.connect();
const servicio = await servidor.getPrimaryService("heart_rate");
const medida = await servicio.getCharacteristic("heart_rate_measurement");
medida.addEventListener("characteristicvaluechanged", (e) => {
const pulso = e.target.value.getUint8(1); // formato de 8 bits
console.log(`Pulso: ${pulso} ppm`);
});
await medida.startNotifications();Web MIDI API
Recibe y envía mensajes MIDI a teclados, controladores y sintetizadores.
- Soporte:
- Chromium y Firefox
- Mensajes:
- Unos pocos bytes
MIDI es el idioma de los instrumentos electrónicos desde los años 80: no transmite sonido, sino órdenes cortas como «nota 60 pulsada con fuerza 100». navigator.requestMIDIAccess() te da las entradas y salidas conectadas; escuchas midimessage en una entrada para saber qué tecla se pulsa y usas send() en una salida para hacer sonar un instrumento.
Combinada con la Web Audio API, permite crear un sintetizador completo en el navegador que se toca con un teclado real.
Úsalo cuando
Para pianos y sintetizadores virtuales, apps de aprendizaje musical, editores de configuración de instrumentos o controlar luces y software de música.
Ten en cuenta
Es la excepción del grupo: además de Chromium, Firefox también la ofrece, aunque con un paso de permiso extra; Safari no. Los mensajes de sistema exclusivo (SysEx), que pueden modificar el firmware de un instrumento, exigen un permiso aparte.
const midi = await navigator.requestMIDIAccess();
for (const entrada of midi.inputs.values()) {
entrada.onmidimessage = ({ data }) => {
const [estado, nota, fuerza] = data;
if ((estado & 0xf0) === 0x90 && fuerza > 0) tocarNota(nota, fuerza);
};
}
// Hacer sonar un do central (nota 60) en el primer instrumento
const salida = [...midi.outputs.values()][0];
salida?.send([0x90, 60, 100]);Mejora progresiva
Comprueba siempre si la API existe ("serial" in navigator) antes de mostrar el botón «Conectar». Si no está, explica al usuario qué navegador necesita, en lugar de mostrar un botón que no hace nada.
Interactivo
Cuatro formas de hablar con hardware
Activa o desactiva opciones para compararlas cara a cara.
| Característica | Web Serial | WebUSB | Web Bluetooth |
|---|---|---|---|
| Conexión | Puerto serie, normalmente por USB | USB directo, sin driver del sistema | Bluetooth de bajo consumo (BLE) |
| Punto de entrada | navigator.serial.requestPort() | navigator.usb.requestDevice() | navigator.bluetooth.requestDevice() |
| Cómo te comunicas | Un flujo de bytes (streams) | Transferencias por endpoints | Servicios y características GATT |
| Soporte | Chromium, sobre todo escritorio | Chromium | Chromium |
| Ejemplo típico | Arduino, ESP32, impresoras 3D | Actualizar el firmware de un dispositivo propio | Pulsómetros, sensores, bombillas |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
Tu banco quiere un inicio de sesión sin contraseña que no se pueda robar con una web falsa que imite la suya.
Para recordar
- WebAuthn y las passkeys sustituyen las contraseñas por un par de claves: el servidor solo guarda la pública y la credencial no funciona en webs falsas.
navigator.credentialses la puerta común; el tipo de credencial que pides decide qué ocurre.- La Web Crypto API ofrece criptografía estándar en el navegador, solo en contextos seguros; nunca inventes la tuya ni uses
Math.random()para seguridad. - Web Serial, WebUSB y Web Bluetooth funcionan sobre todo en Chromium, y Web MIDI además en Firefox: detecta siempre la API y ofrece una alternativa.
- Todas las APIs de hardware exigen HTTPS, un gesto del usuario y que sea él quien elija el dispositivo.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.¿Por qué una passkey protege contra el phishing?
2.Pruebas tu web en
http://192.168.1.20:3000desde el móvil ycrypto.subtleesundefined. ¿Por qué?3.Tu web llama a
navigator.serial.requestPort()nada más cargar y no aparece nada. ¿Qué falla?4.Cambias una sola letra de un texto y vuelves a calcular su SHA-256. ¿Qué esperas?
5.¿Es buena idea guardar las contraseñas de tus usuarios como su SHA-256?