Using Base85 (Ascii85, Z85, and Git)
Base85 packs four input bytes into a 32-bit unsigned integer, then divides by 85 four times to emit five output
characters. The payload expansion is 25 % - the smallest of any encoding the library ships, but at the cost
of a denser alphabet that includes ASCII punctuation.
input bytes : 4D 61 6E 20 (ASCII "Man ", 4 bytes)
┌── 32-bit unsigned big-endian = 0x4D616E20 ──┐
│ 1298230816 / 85⁴ = 24 r 45,415,816 │
│ 45,415,816 / 85³ = 73 r 584,691 │
│ 584,691 / 85² = 80 r 6,691 │
│ 6,691 / 85 = 78 r 61 │
│ 61 │
└── digits 24, 73, 80, 78, 61 ┘
encoded text : 9jqo^ (Ascii85; alphabet '!' (33) + digit)
k_kxi (Z85; different alphabet ordering)
Quick reference
using Bodu.Text.Encoding;
byte[] data = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE };
// Adobe Ascii85 (default)
string a = Base85.Encode(data);
// 8 bytes → 10 characters
// ZeroMQ Z85
string z = Base85.Encode(data, Base85Variant.Z85);
// 8 bytes → 10 characters (different alphabet)
// Decode
byte[] back = Base85.Decode(a);
Variant comparison
| Variant | Alphabet | z shortcut |
Partial groups | Input alignment |
|---|---|---|---|---|
Ascii85 (Adobe Tech Note 5045) |
! (33) through u (117) - 85 contiguous ASCII characters |
Yes - z represents 4 zero bytes |
Yes - 1, 2, or 3-byte tails permitted | Any length |
Z85 (RFC 32 - ZeroMQ) |
0-9 a-z A-Z .-:+=^!/*?&<>()[]{}@%$# - shell-safe, no quote or backslash |
No | No | Multiple of 4 bytes |
GitCompact (Git base85.c) |
0-9 A-Z a-z !#$%&()*+-;<=>?@^_ ` {|}~ - Git binary-patch alphabet |
No | Yes - compact 1, 2, or 3-byte tails | Any length |
When to pick each
| Variant | Reach for |
|---|---|
Ascii85 |
PDF / PostScript embedded binary, Adobe Tech Note 5045-compatible streams, dense Base85 with shortcut |
Z85 |
ZeroMQ wire keys, shell-pasted binary keys (alphabet avoids quote / backslash / semicolon) |
GitCompact |
The Git binary-patch Base85 alphabet - round-trip-safe compact output, or the exact padded line primitive |
The z shortcut (Ascii85 only)
Adobe Ascii85 reserves the character z (ASCII 122 - outside the !-u alphabet) as a shortcut for four
consecutive zero bytes. The encoder emits it automatically:
byte[] zeros = new byte[8];
string encoded = Base85.Encode(zeros); // "zz" - two shortcuts, not 10 chars
byte[] back = Base85.Decode("zz"); // 8 zero bytes
The shortcut is only valid at a group boundary. The decoder rejects z mid-group:
Base85.Decode("9jz"); // FormatException - z after partial group
Z85 has no shortcut - all-zero input emits five 0 characters per group.
Partial groups (Ascii85 only)
Ascii85 allows trailing partial groups of 1, 2, or 3 bytes. The encoder pads the trailing bytes with zeros to fill a 4-byte group, encodes the full group, then emits (1 + remaining) characters from the result:
| Trailing bytes | Encoded characters |
|---|---|
| 1 | 2 |
| 2 | 3 |
| 3 | 4 |
| 4 (full group) | 5 |
The decoder reverses this: a trailing partial group of 2, 3, or 4 characters is padded with the maximum digit
value (u) to fill 5 characters, decoded, then truncated to (input - 1) bytes.
Base85.Encode(new byte[] { 0x00 }); // "!!" (1 byte → 2 chars)
Base85.Encode(new byte[] { 0x00, 0x00 }); // "!!!" (2 bytes → 3 chars)
Base85.Encode(new byte[] { 0x00, 0x00, 0x00 }); // "!!!!" (3 bytes → 4 chars)
A single trailing character (or six characters - full group plus one) is rejected:
Base85.Decode("9"); // FormatException - single trailing char invalid
Base85.Decode("uuuuuu"); // FormatException - full group + 1 invalid
Z85 alignment requirement
Z85 enforces input alignment per RFC 32: encoder input length must be a multiple of four bytes, decoder input
length must be a multiple of five characters. Non-aligned input throws ArgumentException on encode and
FormatException on decode.
Base85.Encode(new byte[5], Base85Variant.Z85); // ArgumentException - 5 is not a multiple of 4
Base85.Decode("HelloW", Base85Variant.Z85); // FormatException - 6 is not a multiple of 5
Z85 shell-safe alphabet
Z85's defining feature is its alphabet choice - it avoids characters that shells, JSON, or quoted strings would need to escape:
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#
Notice the absence of ", ', \, ;, |, ` - exactly the characters that would require escaping in
shell commands or in JSON / XML string literals. This makes Z85 a popular choice for binary keys that need to be
pasted into shell sessions or embedded directly in configuration files.
Git Base85
Base85Variant.GitCompact adds the alphabet Git uses for binary patch payloads (base85.c). It shares the Ascii85 partial-group
behaviour but uses Git's alphabet, and it has no z shortcut and no Adobe <~/~> delimiters - those
characters (<, =, >, ~) are ordinary Git digits.
Note
Base85Variant.GitCompact implements the Git Base85 alphabet only. It does not parse Git binary patches - literal/delta
sections, zlib payloads, line-length prefixes, and patch application are out of scope.
Compact mode (default, round-trip safe)
Base85.Encode(..., Base85Variant.GitCompact) emits a compact, self-delimiting tail following Python's
base64.b85encode(..., pad=False): a final remainder of 1, 2, or 3 bytes becomes 2, 3, or 4 characters. This makes
Decode(Encode(bytes, GitCompact), GitCompact) == bytes for any byte length, so the variant is registered as an
IBinaryEncoding (base85-git / git-base85 / b85).
Base85.Encode(new byte[] { 0x00, 0x00, 0x00, 0x00 }, Base85Variant.GitCompact); // "00000" (no shortcut)
Base85.Encode(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, Base85Variant.GitCompact); // "|NsC0"
Base85.Encode("hello"u8, Base85Variant.GitCompact); // "Xk~0{Zv" (5 bytes → 7 chars)
An encoded length of len % 5 == 1 is invalid (a single trailing character cannot represent a byte), exactly as
Ascii85.
Padded mode (exact Git line primitive)
Git's binary-patch line format is not self-delimiting: it always emits five characters per group and carries the
decoded byte count in the line prefix. The EncodeGitPadded / DecodeGitPadded helpers expose that primitive - the
caller supplies the decoded length on decode. They are not registered as IBinaryEncoding, because the interface
cannot carry that length.
string padded = Base85.EncodeGitPadded(new byte[] { 0x01 }); // "0RR91" (1 byte → 5 chars)
byte[] back = Base85.DecodeGitPadded(padded, decodedLength: 1); // { 0x01 }
Lenient parsing
| Flag | Effect |
|---|---|
BaseFormatStyles.IgnoreWhitespace |
Strip ASCII space / tab / CR / LF anywhere |
BaseFormatStyles.AllowPrefix |
No-op for Base85 |
BaseFormatStyles.AllowMissingPadding |
No-op for Base85 (no padding character) |
Span path
int maxChars = Base85.GetMaxEncodedLength(data.Length);
char[] buffer = new char[maxChars];
int written = Base85.Encode(data, buffer);
ReadOnlySpan<char> result = buffer.AsSpan(0, written);
GetMaxEncodedLength(int) is an upper bound because the Ascii85 z shortcut compresses a four-zero group to one
character, so the actual Ascii85 output can be shorter than the worst case. When you need the exact length, pass
the data span to the GetEncodedLength(ReadOnlySpan<byte>, …) overload, which scans for z-eligible groups:
Base85.GetMaxEncodedLength(8, Base85Variant.Ascii85); // worst case (no shortcuts)
Base85.GetEncodedLength(new byte[8], Base85Variant.Ascii85); // exact - counts the two 'z' shortcuts
Base85.GetEncodedLength(data, Base85Variant.Z85); // exact - Z85 has no shortcut, so equals the bound
Like Base58, Base85 is not streamable: the EncodeToUtf8 / DecodeFromUtf8 overloads exist for API consistency
with the other encodings but treat the input as a single block.
Encoding a GUID
Base85 encodes a Guid directly. A 16-byte value is a whole number of 4-byte groups, so Ascii85
emits 20 characters with no partial-group tail:
Guid id = Guid.NewGuid();
string token = Base85.Encode(id); // 20 Ascii85 characters
Guid back = Base85.DecodeGuid(token); // FormatException unless it decodes to 16 bytes
bool ok = Base85.TryDecodeGuid(token, out Guid parsed);
The bytes use the GUID's native mixed-endian layout (matching Guid.TryWriteBytes). A GUID whose bytes happen to
contain a four-zero group encodes with a z shortcut, so the token can be shorter than 20 characters and still
round-trips exactly.
Validation and sizing
Base85.IsValid("9jqo^"); // true (Ascii85)
Base85.IsValid("9jqov"); // false ('v' is ASCII 118, above 'u')
Base85.IsValid("z"); // true (z shortcut)
Base85.IsBase85Digit('!'); // true
Base85.IsBase85Digit('z'); // false (z is the shortcut, not a digit)
Base85.GetEncodedLength(4, Base85Variant.Ascii85); // 5 (no shortcut path)
Base85.GetEncodedLength(4, Base85Variant.Z85); // 5
Base85.GetMaxDecodedLength(5); // 4
Common patterns
Embedded binary in a config file
byte[] secret = Convert.FromBase64String(env);
string z85Key = Base85.Encode(secret, Base85Variant.Z85);
// z85Key can be pasted into JSON, YAML, or a shell variable without escaping
PostScript / PDF Ascii85 stream
string Stream(ReadOnlySpan<byte> binary) => "<~" + Base85.Encode(binary) + "~>";
// PostScript convention: <~…~> delimits an Ascii85 region
(The library does not add or strip the <~/~> delimiters automatically - they are a PostScript convention, not
part of the encoding itself.)
Where to go next
- Base58 guide - when the use case is blockchain or human-typed identifiers.
- Base64 guide - when familiarity beats density.
IBinaryEncodinginterface - runtime-selected encoding choice.- Text & Serialization guides - every guide in this topic, across Bodu.Text.Encoding, Bodu.Text.Formats, and the Bencode / TOML serializers.