UrsaKit cover explaining how Base64 encoding turns bytes into printable characters

Base64 shows up everywhere - in email attachments, data URLs, JSON payloads, JWT segments and API fields that carry a file - and it is usually pasted around without anyone asking what it does. This guide explains the encoding itself: why it exists, the exact arithmetic, where padding comes from, and the cases where reaching for Base64 is the wrong move.

You can follow along in the UrsaKit Base64 encoder and decoder. Encoding and decoding run in your browser, so you can paste real tokens while you read.

01Why Base64 exists

Many transports were designed for text and only promise to carry a limited set of printable characters. Email bodies, HTTP headers, XML text nodes and URL paths all have characters they treat as special. A raw JPEG or a compressed archive contains every byte value from 0 to 255, including control characters and bytes that a text channel will happily mangle.

Base64 solves exactly one problem: it re-expresses arbitrary bytes using 64 printable characters that are safe in every text channel. RFC 4648 defines the scheme and notes that the 65th character, the equals sign, is used to signal padding rather than data.

A-Z  a-z  0-9  +  /      (standard alphabet, 64 symbols)
- and _ replace + and /   (URL-safe alphabet, RFC 4648 section 5)
= pads the final group

The characters were chosen because they survive mail gateways, HTML parsing and copy-paste. The alphabet is a transport vest, not encryption and not ASCII art.

02The 3-byte to 4-character rule

A byte holds 8 bits, so three bytes hold 24 bits. Sixty-four symbols represent log2(64) = 6 bits each. Four 6-bit symbols therefore hold exactly 24 bits. RFC 4648 section 4 states the same rule in one sentence: the encoding represents 24-bit groups of input bits as output strings of 4 encoded characters. That is the whole design: 3 bytes in, 4 characters out.

"Man"  ->  bytes 4D 61 6E  ->  010011 010110 000101 101110  ->  TWFu
"Ma"   ->  bytes 4D 61     ->  010011 010110 0001(00)      ->  TWE=
"M"    ->  bytes 4D        ->  010011 01(0000)             ->  TQ==

Those three examples were produced locally with Python 3 and the standard library base64 encoder on 2026-10-04. The trailing group has fewer than 24 bits, so the missing bits are set to zero and the unused characters are replaced with an equals sign. RFC 4648 section 3.2 requires that padding unless the referring specification says otherwise, and RFC 7515 is exactly that exception for JWT: it drops all trailing padding characters.

The practical consequence is the size overhead. RFC 2045 section 6.8, which defines Base64 for MIME email, puts the encoded data at "consistently only about 33 percent larger than the unencoded data". A real file shows the same ratio:

source file   63,728 bytes
base64 output 84,972 characters   ->  33.34% larger

That measurement is the UrsaKit cover publichttps://static.ursakit.com/media/861934dec6614009.jpg, encoded locally with Python on 2026-10-04. A 3 MB image therefore becomes about 4 MB of text, and that is before JSON quoting. If you control the endpoints, send the file as multipart/form-data or as raw bytes over HTTP instead.

03Padding and the URL-safe alphabet

Standard Base64 contains plus and slash. In a URL, plus decodes as a space and slash is a path separator; in a filename, slash is illegal. RFC 4648 section 5 defines a URL- and filename-safe alphabet that swaps them for hyphen and underscore:

Input bytesStandardURL-safe
`0xFB 0xFF``+/8=``-_8=`
`0xFF 0xEF``/+8=``_-8=`
string `Hi?``SGk/``SGk_`

All six values were produced locally with Python `base64.b64encode` and `base64.urlsafe_b64encode` on 2026-10-04. Padding is often dropped in URL-safe mode too, because a URL segment or a structured token can carry its own length. JWT uses the URL-safe alphabet without padding, which is why a header segment looks like `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` and never ends in an equals sign.

Standard and URL-safe alphabets are not interchangeable.

Decoding the URL-safe string -_8= with a standard decoder fails or returns the wrong bytes, and decoding +/8= with a URL-safe decoder has the same problem. Check which one the producer used before debugging anything else.

04Base64 is not encryption

Base64 has no key and no secret. Decoding is a table lookup that anyone can perform, so a Base64 string is best thought of as a different font for the same bytes.

Treat these as plaintext: a password "obfuscated" with Base64, an API key wrapped in Base64 inside a config file, a personal record hidden in a data URL. If the data needs to be unreadable, use real encryption - TLS on the wire, AES-GCM or an equivalent for data at rest - and remember that the ciphertext will itself be Base64-encoded when it is transmitted.

The same reasoning applies to integrity. Base64 detects no corruption beyond the alphabet check; nothing in the format tells you whether a byte was flipped in transit. RFC 4648 section 12 warns about the opposite direction as well: a decoder that silently ignores non-alphabet characters instead of rejecting them opens a covert channel that can be used to leak data. Reject invalid input, and use a hash or a MAC for integrity.

05Decoding: bytes first, text second

A decoder returns bytes, not a string. Turning those bytes into readable text is a second decision, and it is where "the decoded text looks like the wrong accented characters" comes from.

The visible character e-acute has different bytes depending on the encoding:

UTF-8 "e-acute"        -> bytes C3 A9 -> "w6k="
ISO-8859-1 "e-acute"   -> byte  E9    -> "6Q=="

Both strings are correct; they simply describe different byte sequences. Verified locally with Python on 2026-10-04. When decoding, choose the charset that the producer used. If you do not know it, try UTF-8 first: it is the default on the web, and its invalid-sequence rules make wrong guesses visible quickly.

Two more decoding traps:

  • Double encoding. Encoding an already-encoded string produces valid Base64 that decodes to another Base64 string. If the output looks like Base64, decode once more before assuming corruption.
  • Whitespace. Line breaks and spaces are ignored by most decoders, and RFC 2045 requires MIME encoders to wrap output at no more than 76 characters per line. A strict decoder that rejects a newline needs the wrapping stripped first.

06How to recognize Base64 in the wild

That same dense, space-free alphabet is a useful signal when you are reading a log, a config file or a support ticket and something looks like a wall of random characters. Four checks separate a real Base64 blob from a random token:

  1. Alphabet. Every character is an upper or lower case letter, a digit, and either plus slash (standard) or hyphen underscore (URL-safe). A string containing spaces, punctuation or non-ASCII is not Base64.
  2. Length. With padding, the length is a multiple of four. Without padding, it is a multiple of four minus 0, 1 or 2 characters. Anything else is truncated or mis-copied.
  3. Padding. Zero, one or two equals signs at the end, never in the middle.
  4. Context. A data URL starts with a media type and the marker `base64`, and a JSON Web Token is three Base64url segments joined by dots. MDN documents the data URL syntax as `data:[<mediatype>][;base64],<data>`.

These checks tell you a string might be Base64. They do not tell you what is inside or whether it is malicious - a decoded blob can be an image, a compressed archive, or a payload someone encoded to slip past a filter. Decode it in a tool you trust, and never execute the result as code.

07Mistakes that break decoders

SymptomCauseFix
Invalid character on position 0Input starts with a data URL prefix such as data:image/png;base64,Strip everything up to and including the comma
Decoded image will not openString was truncated by a copy that dropped the tail, or URL-safe characters were replaced by plus slashRe-copy the full string; check the alphabet
Output is gibberishWrong charset for the decoded bytesDecode as UTF-8, or select the original charset
Padding error on a JWT segmentJWT omits the equals paddingAdd padding to a multiple of 4 before decoding
Size grew and the API rejected itBase64 was sent where raw bytes were expectedSend the file as a multipart upload

08When to use something else

NeedBetter than Base64
Send a file between servicesmultipart/form-data or a raw binary body
Store bytes inside JSONBase64 is the standard answer; there is no binary literal in JSON
Keep a secret unreadableAES-GCM or another authenticated cipher
Encode arbitrary bytes in a URL pathPercent-encoding for short values, Base64url for opaque blobs
Compare or index bytesHex - twice as long, but readable and sortable

09Encode and decode in the browser

The UrsaKit Base64 tool covers the workflow end to end without uploading anything:

  1. Pick a direction - Encode or Decode. The workspace switches to the matching input mode.
  2. Choose text or a file - Paste text, or drop a file such as an image or a PDF. File input avoids the copy-paste truncation that breaks long strings.
  3. Set the alphabet options if you need them - URL-safe replaces plus and slash, wrap at 76 inserts MIME line breaks, and live mode re-encodes as you type.
  4. Check the charset on decode - Auto-detect recognises a plain Base64 string or a full data URL; if the text looks wrong, switch between UTF-8 and single-byte charsets.
  5. Copy or download the result - The decoded side can be saved as a file, which is the reliable way to recover an encoded image.

Once the string is correct, the rest of the pipeline is usually JSON - check it with the JSON formatter before it ships.

10Sources

Frequently asked questions

Does Base64 make my data smaller?

No, it makes it larger. Every 3 bytes become 4 characters, so RFC 2045 describes the encoded data as consistently about 33 percent larger before transport compression.

Is Base64 encryption?

No. Base64 is a reversible encoding with no key. Anyone who has the string can decode it, so it must never be used to protect passwords, tokens or personal data.

Why does my Base64 string end with one or two equals signs?

The equals signs pad the final group up to four characters. RFC 4648 requires padding in the general case, because it lets a decoder know exactly how many bits were in the last group. Inputs whose length is divisible by three need no padding.

Why did decoding produce odd characters like the accented e?

The bytes were decoded as UTF-8 but were originally written in a single-byte encoding such as ISO-8859-1, or the other way round. Pick the charset that matches the source.

When should I use URL-safe Base64?

Whenever the result travels inside a URL, a filename, a query string or a JWT. RFC 4648 section 5 replaces the standard plus and slash with hyphen and underscore; RFC 7515 additionally drops the trailing padding for JWT segments.

Do the line breaks in a Base64 file matter?

Not to a decoder, but RFC 2045 requires MIME base64 output to be wrapped at no more than 76 characters per line. Wrapping is a transport convention, not part of the encoding, and decoders ignore the line breaks.

How can I tell whether a string is Base64?

Look for the alphabet (upper and lower case letters, digits, and plus slash or hyphen underscore), a length that is a multiple of four when padding is present, and a total absence of spaces. A JWT header segment usually starts with eyJ, which is the base64url of the opening characters of a JSON object.

Back to articles

Related articles

Related tools