Hash files in the browser with web crypto

File integrity verification is crucial for secure data handling in web applications. The Web Crypto API provides built-in cryptographic operations that let you compute secure hash values directly in the browser. This post explores how to implement client-side file hashing to detect data tampering or corruption during file transfers.
Browser compatibility and requirements
The Web Crypto API is widely supported in modern browsers:
- Chrome 41+
- Firefox 34+
- Safari 7+
- Edge 79+
Important: The Web Crypto API requires a secure context (HTTPS) to function. When developing
locally, localhost
is considered secure by default.
Supported hash algorithms
The Web Crypto API supports several hash algorithms through the crypto.subtle.digest()
method:
- SHA-256 (recommended for general use)
- SHA-384 (stronger security)
- SHA-512 (strongest security)
- SHA-1 (not recommended due to known vulnerabilities)
Implementing file hashing
Here's a complete implementation that handles file hashing with proper error handling:
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)
}
})
}
The corresponding HTML structure:
<div class="hash-container">
<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>
<input type="file" id="fileInput" />
<div id="hashOutput"></div>
</div>
Handling large files
For large files, processing the entire content at once might cause memory issues. Here's an implementation that processes files in chunks:
async function calculateHashInChunks(file, algorithm = 'SHA-256') {
const chunkSize = 2097152 // 2MB chunks
const chunks = Math.ceil(file.size / chunkSize)
let position = 0
// Create a hash object
const crypto = window.crypto.subtle
let hashBuffer = await crypto.digest(algorithm, new ArrayBuffer(0))
for (let i = 0; i < chunks; i++) {
const chunk = file.slice(position, position + chunkSize)
const arrayBuffer = await chunk.arrayBuffer()
position += chunkSize
// Update hash with current chunk
hashBuffer = await crypto.digest(algorithm, arrayBuffer)
// Update progress if needed
const progress = Math.round(((i + 1) / chunks) * 100)
console.log(`Processing: ${progress}%`)
}
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
}
Verifying file integrity
To verify file integrity, compare the computed hash with a known good value:
function verifyFileIntegrity(computedHash, expectedHash) {
// Use constant-time comparison to prevent timing attacks
if (computedHash.length !== expectedHash.length) {
return false
}
return computedHash.toLowerCase() === expectedHash.toLowerCase()
}
// Example usage
const expectedHash = '123abc...'
const isValid = verifyFileIntegrity(computedHash, expectedHash)
Best practices
- Always use secure hash algorithms (SHA-256 or stronger)
- Implement proper error handling
- Show progress indicators for large files
- Use constant-time comparisons for hash verification
- Consider implementing rate limiting for multiple files
Client-side file hashing adds an important security layer to web applications, helping ensure data integrity during file transfers. While this implementation uses the Web Crypto API, production systems often employ additional security measures and server-side verification.
Happy coding!