
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.
| Encoding | Value 62 | Value 63 | Padding | Section |
|---|---|---|---|---|
| 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 digits0and1, which helps avoid confusing them withOandIorL. The lettersI,L,O, andSare 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 case | Typical form | Basis |
|---|---|---|
| Email attachment | Standard Base64 with 76-character line limit | RFC 2045 MIME |
| HTTP Basic credentials | Standard Base64 | RFC 7617 |
| URL query or path | Base64URL; follow the enclosing specification for padding | RFC 4648 §3.2 and §5 |
| JWS, JWE, or JWT | Unpadded Base64URL | RFC 7515, RFC 7516, and RFC 7519 |
| Filename | Base64URL | Avoids the slash used by standard Base64 |
| Data URL | Standard Base64 | RFC 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
- RFC 4648 — The Base16, Base32, and Base64 Data Encodings ↗
- RFC 7515 — JSON Web Signature (JWS) ↗
- RFC 7516 — JSON Web Encryption (JWE) ↗
- RFC 7519 — JSON Web Token (JWT) ↗
- RFC 2045 — Multipurpose Internet Mail Extensions (MIME) ↗
- WHATWG URL Standard — application/x-www-form-urlencoded parsing ↗
- RFC 7617 — The Basic HTTP Authentication Scheme ↗
- RFC 2397 — The data URL scheme ↗
- Python documentation — Base64 encodings ↗
- OpenSSL documentation — openssl-enc ↗
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.

