Files
Tools/base64-rs/README.md
T
LyAhn 665f846dc9 test(base64-rs): add GNU differential parity harness
Runs both binaries over 154 cases and compares stdout, stderr and exit
code: encoding at every wrap width, decode round-trips, canonical-tail
rules, padding placement, concatenated streams, every prefix of a known
encoding, usage errors and file operands.

Locates a genuine GNU base64 rather than trusting /usr/bin/base64, since
distributions increasingly ship uutils there and it disagrees with GNU on
most malformed input — running against it fails 51 of the 154 cases, which
doubles as a negative control for the harness itself.

The three known differences are asserted as expected rather than ignored.
2026-07-27 21:53:26 +01:00

4.4 KiB
Raw Blame History

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 154-case differential test (tests/parity.py) 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. aGVsbG8 decodes to hello; aGV is rejected because V carries stray bits.
  • Concatenated streams decode as one (aGVsbG8=aGVsbG8=hellohello).
  • -w0 emits no trailing newline; wrapped output always ends with one. Empty input produces no output at all.
  • Exit codes: 0 success, 1 on 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.

Running the tests

cargo build --release
python3 tests/parity.py

Linux-only — it diffs against a real GNU base64, so it can't run on the Windows box this tool targets. Note that many distributions now ship uutils coreutils as /usr/bin/base64; it disagrees with GNU on most malformed-input cases, so the script hunts for a genuine GNU build and refuses to run against anything else. On Ubuntu with coreutils-from-uutils installed, the GNU one is gnubase64.

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.