# File Hash Verification

> Check a download against its checksum or SHA256SUMS file. Files never leave your browser.

Category: Hashing · Page: https://util.caruana.me/tools/hash/verify/

## API, MCP and A2A

### Verify a checksum (`hash_verify`)

`GET|POST https://util.caruana.me/api/hash/verify` · MCP tool `hash_verify` · A2A skill `hash_verify` · [reference](https://util.caruana.me/reference/hash_verify/)

Hashes the data with every algorithm whose digest length matches the expected hash (for 64 hex digits: SHA-256, SHA3-256 and BLAKE3) and reports whether any matches. `expected` may be hex, Base64, SRI (`sha384-…`), prefixed (`sha256:…`), or a whole GNU/BSD checksum file, in which case the line for `filename` (or the uploaded file name) is used. A mismatch is a successful response with `match: false`.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string | no | Data as UTF-8 text. Use exactly one of `input`, `input_base64`, or a file upload. |
| `input_base64` | string | no | Data as Base64 (either alphabet), for binary content over JSON or MCP. |
| `expected` | string | yes | Expected hash in any supported form, or the contents of a checksum file. |
| `filename` | string | no | Which checksum-list line to use. Defaults to the uploaded file name. Directory parts are ignored. |

Privacy: The server receives the data and the expected hash, hashes the data in memory, and returns the comparison. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. Uploading a file means the server sees its full contents: for private or large files use the page, which hashes locally.


## User guide

The tool sits in a window that works like a desktop app: menus on the top row and the **full-screen** icon at its right (**Esc** leaves full screen). The set-up options described below are in **Settings → Options…**, an overlay, and the **Settings** menu also lists the common choices directly. To keep the options in view instead, choose **View → Inline config**; the page remembers that choice. **Help → Keyboard shortcuts…** lists the keys.

1. **Drop the downloaded file** on the box, or click it to choose one.
2. **Paste the expected checksum** from the publisher. Any of these works:
   - a hex hash: `9f86d081…`
   - a Base64 or SRI hash: `sha384-oqVuAfXR…`
   - a prefixed hash: `sha256:9f86…`
   - the whole contents of a `SHA256SUMS`, `*.sha256` or `CHECKSUMS` file
3. Read the verdict:
   - **Match** names the algorithm that matched. The file is bit-for-bit what the checksum describes.
   - **Mismatch** means the file is corrupted, incomplete, or a different file. Download it again from the official source.

You do not need to choose an algorithm: it is worked out from the length of the expected hash. With a checksum list, the line whose file name matches your file is used, ignoring any directory part.

Without an expected hash, the page simply computes the digests ticked under **Algorithms to compute**. Hashing a multi-gigabyte file takes a while in JavaScript; a progress bar shows how far it has got.

> **What a matching checksum proves.** It proves the file matches the checksum, nothing more. If an attacker can replace the download, they can usually replace a checksum on the same website too. To know the checksum itself is genuine, verify a signed checksum file (PGP or similar) or get the checksum over a separate channel.

## Specification

**Expected-hash formats.**

| Form           | Example                              | How it is read                                                     |
| -------------- | ------------------------------------ | ------------------------------------------------------------------ |
| Hex            | `ba7816bf…20015ad`                   | Case-insensitive; spaces and colons ignored                        |
| Base64         | `ungWv48B…FAa0=`                     | Either alphabet; padding optional                                  |
| SRI / prefixed | `sha256-…`, `SHA-256:…`, `blake3=…`  | Restricts the comparison to the named algorithm                    |
| GNU list       | `<hash>  <file>` or `<hash> *<file>` | `sha256sum`, `md5sum`, `b2sum` output                              |
| BSD list       | `SHA256 (<file>) = <hash>`           | `shasum --tag`, BSD `sha256` output; algorithm taken from the line |

**Algorithm detection.** These are the candidates for each decoded digest length; all candidates are computed and compared:

| Bytes | Candidates                     |
| ----- | ------------------------------ |
| 16    | MD5                            |
| 20    | SHA-1                          |
| 32    | SHA-256, SHA3-256, BLAKE3      |
| 48    | SHA-384                        |
| 64    | SHA-512, SHA3-512, BLAKE2b-512 |

**Line selection in a checksum list.**

- **Multiple lines:** the line whose file name equals the dropped file's name is used, comparing the base names only.
- **A single line** is used whatever its file name.
- **No matching line** is reported as an error rather than a mismatch.

**Processing on the page.**

- **Reading:** `File.stream()` chunks are fed to every selected hasher in a single pass, inside a dedicated Web Worker started with the page.
- **Cancelling:** choosing another file cancels the running job by terminating the worker.

**API differences.**

- **Size:** at most 1 MiB of data per request, because of Cloudflare's per-request CPU limits.
- **Mismatches** return `200` with `"match": false`.
- **Unreadable expectations** return `422`.
