Fast, lightweight file hash checker built with Tauri 2 + Rust
Hasher is a desktop application that computes cryptographic file hashes instantly. Drop any file into the window and get MD5, SHA-1, SHA-256, and SHA-512 checksums — useful for verifying downloads, comparing files, or checking data integrity. Built with Tauri 2 and Rust for minimal resource usage (~2.3 MB binary).
- Multiple algorithms — MD5 / SHA-1 / SHA-256 / SHA-512, toggle any combination in settings
- Hash verification — paste an expected hash, algorithm auto-detected by length; the matching row is highlighted green, mismatches flagged in red
- Drag & drop or click — drop files into the window or click to open a file picker
- Batch processing — handle multiple files at once with real-time progress bars
- Parallel & fast — one thread per algorithm, memory-mapped I/O, ~2.3 MB binary
- Dark / Light mode — follows system preference and updates live when the OS theme changes
- Chinese / English — auto-detects system locale and updates live when the OS language changes
- One-click copy — copies
SHA-256: abc123...to clipboard - File type icons — color-coded by category
- Collapse / expand — per-card and global toggle
- macOS Dock drop — drop files on Dock icon to hash (cold start supported)
curl -fsSL https://raw.githubusercontent.com/yuman07/Hasher/main/install.sh | bashThe script automatically downloads the latest version, installs it to /Applications, and removes the quarantine flag so it opens without issues.
Download the .dmg from Releases, open it, and drag Hasher.app to Applications.
Note: This app is not signed with an Apple Developer certificate. macOS Gatekeeper will block it on first launch with a message like "Hasher.app can't be opened because Apple cannot check it for malicious software". To fix this, choose one of the following methods after installing:
Method 1 — System Settings (easiest):
- Try to open Hasher — it will be blocked
- Go to System Settings > Privacy & Security
- Scroll down to the Security section, you'll see a message about Hasher being blocked
- Click "Open Anyway", then confirm in the dialog
Method 2 — Right-click:
- In Finder, right-click (or Control-click) on
Hasher.app- Select "Open" from the context menu
- Click "Open" in the dialog that appears
Method 3 — Terminal:
xattr -cr /Applications/Hasher.appYou only need to do this once. After the first successful launch, macOS will remember your choice.
Download Hasher_Win10_x64_<version>.exe from Releases — portable, no install needed.
Note: This app is not code-signed. Windows SmartScreen may show a warning saying "Windows protected your PC" on first launch. Click "More info" then "Run anyway" to proceed. This only happens once.
Only macOS build steps are provided.
| Dependency | Recommended version |
|---|---|
| macOS | 26.4.1 Tahoe, Apple Silicon |
| Xcode Command Line Tools | 26.4.1 |
| Devbox | 0.17.1 |
These are the author's current dev-environment versions, verified to work for both dev and release builds. Lower versions may also work but are untested and not guaranteed.
For each item above, check whether your machine already satisfies it; if not, follow the install / upgrade steps.
- macOS
- Check: run
sw_versin Terminal, or open System Settings → General → About. - Upgrade: System Settings → General → Software Update (recommended). Apple Silicon is mandatory — the app does not run on Intel Macs, so Intel hardware cannot satisfy this prerequisite at all.
- Check: run
- Xcode Command Line Tools
- Check:
xcode-select -pprints the install path if present;pkgutil --pkg-info=com.apple.pkg.CLTools_Executablesprints the installed version. - First-time install:
xcode-select --install(opens a GUI dialog that downloads and installs from Apple). - Upgrade: System Settings → General → Software Update (recommended) — CLT updates ship alongside macOS updates there.
- Check:
- Devbox
- Check:
devbox version. - First-time install:
curl -fsSL https://get.jetify.com/devbox | bash. - Upgrade:
devbox version update.
- Check:
Assumes all prerequisites above are already satisfied.
# 1. Clone the repository and enter the project directory
git clone https://github.com/yuman07/Hasher.git
cd Hasher
# 2. Install frontend dependencies
# (Devbox also materializes Rust + Node.js on first invocation — no manual setup needed)
devbox run -- npm install
# 3. Run in dev mode or build for release
devbox run -- npx tauri dev # dev mode
devbox run -- npx tauri build # release buildHasher is a Tauri 2 hybrid app — a Rust backend handles all file I/O and cryptographic computation, while a vanilla JS frontend manages the UI. The two sides communicate through Tauri's IPC bridge: the frontend invoke()s Rust commands, and the backend emit()s events back.
When files are dropped (or selected), the frontend calls the compute_hashes command for each file. The backend then:
- Opens the file with platform-specific sequential-read hints (
FILE_FLAG_SEQUENTIAL_SCANon Windows,madvise(SEQUENTIAL)on Unix) to tell the OS to prefetch aggressively and release pages early. - Memory-maps the file via
memmap2— zero user-space buffering, the OS page cache serves data directly to the hash functions. - Spawns one OS thread per selected algorithm (up to 4). All threads share the same read-only mmap, so the file is only mapped once regardless of how many algorithms run. Each thread has a 256 KB stack (hash state needs < 2 KB; the platform default of 512 KB–8 MB would be wasteful).
- Tracks progress with a single
AtomicU64counter. Every thread bumps it after each 2 MB chunk. The calling thread polls this counter every 50 ms and emits ahash-progressevent to the frontend, which updates the progress bar. - Returns results once all threads finish. Hex encoding uses a precomputed lookup table for speed.
Empty files are fast-pathed — their well-known hashes are computed inline without threads or mmap.
The frontend is zero-framework vanilla JS + CSS to keep the binary small (~2.3 MB total). Key design choices:
- i18n — a flat
messagesobject withen/zhkeys; UI language is chosen fromnavigator.languageat launch, and alanguagechangelistener re-renders the UI live if the OS locale changes. - Theming — CSS variables drive light/dark mode. A synchronous
<script>in<head>readsprefers-color-schemebefore first paint to prevent flash; amatchMedialistener then applies OS theme changes live with a 350 ms CSS transition. - Hash verification — each card has an expected-hash input; the value is normalized (strips
algo:prefixes and whitespace), length-mapped to MD5/SHA-1/SHA-256/SHA-512, and compared against the already-computed rows. Runs on every keystroke and again once results render, so pasting before the hash finishes still works. - State — a single
stateobject holds the file map, settings, language, and theme. Only algorithm selection and hash case persist tolocalStorage; language and theme are always derived from the OS — resolved at launch and kept in sync vialanguagechange/matchMedialisteners. - macOS Dock drop — on macOS, files dropped onto the Dock icon fire a Tauri
RunEvent::Opened. If the frontend isn't ready yet (cold start), paths are buffered in aMutex<Vec<String>>on the Rust side; the frontend callstake_pending_fileson init to retrieve them.
| Component | Technology |
|---|---|
| Framework | Tauri 2 |
| Backend | Rust — md-5, sha1, sha2, memmap2 |
| Frontend | Vanilla JS + CSS (zero framework) |
| Build | Vite 8 |
| Runtime | Node.js 24 |
graph LR
subgraph FE["Frontend · Vanilla JS + CSS"]
DZ["Drop Zone / File Picker"]
UI["UI Renderer"]
LS["localStorage"]
end
subgraph BE["Backend · Rust"]
IPC["Tauri IPC Commands"]
IO["open_for_hashing()"]
MM["memmap2 Mapping"]
subgraph TH["Parallel Threads · 256 KB stack"]
T1["MD5"]
T2["SHA-1"]
T3["SHA-256"]
T4["SHA-512"]
end
AC["AtomicU64 Counter"]
PF["PendingFiles Buffer"]
end
DZ -->|"invoke(compute_hashes)"| IPC
IPC -->|"platform-specific open"| IO
IO -->|"sequential-scan hint"| MM
MM -->|"shared read-only mmap"| T1
MM -->|"shared read-only mmap"| T2
MM -->|"shared read-only mmap"| T3
MM -->|"shared read-only mmap"| T4
T1 -->|"2 MB chunk count"| AC
T2 -->|"2 MB chunk count"| AC
T3 -->|"2 MB chunk count"| AC
T4 -->|"2 MB chunk count"| AC
AC -->|"emit(hash-progress) / 50 ms"| UI
IPC -->|"return HashResult[]"| UI
UI <-.->|"auto-persist preferences"| LS
PF -.->|"macOS Dock drop on init"| DZ
- Main data flow — user drops files into the Drop Zone, which
invoke()s the Rust backend. The backend opens the file with OS-level sequential hints, memory-maps it once, and fans out to parallel hash threads. Progress flows back to the UI via 50 ms event polling; final results are returned asHashResult[] - Shared mmap design — all hash threads read from the same read-only memory mapping. This means a 1 GB file is mapped once (not 4 times), and the OS page cache serves every algorithm without redundant I/O
- Dock drop buffer — the
PendingFilesmutex handles the race condition where macOS deliversRunEvent::Openedbefore the frontend webview is ready. Paths are buffered in Rust and drained by the frontend on init viatake_pending_files() - Preference persistence — algorithm selection and hash case are stored in
localStorageand restored on every launch; theme and language are driven by the OS — resolved at launch and updated live viaprefers-color-scheme/languagechangelisteners, decoupled from the Rust backend
Hasher/
|-- .github/
| `-- workflows/
| `-- release.yml # CI: build macOS & Windows on release
|-- src/ # Frontend
| |-- main.js # App logic, i18n, UI rendering
| `-- styles.css # Themes, layout, animations
|-- src-tauri/
| |-- src/
| | |-- main.rs # Entry point
| | `-- lib.rs # Hashing engine, IPC commands
| |-- icons/ # App icons (icns, ico, png)
| |-- Info.plist # macOS metadata
| |-- Cargo.toml # Rust dependencies
| `-- tauri.conf.json # Tauri config (window, bundle)
|-- index.html # HTML shell with embedded SVG icons
|-- vite.config.js # Vite dev server config
|-- package.json # Frontend dependencies
|-- devbox.json # Devbox environment (Rust, Node.js)
|-- build-meta.json # Windows build naming metadata
|-- install.sh # macOS one-click install script
`-- rust-toolchain.toml # Rust stable channel pin



