Windows has no base64 command; certutil is file-only, adds PEM markers and cannot be piped. This is a real base64.exe matching GNU coreutils: same flags (-d, -i, -w), stdin/stdout streaming, exit codes and error messages, so existing muscle memory and scripts carry over. Verified against coreutils 9.7 with a 153-case differential test. The subtle behaviours are reproduced: bytes are emitted incrementally so a stream that goes bad still writes its valid prefix, unpadded tails are accepted only when canonically encoded, concatenated streams decode as one, and -w0 suppresses the trailing newline. Deliberate deviation: '\r' is skipped when decoding alongside '\n', as CRLF base64 files are routine on Windows and GNU rejects them. Zero dependencies, so the build works offline.
3.9 KiB
base64 for Windows
Windows has no base64 command. certutil -encode is the usual substitute, but it only works on files, wraps output in -----BEGIN CERTIFICATE----- markers, and can't be piped. This is a real base64.exe that behaves like the one on a Linux box, so the commands and scripts you already know carry over.
base64 image.png > image.b64
base64 -d image.b64 > image.png
type secrets.json | base64 -w0
base64 -di messy.txt > payload.bin
Build and install
cargo build --release
Copy target\release\base64.exe somewhere on your PATH — e.g. %LOCALAPPDATA%\Microsoft\WindowsApps, or a C:\tools\bin folder you've added yourself:
mkdir C:\tools\bin
copy target\release\base64.exe C:\tools\bin\
[Environment]::SetEnvironmentVariable(
'Path',
[Environment]::GetEnvironmentVariable('Path','User') + ';C:\tools\bin',
'User')
Open a new terminal afterwards for the PATH change to take effect. No runtime dependencies — it's a single static binary, and the crate itself has zero dependencies, so the build works offline.
Usage
Usage: base64 [OPTION]... [FILE]
Base64 encode or decode FILE, or standard input, to standard output.
With no FILE, or when FILE is -, read standard input.
-d, --decode decode data
-i, --ignore-garbage when decoding, ignore non-alphabet characters
-w, --wrap=COLS wrap encoded lines after COLS character (default 76).
Use 0 to disable line wrapping
--help display this help and exit
--version output version information and exit
Long options accept unambiguous abbreviations (--dec), short options cluster (-di), -w takes its value joined or separate (-w0, -w 0, --wrap=0), and -- ends option parsing.
Behaviour
Verified against GNU coreutils 9.7 with a 153-case differential test covering encoding at every wrap width, decode round-trips, malformed padding, truncated input, garbage handling, all the usage errors and file operands. Output, exit codes and error messages match, including the fiddly parts:
- Partial output on failure. Bytes are emitted as soon as enough bits are available, so a stream that goes bad halfway still writes the valid prefix before the error — same as coreutils.
- Unpadded input is accepted when the encoding is canonical, i.e. the leftover bits are zero.
aGVsbG8decodes tohello;aGVis rejected becauseVcarries stray bits. - Concatenated streams decode as one (
aGVsbG8=aGVsbG8=→hellohello). -w0emits no trailing newline; wrapped output always ends with one. Empty input produces no output at all.- Exit codes:
0success,1on any error.
Deliberate differences
| Why | |
|---|---|
\r is skipped when decoding, as \n already is |
GNU rejects CRLF input as invalid. On Windows a base64 file will routinely have CRLF line endings, and erroring on those would make the tool useless for its main job. Everything else is still rejected unless you pass -i. |
--help omits GNU's support URLs |
This isn't GNU coreutils; pointing at their bug tracker would be wrong. |
Error messages quote with 'ASCII' |
GNU uses '…' in a UTF-8 locale. Windows consoles are frequently cp437/cp1252, where those bytes render as mojibake. |
Notes
- Roughly 2× slower than GNU on large inputs (100 MB encodes in ~0.35s vs ~0.18s). GNU uses hand-tuned routines; this is plain portable Rust and fast enough that the difference is invisible for normal use.
- Input is streamed, not buffered whole, so encoding a multi-gigabyte file doesn't blow up memory.
- Piping to a program that exits early (
base64 big.bin | head) is handled quietly rather than reporting a broken pipe. - Writing raw binary to a Windows console fails with a clear message telling you to redirect, because consoles take UTF-16 text and would corrupt the bytes. Redirecting to a file or piping is unaffected.