Hashear archivos en el navegador con Web Crypto
La verificación de la integridad de los archivos es fundamental para manejar datos de forma segura en aplicaciones web. La Web Crypto API ofrece operaciones criptográficas integradas que te permiten calcular valores hash seguros directamente en el navegador. Este artículo explora cómo implementar el hashing de archivos en el lado del cliente para detectar manipulaciones o corrupción de datos durante las transferencias de archivos.
Compatibilidad con navegadores y requisitos
Este ejemplo necesita tanto crypto.subtle como File.prototype.arrayBuffer, disponibles en los
navegadores modernos. Comprueba estas funciones antes de habilitar la interfaz de hashing; el
soporte de Web Crypto por sí solo no garantiza el soporte de la API de lectura de archivos que se
usa aquí.
La Web Crypto API requiere un contexto seguro (HTTPS) para funcionar. Cuando desarrollas
en local, localhost se considera seguro de forma predeterminada.
Algoritmos de hash compatibles
La Web Crypto API admite varios algoritmos de hash mediante el método crypto.subtle.digest():
- SHA-256 (recomendado para uso general)
- SHA-384 (resumen de 384 bits)
- SHA-512 (resumen de 512 bits)
- SHA-1 (no recomendado por vulnerabilidades conocidas)
Implementar el hashing de archivos
Esta es una implementación completa que realiza el hashing de archivos con un manejo de errores adecuado:
async function calculateHash(file, algorithm = 'SHA-256') {
if (!(file instanceof File)) {
throw new Error('Input must be a File object')
}
try {
const arrayBuffer = await file.arrayBuffer()
const hashBuffer = await crypto.subtle.digest(algorithm, arrayBuffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
} catch (error) {
if (error instanceof DOMException) {
throw new Error(`Unsupported hash algorithm: ${algorithm}`)
}
throw new Error('Failed to calculate file hash')
}
}
function setupFileHashing() {
const fileInput = document.getElementById('fileInput')
const hashOutput = document.getElementById('hashOutput')
const algorithmSelect = document.getElementById('algorithmSelect')
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0]
if (!file) return
const algorithm = algorithmSelect.value
hashOutput.textContent = 'Computing hash...'
try {
const hash = await calculateHash(file, algorithm)
hashOutput.textContent = `${algorithm}: ${hash}`
} catch (error) {
hashOutput.textContent = `Error: ${error.message}`
console.error('Hashing error:', error)
}
})
}
La estructura HTML correspondiente:
<div class="hash-container">
<label for="algorithmSelect">Hash algorithm</label>
<select id="algorithmSelect">
<option value="SHA-256">SHA-256</option>
<option value="SHA-384">SHA-384</option>
<option value="SHA-512">SHA-512</option>
</select>
<label for="fileInput">Choose a file to hash</label>
<input type="file" id="fileInput" />
<div id="hashOutput" role="status"></div>
</div>
Llama a setupFileHashing() después de que este HTML se haya cargado, por ejemplo desde un script diferido.
Manejar archivos grandes
crypto.subtle.digest() no admite entrada en streaming.
El archivo completo debe caber en memoria. Hashear fragmentos por separado de forma independiente no
produce el hash del archivo completo; conservar solo el último resultado verifica únicamente el
último fragmento.
Para los archivos que superan el presupuesto de memoria de tu aplicación, usa una biblioteca de hashing incremental con mantenimiento activo o calcula el hash de un stream en el servidor. Un Web Worker puede mantener el trabajo de hashing fuera del hilo principal, pero mover este ejemplo a un worker no elimina su requisito de memoria para el archivo completo.
Verificar la integridad de los archivos
Para verificar la integridad de un archivo, compara el hash calculado con un valor esperado obtenido de una fuente confiable. Un atacante que pueda reemplazar tanto el archivo como la suma de verificación publicada puede hacer que coincidan.
function verifyFileIntegrity(computedHash, expectedHash) {
// These are public checksums, not secret authentication values.
if (computedHash.length !== expectedHash.length) {
return false
}
return computedHash.toLowerCase() === expectedHash.toLowerCase()
}
// Pass the selected File and a trusted SHA-256 checksum to this helper.
async function verifySelectedFile(file, expectedHash) {
const computedHash = await calculateHash(file, 'SHA-256')
return verifyFileIntegrity(computedHash, expectedHash)
}
Mejores prácticas
- Usa SHA-256, SHA-384 o SHA-512 en lugar de SHA-1 para las nuevas comprobaciones de integridad
- Implementa un manejo de errores adecuado
- Muestra un indicador de actividad indeterminado mientras Web Crypto calcula el resumen
- Obtén la suma de verificación esperada a través de un canal confiable
- Considera implementar una limitación de tasa para varios archivos
El hashing de archivos en el lado del cliente añade una capa de seguridad importante a las aplicaciones web y ayuda a garantizar la integridad de los datos durante las transferencias de archivos. Aunque esta implementación usa la Web Crypto API, los sistemas en producción suelen emplear medidas de seguridad adicionales y verificación en el lado del servidor.
¡Feliz programación!
