Javascript is required
How it worksPublished 2026-07-28Updated 2026-08-085 min read

Web Crypto SHA-256: Large-File Memory Limits

Front-end upload and deduplication flows often need a file fingerprint. Native crypto.subtle.digest supports SHA-256 but has no .update() method: the complete input must be in memory before the call. Blob.slice() alone cannot turn it into streaming hashing. This guide separates a size-bounded native path from the incremental Worker architecture required for genuinely large files.

By: Tiny
Web Crypto APILarge File HashAsyncPerformance

1. Problem: peak memory and interaction risks during file hashing

Calling file.arrayBuffer() on a multi-gigabyte file allocates another contiguous buffer roughly as large as the file and may fail, trigger process termination, or create severe memory pressure. The fact that subtle.digest() returns a Promise does not eliminate CPU and resource contention, so explicit size policy and device testing are still required.

2. Minimal reproduction: indiscriminate file loading and Web Crypto limits

This snippet illustrates common pitfalls when attempting full file reads and confusing SubtleCrypto.digest algorithm support:

/* Defect 1: attempting MD5 with SubtleCrypto -> throws DOMException (Algorithm Not Supported) */
// await window.crypto.subtle.digest("MD5", data); // SubtleCrypto does NOT support MD5!

/* Defect 2: loading a 2GB file entirely into ArrayBuffer -> OOM browser crash! */
// const buffer = await file.arrayBuffer(); // Dangerous!

3. Root cause: SubtleCrypto algorithm and input-model limits

1. Algorithm limits: subtle.digest() supports SHA-1, SHA-256, SHA-384, and SHA-512; MD5 is omitted. 2. No incremental API: one call accepts a complete BufferSource and cannot update one digest state chunk by chunk. 3. Memory boundary: the file buffer, browser-internal processing, and the rest of the page contribute to peak memory. No fixed per-browser threshold is guaranteed by the Web Crypto specification.

4. Recommendation: choose the implementation by file size

1. Small and medium files: enforce a configurable product limit, then use file.arrayBuffer() and crypto.subtle.digest('SHA-256', buffer). 2. Genuinely large files: use a vetted incremental SHA-256 JS/Wasm implementation inside a Web Worker, read chunks sequentially, and update one hash state. Never hash chunks independently and concatenate their digests. 3. Progress and cancellation: the main thread should receive only progress and the final digest and must be able to terminate the Worker. A Worker isolates CPU work but does not remove memory requirements.

5. Complete code: size-bounded native Web Crypto SHA-256

This code states the native API boundary honestly: it accepts files only below an explicit limit and reads the full input once. 64 MiB is an example product policy, not a browser guarantee; calibrate it across supported devices. Route larger files to an incremental Worker implementation.

const DEFAULT_NATIVE_DIGEST_LIMIT = 64 * 1024 * 1024;

async function computeFileSha256WithWebCrypto(
  file: File,
  maxBytes = DEFAULT_NATIVE_DIGEST_LIMIT,
): Promise<string> {
  if (file.size > maxBytes) {
    throw new RangeError(
      "File exceeds the native digest memory policy; use incremental SHA-256 in a Worker",
    );
  }

  const bytes = await file.arrayBuffer();
  const digest = await crypto.subtle.digest("SHA-256", bytes);
  return Array.from(new Uint8Array(digest), (byte) =>
    byte.toString(16).padStart(2, "0"),
  ).join("");
}

6. Incorrect approaches

Calling SubtleCrypto.digest('MD5', buffer) expecting MD5, resulting in unhandled DOMException errors. Blocking the UI main thread with endless synchronous hash loops. Loading entire multi-gigabyte files without checking size bounds.

7. Boundaries: device variance, Workers, and buffer ownership

Do not rely on a fixed memory threshold that no browser specification guarantees; available memory varies by device, browser version, and concurrent tabs. Transfer chunk buffers to the Worker to reduce copying, but verify that the incremental library does not retain every chunk. Cancellation, navigation, and Worker errors must release resources.

8. Verification

Compare browser output with shasum -a 256 or a server-side SHA-256 implementation. Fix test vectors for empty files, non-aligned sizes, zero bytes, and over-limit inputs. For the Worker path, measure peak memory, main-thread long tasks, cancellation behavior, and monotonic progress across file sizes.

9. FAQ

Can I call subtle.digest() on every Blob.slice() and concatenate the results? No. That produces independent digests and is not the SHA-256 of the complete file.

How should a 2GB file be handled? Use a trusted incremental SHA-256 JS/Wasm implementation in a Worker, feeding chunks into one state while validating supply chain, licensing, and standard test vectors.

Does a Worker prevent OOM? No. It isolates computation, but you must still bound chunks, avoid retaining all input, and measure peak memory.

10. Summary

Native Web Crypto digest() is non-streaming. Use it only below an explicit memory policy; for large files, use a Worker plus a vetted incremental hash implementation and verify it with standard vectors and resource measurements. Empty chunk loops are not streaming SHA-256.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool