Base64 vs. Base64URL: Padding, URLs, and RFC 4648 Pitfalls
DDEVELOPER
DeveloperPublished: 7 min read

Base64 vs. Base64URL: Padding, URLs, and RFC 4648 Pitfalls

RFC 4648 defines Base64 as a way to represent binary data with printable ASCII characters. It appears in JWS and JWT data, email attachments, and data URLs. The subtle part is context: embedding ordinary Base64 directly in a URL or filename can require escaping +, /, or =. This guide separates standard Base64 from the base64url alphabet and explains three common interoperability traps.

Japanese original published: 2026-04-19

How Base64 works

Base64 divides binary input into six-bit values and maps each value to one of 64 printable characters.

  • Input: an arbitrary sequence of bytes
  • Output alphabet: A-Z a-z 0-9 + /, plus the padding character =

Three input bytes contain 24 bits, which become four six-bit values and therefore four ASCII characters. With padding, the encoded length is 4 × ceil(input bytes / 3) characters. For sufficiently long input, this is about four-thirds of the original byte count. Four output characters are not a 24-bit storage representation: storing or transmitting the text normally uses at least one byte per ASCII character. If the input length is not divisible by three, one or two = characters complete the final four-character block.

// "ABC" (3 bytes) → "QUJD" (4 characters)
// "AB"  (2 bytes) → "QUI=" (one padding character)
// "A"   (1 byte)  → "QQ==" (two padding characters)

The two alphabets in RFC 4648

RFC 4648 specifies two related Base64 alphabets.

EncodingValue 62Value 63PaddingSection
Standard Base64+/=§4
Base64URL (base64url)-_May be omitted when the referring specification permits it§5

Base64URL replaces the standard alphabet's + and / with the URI unreserved characters - and _. A plus sign does not become a space under the general URL syntax. That conversion happens when a query is parsed as application/x-www-form-urlencoded. A slash is not always a path separator inside a query either, but it conflicts with URL structure in path segments and with conventions in filenames.

Pitfall 1: Putting standard Base64 directly into a URL

Directly concatenating standard Base64 into a URL is unsafe. The characters that need special handling depend on whether the value appears in a query, a path, a form-encoded payload, or another URL component.

// A form-style query parser may turn "+" into a space
https://example.com/api?token=A/B+C=

// Percent-encode the value if standard Base64 must be used
https://example.com/api?token=A%2FB%2BC%3D

// URLSearchParams applies form-style encoding to the value
new URLSearchParams({ token: "A/B+C=" }).toString();
// "token=A%2FB%2BC%3D"

When you control the data format, Base64URL can reduce the amount of percent-encoding required. Padding is a separate decision. RFC 7515 defines JWS base64url values with all trailing = characters omitted. JWTs are represented as JWS or JWE objects, and JWE uses the same unpadded base64url convention.

// JavaScript example
// btoa() accepts a binary string whose code units represent individual bytes,
// not an arbitrary Unicode string. Encode Unicode text to UTF-8 bytes first.
// Input containing "/" or "+" does not guarantee either character in the output;
// they appear only when a six-bit value is 63 or 62.
const standard = btoa("Subjects?");        // "U3ViamVjdHM/"
const urlSafe = standard.replace(/+/g, "-")
                        .replace(///g, "_")
                        .replace(/=+$/, ""); // "U3ViamVjdHM_"

Pitfall 2: Assuming padding is always optional

RFC 4648 §3.2 sets the default rule: an encoder adds the appropriate padding unless the referring specification explicitly says otherwise. Section 5 notes that padding can be avoided when the data length is known implicitly. Therefore, “Base64URL never has padding” is not a general rule; an agreement or a specification such as JWS must establish that convention.

JWS, JWE, and JWT use unpadded base64url by specification. A decoder can restore the missing padding from the length, but an input whose length modulo four is one cannot be valid base64url.

// Accept padded or unpadded input and require a canonical encoding.
function decodeBase64Url(value) {
  const match = /^([A-Za-z0-9_-]*)(={0,2})$/.exec(value);
  if (!match) {
    throw new Error("Invalid base64url");
  }

  const body = match[1];
  const explicitPadding = match[2];
  if (body.length % 4 === 1) {
    throw new Error("Invalid base64url length");
  }

  const requiredPadding = (4 - body.length % 4) % 4;
  if (explicitPadding.length !== 0 && explicitPadding.length !== requiredPadding) {
    throw new Error("Invalid base64url padding");
  }

  const standard = body.replace(/-/g, "+").replace(/_/g, "/")
                       + "=".repeat(requiredPadding);
  const decoded = atob(standard); // Binary string; decode UTF-8 separately.
  const canonical = btoa(decoded).replace(/\+/g, "-").replace(/\//g, "_")
                                 .replace(/=+$/, "");
  if (canonical !== body) {
    throw new Error("Non-canonical base64url");
  }
  return decoded;
}

This example accepts either unpadded input or input with exactly the required number of trailing = characters. Re-encoding the decoded bytes also rejects non-canonical encodings whose unused pad bits are not zero. Conversely, a decoder designed for standard padded Base64 may reject unpadded input. Make the padding convention explicit at both ends of an interface.

Pitfall 3: Confusing MIME Base64 with RFC 4648 output

RFC 2045 specifies MIME Base64 with encoded lines no longer than 76 characters, separated by CRLF.

RFC 4648, by contrast, says encoders must not add line feeds unless a referring specification directs them to do so. Python's base64.encodebytes() and the OpenSSL base64 command can produce wrapped output. Check whether the receiving API accepts line breaks, and suppress wrapping at generation time when it requires a single line. Decoders also differ in how they handle characters outside the alphabet, so stripping whitespace afterward is not a universal interoperability rule.

# Python: single-line output
import base64
encoded = base64.b64encode(data).decode()
# encodebytes() uses MIME-style wrapping.
# encodestring() was removed in Python 3.9.

// Node.js: single-line output
const encoded = Buffer.from(data).toString("base64");

# OpenSSL: -A suppresses line wrapping
openssl base64 -A -in input.bin

Other encodings in RFC 4648

  • Base32 (§6): A-Z 2-7. It excludes the digits 0 and 1, which helps avoid confusing them with O and I or L. The letters I, L, O, and S are still part of the alphabet.
  • Base32 Extended Hex (§7): 0-9 A-V. Encoded strings preserve the bitwise sort order of the underlying data.
  • Base16 (§8): hexadecimal encoding using 0-9 A-F.

For long input, Base64 produces about 4/3 as many characters as input bytes, Base32 about 8/5, and Base16 twice as many. These encodings do not compress data. Choose one according to the permitted alphabet, interoperability requirements, readability, and length overhead.

Choosing the right form

Use caseTypical formBasis
Email attachmentStandard Base64 with 76-character line limitRFC 2045 MIME
HTTP Basic credentialsStandard Base64RFC 7617
URL query or pathBase64URL; follow the enclosing specification for paddingRFC 4648 §3.2 and §5
JWS, JWE, or JWTUnpadded Base64URLRFC 7515, RFC 7516, and RFC 7519
FilenameBase64URLAvoids the slash used by standard Base64
Data URLStandard Base64RFC 2397

Base64 is not encryption. It provides no confidentiality and can be decoded by anyone. Do not use it to protect passwords, API keys, personal data, or other secrets.

Summary

  • RFC 4648 defines standard Base64 in §4 and the base64url alphabet in §5.
  • Base64URL replaces + and / with - and _; padding may be omitted only when an agreement or referring specification permits it.
  • A plus sign becomes a space during form-style parsing, not under URL syntax in general.
  • JWS, JWE, and JWT use unpadded base64url.
  • MIME Base64 has a 76-character line limit, while RFC 4648 output is not wrapped by default.
  • Base64 is encoding, not encryption.

References and sources

Editorial note

This article was prepared with AI assistance and reviewed by an editor before publication. It may still contain factual errors, interpretation mistakes, or outdated information. Check the cited primary sources or official documentation before making an important decision.

Related articles