Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Here is the list of all utilities:
- [Random String Generator](https://jam.dev/utilities/random-string-generator)
- [CSV file viewer](https://jam.dev/utilities/csv-file-viewer)
- [JSONL Validator](https://jam.dev/utilities/jsonl-validator)
- [File Integrity Checker](https://jam.dev/utilities/file-integrity-checker)

### Built With

Expand Down
38 changes: 38 additions & 0 deletions __tests__/pages/utilities/file-integrity-checker.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import FileIntegrityChecker from "../../../pages/utilities/file-integrity-checker";

describe("FileIntegrityChecker", () => {
test("computes the sha256 hash automatically as soon as a file is uploaded", async () => {
render(<FileIntegrityChecker />);

const user = userEvent.setup();
const file = new File(["hello world"], "hello.txt", {
type: "text/plain",
});

await user.upload(screen.getByTestId("input"), file);

expect(await screen.findByText(/sha-256 hash/i)).toBeInTheDocument();
expect(screen.getByDisplayValue(/^[a-f0-9]{64}$/i)).toBeInTheDocument();
});

test("switching algorithms clears the stale hash and recomputes for the new one", async () => {
render(<FileIntegrityChecker />);

const user = userEvent.setup();
const file = new File(["hello world"], "hello.txt", {
type: "text/plain",
});

await user.upload(screen.getByTestId("input"), file);
await screen.findByDisplayValue(/^[a-f0-9]{64}$/i); // sha256 by default

await user.selectOptions(screen.getByLabelText(/hash algorithm/i), "md5");

expect(await screen.findByText(/md5 hash/i)).toBeInTheDocument();
expect(
await screen.findByDisplayValue(/^[a-f0-9]{32}$/i)
).toBeInTheDocument();
});
});
101 changes: 101 additions & 0 deletions components/seo/FileIntegrityCheckerSEO.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
export default function FileIntegrityCheckerSEO() {
return (
<div className="content-wrapper">
<section>
<p>
Generate a checksum (hash) of any file to confirm it wasn&apos;t
corrupted during download or transfer, or tampered with along the way.
If the hash you calculate matches the hash published by the
file&apos;s source, the file is identical, byte for byte, to the
original.
</p>
</section>

<section>
<p>
Drop a file into the tool above or click to browse for one. Large
files are streamed in chunks, so there&apos;s no file size limit and
nothing is ever uploaded anywhere.
</p>
</section>

<section>
<h2>How to Check File Integrity</h2>
<ul>
<li>
<b>Drop in or browse for your file:</b> <br /> Drag and drop the
file directly or click to select it from your machine.
</li>
<li>
<b>Pick the algorithm:</b> <br /> Choose SHA-256, SHA-512, or MD5 to
match the hash you were given.
</li>
<li>
<b>Wait for the hash to generate:</b> <br /> Large files are
streamed in chunks, so even multi-gigabyte files can be checked
without running out of memory.
</li>
<li>
<b>Verify against a known hash:</b> <br /> Paste the published hash
into the verify field to confirm it matches.
</li>
</ul>
</section>

<section>
<h2>SHA-256 vs SHA-512 vs MD5</h2>
<p>
SHA-256 is the most common choice today for verifying downloads and
software packages. SHA-512 offers a longer digest for the same family.
MD5 is faster and still widely used for detecting accidental
corruption, though it&apos;s not considered cryptographically secure
against deliberate tampering.
</p>
</section>

<section>
<h2>Why Use a File Integrity Checker?</h2>
<ul>
<li>
<b>Catch corrupted downloads:</b> <br /> A hash mismatch tells you
immediately if a download was interrupted or damaged in transit.
</li>
<li>
<b>Detect tampering:</b> <br /> Comparing against a
publisher-provided hash confirms a file wasn&apos;t altered before
it reached you.
</li>
<li>
<b>Works on any file size:</b> <br /> Chunked streaming means
multi-gigabyte files are checked without loading the whole file into
memory.
</li>
</ul>
</section>

<section>
<h2>FAQs</h2>
<ul>
<li>
<b>Is this tool safe for large or sensitive files?</b> <br /> Yes.
Everything runs locally in your browser — files are never uploaded
anywhere.
</li>
<li>
<b>Which algorithm should I use?</b> <br /> Use whichever algorithm
matches the hash published by the file&apos;s source — SHA-256,
SHA-512, or MD5.
</li>
<li>
<b>Is there a file size limit?</b> <br /> No. Files are read and
hashed in chunks, so even very large files can be checked.
</li>
<li>
<b>Is my file sent to a server?</b> <br /> No. All hashing happens
locally in your browser. Your file never leaves your machine.
</li>
</ul>
</section>
</div>
);
}
6 changes: 6 additions & 0 deletions components/utils/tools-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,10 @@ export const tools = [
"View, search, and filter CSV, TSV or LOG files with color-coded severity levels. Quickly scan through logs with Datadog-inspired faceted filtering.",
link: "/utilities/csv-file-viewer",
},
{
title: "File integrity checker",
description:
"Generate SHA-256, SHA-512, and MD5 checksums for files of any size, then verify them against a known hash to confirm the file wasn't corrupted or altered in transit.",
link: "/utilities/file-integrity-checker",
},
];
13 changes: 13 additions & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import "@testing-library/jest-dom";

// jsdom's Blob (and File, which extends it) doesn't implement arrayBuffer().
// so polyfill via FileReader, which jsdom does support.
if (!Blob.prototype.arrayBuffer) {
Blob.prototype.arrayBuffer = function (this: Blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as ArrayBuffer);
reader.onerror = () => reject(reader.error);
reader.readAsArrayBuffer(this);
});
};
}

// There's no official way to mock next/navigation.
// We just make it no-op for all tests.
jest.mock("next/navigation", () => ({
Expand Down
Loading