Winix.Digest
0.4.0
Prefix Reserved
dotnet tool install --global Winix.Digest --version 0.4.0
dotnet new tool-manifest
dotnet tool install --local Winix.Digest --version 0.4.0
#tool dotnet:?package=Winix.Digest&version=0.4.0
nuke :add-package Winix.Digest --version 0.4.0
digest
Cross-platform cryptographic hashing and HMAC — SHA-2, SHA-3, BLAKE2b — with safe HMAC key handling (env / file / stdin / literal). Single native binary, no runtime, one consistent flag surface across Windows, Linux, and macOS. Replaces sha256sum / md5sum / openssl dgst for hashing, and fills the gap left by Windows, which has no first-class HMAC CLI.
Install
Scoop (Windows)
scoop bucket add winix https://github.com/Yortw/winix
scoop install winix/digest
Winget (Windows, stable releases)
winget install Winix.Digest
.NET Tool (cross-platform)
dotnet tool install -g Winix.Digest
Direct Download
Download native binaries from GitHub Releases.
Usage
digest [options] [file ...]
digest [options] -s VALUE
digest [options] # reads stdin
Examples
# SHA-256 of a literal string
digest -s "hello"
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# SHA-256 of a file
digest file.iso
# 046e70fbe5d8e3b60d03c560579f196f6e8a9978d07afcd050ba2d0e42c2d11d *file.iso
# SHA-256 of multiple files (sha256sum-compatible output)
digest *.txt
# SHA-512
digest --sha512 -s "hello"
# BLAKE2b (single-pass, faster than SHA-2 on most CPUs)
digest --blake2b file.iso
# SHA-3 — requires Windows 11 22H2+ / recent Linux / recent macOS
digest --sha3-256 -s "hello"
# HMAC-SHA-256 with key from environment variable
digest --hmac sha256 --key-env API_SECRET -s "payload"
# HMAC of a file with key from a file (Unix permissions are checked)
digest --hmac sha256 --key-file ~/.secret file.bin
# HMAC with key piped from age-encrypted storage
age --decrypt key.age | digest --hmac sha256 --key-stdin -s "msg"
# HMAC with key piped from passwordstore
pass show mykey | digest --hmac sha256 --key-stdin -s "msg"
# Base64 output (standard alphabet)
digest --base64 -s "abc"
# ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=
# URL-safe base64 (for inclusion in URLs)
digest --base64-url -s "abc"
# Crockford base32 (uppercase, no ambiguous chars)
digest --base32 -s "abc"
# Verify a known hash in constant time (exit 0 on match, 1 on mismatch)
digest --verify e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -s ""
# JSON output for machine parsing
digest --json -s "abc"
# {"algorithm":"sha256","format":"hex","hash":"ba7816bf…","source":"string"}
# Read payload from stdin (explicit)
echo -n "abc" | digest -
# Pipe a hash to the clipboard
digest file.bin | clip
Options
| Flag | Short | Default | Description |
|---|---|---|---|
--sha256 |
default | SHA-256. | |
--sha384 |
SHA-384. | ||
--sha512 |
SHA-512. | ||
--sha3-256 |
SHA3-256. Requires modern OS crypto backend. | ||
--sha3-512 |
SHA3-512. Requires modern OS crypto backend. | ||
--blake2b |
BLAKE2b-512. | ||
--sha1 |
SHA-1 (legacy — emits a stderr warning). | ||
--md5 |
MD5 (legacy — emits a stderr warning). | ||
--algo A |
-a A |
Alternative to individual algorithm flags: sha256, sha384, sha512, sha1, md5, sha3-256, sha3-512, blake2b. |
|
--hmac A |
HMAC mode using the given hash algorithm. Requires a key source (--key-env / --key-file / --key-stdin / --key). Conflicts with the plain --sha256/--algo/etc. flags. |
||
--key-env V |
Read HMAC key from environment variable V. |
||
--key-file P |
Read HMAC key from file P. Unix: emits a warning if the file is readable by group or other. |
||
--key-stdin |
Read HMAC key from stdin (cannot be combined with stdin payload). | ||
--key K |
HMAC key as a literal CLI argument. Emits a non-suppressible warning — the key is visible to ps, shell history, and process listings. |
||
--key-raw |
off | Preserve bytes on --key-file / --key-stdin (skip trailing-newline strip). |
|
--hex |
default | Hex output, lowercase. | |
--base64 |
Base64 output (standard alphabet). | ||
--base64-url |
Base64 URL-safe variant. | ||
--base32 |
Crockford base32 output (uppercase, no ambiguous characters). | ||
--uppercase |
-u |
off | Uppercase hex output. |
--string V |
-s V |
Hash the literal string V (UTF-8 bytes). Exclusive with positional file args. |
|
--verify E |
Compare output with expected string E in constant time. Exit 0 on match, 1 on mismatch. Not supported with multiple files. |
||
--json |
off | Emit JSON output (single object, or array for multi-file). | |
--describe |
Emit structured JSON metadata for AI discoverability. | ||
--help |
-h |
Show help and exit. | |
--version |
-v |
Show version and exit. | |
--color[=auto\|always\|never] |
auto |
Coloured output: auto (default when omitted), always, or never. | |
--no-color |
Disable coloured output. Respects NO_COLOR. |
Algorithms
| Algorithm | Bit length | Family | Status |
|---|---|---|---|
sha256 (default) |
256 | SHA-2 | Modern, recommended. |
sha384 |
384 | SHA-2 | Modern. |
sha512 |
512 | SHA-2 | Modern. Often faster than SHA-256 on 64-bit CPUs. |
sha3-256 |
256 | SHA-3 / Keccak | Modern. Requires Windows 11 22H2+, recent Linux, or recent macOS. |
sha3-512 |
512 | SHA-3 / Keccak | Modern. Same platform caveat. |
blake2b |
512 | BLAKE2 | Modern. Single-pass, typically faster than SHA-2 on modern CPUs. |
sha1 |
160 | SHA-1 | Legacy — broken for collisions. Emits stderr warning. Still acceptable as HMAC primitive. |
md5 |
128 | MD5 | Legacy — cryptographically broken. Emits stderr warning. Use only for non-security contexts (e.g. file integrity against accidental corruption). |
HMAC Key Handling
HMAC requires a secret key; the threat model is that the key should not leak via ps output, shell history, core dumps, or world-readable files. digest supports four key sources, listed from safest to least safe:
--key-stdin— read the key from stdin. Pair with an external secrets manager:age --decrypt key.age | digest --hmac sha256 --key-stdin -s "payload" pass show mykey | digest --hmac sha256 --key-stdin -s "payload" secret-tool lookup svc myservice | digest --hmac sha256 --key-stdin -s "payload"--key-env VAR— read the key from an environment variable. Safe if the variable is set by your shell's secrets loader or a secrets manager injecting into the process environment.--key-file PATH— read the key from a file. On Unix,digestemits a stderr warning if the file is readable by group or other (suggestchmod 0600). On Windows, ACL inspection is deferred to a futureprotect/unprotecttool.--key KEY— literal key as a CLI argument. Always emits a warning because the key is visible to:- Other users via
ps//proc/*/cmdline - Your shell history (
~/.bash_history,~/.zsh_history) - Process listing tools and auditd logs
Use
--keyonly for transient throwaway testing — never in scripts or long-running systems.- Other users via
Encrypted-at-rest key files
--key-stdin is designed to compose with external encryption tooling so the on-disk key file is never plaintext. Pipe from age, gpg, pass, secret-tool, or security find-generic-password (macOS Keychain). A future protect/unprotect tool will add a Winix-native path for this on Windows.
Trailing-newline stripping
When reading a key from --key-file or --key-stdin, digest strips a single trailing \n or \r\n by default. This is what you want when the key file was created with echo "secret" > file, which appends a newline. Pass --key-raw to preserve exact bytes (e.g. when the key file contains binary material and legitimately ends in 0x0A).
BLAKE2b key ceiling
BLAKE2b keyed mode caps keys at 64 bytes per RFC 7693 §2.9. Longer keys are rejected before any hashing starts (exit 125). For longer keys, use --hmac sha256 (or another SHA-family algorithm) which auto-hashes oversized keys.
Empty keys are rejected
A zero-length HMAC key is cryptographically meaningless — the BCL HMAC* classes accept it and produce a deterministic-but-forgeable tag. digest rejects empty keys at the resolver layer with a usage error naming the source (env variable, key file, stdin, or --key). All four key sources can produce an empty key (env var set to empty, 0-byte file, EOF-only stdin, --key ""); each is rejected with a clear message.
Key file size cap
Key files are read with a 1 MB cap. HMAC keys are typically 16–128 bytes, and any key larger than the algorithm's block size (128 bytes for SHA-512) is internally pre-hashed by the BCL — so values above ~1 KB add no entropy. The cap defends against accidental --key-file /dev/zero or piping a multi-GB stream via --key-stdin.
Binary stdin payloads
When the payload comes from stdin (cat binary.bin | digest), digest reads raw bytes through to the hasher — no UTF-8 round-trip. This matches sha256sum binary.bin for the same input. Earlier versions decoded stdin as text and re-encoded as UTF-8, which silently corrupted any byte that wasn't valid UTF-8.
Input Modes
- String (
-s VALUE) — hash the UTF-8 bytes of the literal string. Cannot be combined with positional file arguments. - Stdin (default, or explicit
-) — hash bytes read from standard input. - Single file —
digest FILE. IfFILEdoes not exist,digestexits 125 with an error; it will not silently treat it as a literal string. Use--stringif you want to hash an argument as text. - Multiple files —
digest FILE1 FILE2 .... Produces one sha256sum-compatible line per file. If any file is missing, the whole batch fails before any output — no partial "hashed some, errored on the rest" state.
Output Formats
| Format | Example (SHA-256 of "abc") |
|---|---|
--hex (default) |
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad |
--hex --uppercase |
BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD |
--base64 |
ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= |
--base64-url |
URL-safe base64 (replaces +// with -/_) |
--base32 |
Crockford base32 (uppercase, no I/L/O/U) |
Verify Mode
--verify EXPECTED compares the computed hash against EXPECTED in constant time:
digest --verify e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -s ""
# exit 0, no output
digest --verify wronghash -s ""
# stderr: digest: verification failed
# exit 1
Hex and Crockford base32 comparisons are case-insensitive (Crockford base32 defines decoding as case-insensitive even though the encoder emits uppercase only). Standard base64 and base64-url comparisons are case-sensitive — those alphabets carry case meaning. Verify is not supported with multiple files — use sha256sum -c <checksum-file> for batch verification flows.
Multi-file Output Format
Multi-file output is sha256sum-compatible with the binary-mode marker:
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad *file.bin
3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282 *other.bin
The * before the filename signals binary mode — the hash was computed over raw bytes with no CR/LF translation. digest always reads files as raw bytes, so * is always accurate. Output is directly consumable by sha256sum -c for verification.
Differences from sha256sum
- All-or-nothing validation on multi-file mode: a missing file errors the whole command rather than emitting hashes for the first N-1 files and erroring on file N. Safer for verification flows.
- Binary-mode marker by default:
*is always used becausedigestalways reads raw bytes — no confusing text-vs-binary modes. - Built-in HMAC: one flag, four key sources, Unix permission warnings, literal-key exposure warnings. No need to shell out to
openssl dgst. - Base64 and base32 output: useful for JWT signatures, config files, and URL-safe tokens.
--describeemits structured JSON metadata for AI agents.
Wildcards on Windows
cmd.exe and PowerShell don't expand */? wildcards before starting programs, so
digest expands them itself on Windows — digest *.dll works the same as in bash.
* and ? work in any path segment. [...] is matched literally (brackets are legal
Windows filename characters), and ** is rejected with an error — use Git Bash for
recursive patterns. A pattern that matches nothing is passed through unchanged,
so you get the normal "not found" error. In cmd, quoting a pattern ("*.dll")
suppresses expansion; PowerShell removes quotes before digest sees them, so use --%
there if you need a literal. On Linux/macOS your shell expands wildcards as usual and
digest does nothing extra.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (or verification match). |
| 1 | Verification failed (--verify mismatch). |
| 125 | Usage error — bad flags, unknown value, flag conflict, missing file, empty HMAC key, key file over size cap. |
| 126 | Runtime error — SHA-3 unavailable on this platform, file read failure, key file read failure, generic catch-all. |
Exit 1 is reserved for --verify mismatches and never returned for runtime errors. Scripts of the shape digest --verify ... || alert can rely on 1 meaning "the hash you supplied doesn't match" — a runtime crash returns 126 instead.
Related Tools
clip— copy a hash to the clipboard:digest file | clipids— generate UUIDs / ULIDs / NanoIDs (complementary ID primitives)- Future
protect/unprotect— Winix-native encrypted-at-rest storage for key files (Windows DPAPI and equivalents elsewhere)
See Also
man digest(afterwinix install man)digest --describefor JSON metadata
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
## [0.3.0] - 2026-05-10
- Initial release.
See full changelog at https://github.com/Yortw/winix/blob/main/src/digest/CHANGELOG.md