Skip to content
ASCII World

Base64 Encoding: How It Works and When to Use It

Binary-to-Text Conversion, RFC 4648, and Real-World Overhead

By the ASCII World team

The 24-to-32 Bit Re-indexing Scheme

RFC 4648 specifies Base64 as a method that consumes three 8-bit octets (24 bits) and yields four 6-bit units (also 24 bits total), each indexing an array of 64 safe ASCII characters. Early network protocols like SMTP and early versions of NNTP were built to handle 7-bit ASCII lines terminating in specific control sequences. Passing raw binary through those channels routinely corrupted files when null bytes (0x00) terminated strings prematurely or byte values like 0x0D (Carriage Return) and 0x0A (Line Feed) triggered unexpected line breaks. Mapping raw bytes to the printable characters set eliminated this transport instability.

Consider encoding the plain ASCII string "Cat". In memory, these three characters reside as standard 8-bit byte values:

  • 'C' = ASCII 67 = 01000011
  • 'a' = ASCII 97 = 01100001
  • 't' = ASCII 116 = 01110100

Concatenating those bytes produces a continuous 24-bit stream: 010000110110000101110100. Base64 slices this stream into four 6-bit chunks rather than three 8-bit octets:

  1. 010000 (decimal 16)
  2. 110110 (decimal 54)
  3. 000101 (decimal 5)
  4. 110100 (decimal 52)

These values map directly into the standard Base64 index table: index 16 is 'Q', index 54 is '2', index 5 is 'F', and index 52 is '0'. The word "Cat" becomes "Q2F0". You can verify this mapping logic directly with a Base64 tool.

The RFC 4648 Alphabet and Character Index

The standard Base64 alphabet spans indices 0 through 63 using characters drawn exclusively from the US-ASCII repertoire. Capital letters represent indices 0 through 25, lowercase letters cover 26 through 51, numerical digits fill 52 through 61, and two punctuation symbols close out the set at 62 and 63.

Index Range Binary Range Character Mappings Source Category
0 - 25 000000 - 011001 A through Z Uppercase Latin letters
26 - 51 011010 - 110011 a through z Lowercase Latin letters
52 - 61 110100 - 111101 0 through 9 Decimal digits
62 111110 + (Plus) Punctuation character
63 111111 / (Forward Slash) Punctuation character
Pad N/A = (Equal Sign) Structural padding indicator

Because the original input stream may not divide evenly into 3-byte segments, padding rules come into play when terminating a stream.

Padding Mechanics: Zero-Bits and Equal Signs

A byte payload length modulo 3 yields three possible remainders: 0, 1, or 2. If the remainder is 0, the bitstream partitions cleanly into 6-bit units without leftover bits. The other two cases require deterministic padding.

When one byte remains (8 bits), the encoder appends 4 zero bits to make a 12-bit block. That block divides into two 6-bit characters. The encoder then appends two = characters so that the final Base64 quartet remains 4 bytes long. For example, encoding the character 'M' (binary 01001101) pads four trailing zero bits: 010011 (index 19, 'T') and 010000 (index 16, 'Q'). The final output is "TQ==".

When two bytes remain (16 bits), the encoder appends 2 zero bits to produce an 18-bit block, yielding three 6-bit characters. A single = character is added to round out the 4-byte block. Encoding the string "Ma" produces "TWE=".

Decoders strictly enforcing RFC 4648 Section 3.5 verify that these appended bits are set to zero. If non-zero padding bits arrive, robust implementations reject the string because stray bits can signal steganographic leakage or data corruption. Some modern parsers, particularly in web APIs, accept unpadded strings, while others throw decoding errors if the length is not an exact multiple of 4.

URL-Safe Base64: Handling Plus and Slash

Standard Base64 contains two characters that break common web structures: + and /. In HTTP query strings, application/x-www-form-urlencoded parsers treat a plus sign as a space. In paths, forward slashes act as segment delimiters. A token containing standard Base64 will fail or mangle when passed unescaped in a URL parameter.

RFC 4648 Section 5 defines "base64url" to fix this. It swaps index 62 from + to - (minus) and index 63 from / to _ (underscore). JSON Web Tokens (JWT) rely on this variant across their header, payload, and signature blocks. When building authentication tokens or URL parameters, pairing base64url with optional pad-stripping prevents clashes with upstream URL encoders.

Overhead Calculations and Real-World Applications

Every 3 bytes of raw binary produce 4 bytes of ASCII text. That is a permanent 33.33% storage increase before accounting for optional line wraps. If an email client wraps MIME lines at 76 characters using CRLF pairs, the real overhead climbs to approximately 37%. Storing a 10 MB image as a Base64 data URI in HTML or CSS inflates the payload to roughly 13.3 MB. Parsing that data URI also forces the browser or server runtime to allocate additional memory while building the decoding buffer.

Despite this overhead, Base64 is the standard solution in specific scenarios:

  • Data URIs: Inlining small icons (under a few kilobytes) directly inside CSS files avoids extra round-trip HTTP requests on latency-sensitive pages.
  • MIME Email Attachments: RFC 2045 requires safe transport over SMTP relays that may still strip the eighth bit or reject control characters.
  • Cryptographic Fingerprints: SSH public keys, TLS certificates (PEM format), and digital signatures are packaged in Base64 between human-readable header boundaries.
  • JSON and XML Embeddings: Neither JSON nor standard XML supports unescaped arbitrary binary bytes, so Base64 acts as the defacto binary transport container.

Base64 in Everyday Programming Languages

Most modern standard libraries provide optimized Base64 encoders and decoders. Differences emerge around how they handle padding, line breaks, and URL safety.

In Python, the base64 module offers standard and URL-safe routines:

import base64

raw_data = b"\x00\xff\x7fHello"
standard_b64 = base64.b64encode(raw_data).decode("ascii")
# Output: AP9/SGVsbG8=

url_safe_b64 = base64.urlsafe_b64encode(raw_data).decode("ascii")
# Output: AP9_SGVsbG8=

In JavaScript, web browsers supply btoa() and atob() for Latin-1 strings, but these fail when handed multi-byte Unicode strings. Modern runtimes handle binary conversions cleanly using Node.js Buffers or browser Uint8Array instances:

// Node.js buffer conversion
const buf = Buffer.from("Hello World!", "utf-8");
const encoded = buf.toString("base64");
const decoded = Buffer.from(encoded, "base64").toString("utf-8");

// Web API URL-safe replacement
function toBase64Url(base64String) {
  return base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

When working with text rather than raw bytes, character encoding mismatches are common. Converting a UTF-8 string containing non-ASCII glyphs to Base64 requires encoding the text to raw bytes first, then passing those bytes to the Base64 encoder. Attempting to encode high code point directly into 7-bit containers without specifying a byte layout like UTF-8 corrupts the underlying string representation.

Common Pitfalls: Encryption Confusion and Padding Bugs

The most frequent security error regarding Base64 is treating it as encryption. Base64 is an open, deterministic encoding algorithm. It provides zero confidentiality. Anyone who intercepts a Base64 string can decode it instantly without a key. Storing passwords or private tokens in Base64 offers no more protection than storing them as plain text.

Another implementation bug stems from unhandled padding in streaming environments. If a network consumer processes chunks of Base64 data without buffering across the 4-character block boundary, decoding operations fail midway through the stream. Decoders must always process incoming characters in multiples of 4, or wait until the stream finishes to process remainder characters and padding markers.

Where transmission channels can handle raw octets, binary transports such as HTTP/2 framing, gRPC, and MessagePack avoid the 33% payload penalty altogether. Base64 remains the pragmatic bridge when binary data must traverse systems that only speak text.

References

  1. RFC 4648: The Base16, Base32, and Base64 Data Encodings
  2. RFC 2045: Multipurpose Internet Mail Extensions (MIME) Part One
  3. W3C: RFC 2397 The 'data' URL scheme
  4. MDN Web Docs: Base64