What is Base64? Encoding Explained

Base64 is a way of representing binary data — images, files, encrypted bytes, anything — using only 64 printable characters (A–Z, a–z, 0–9, + and /). It exists because many channels were designed for text: email, JSON, XML, URLs, HTTP headers and cookies can all carry a Base64 string safely where raw bytes would be corrupted or rejected.

This guide explains how the encoding works, when it is the right choice, the difference between Base64 and Base64URL, and the mistakes that produce garbled output — including the most important one: Base64 is not encryption.

How Base64 encoding works

Base64 takes the input three bytes (24 bits) at a time and splits them into four groups of six bits. Each six-bit value (0–63) is mapped to one character of the alphabet. Three bytes in, four characters out — which is why Base64 output is always about 33% larger than the input.

When the input length is not a multiple of three, the last group is padded with zero bits and one or two "=" characters are appended so the decoder knows how many real bytes to expect.

Three bytes are 24 bits, read again as four 6-bit groups: four characters out. A short last group is filled with zero bits and "=" is appended.

Hi! — 3 bytes, no padding

Text

H i !

Bytes

01001000 01101001 00100001

Groups

010010 000110 100100 100001

Index

18 6 36 33

Base64

S G k h

Hi — 2 bytes, one "=" of padding

Text

H i

Bytes

01001000 01101001 00

Groups

010010 000110 100100

Index

18 6 36

Base64

S G k =

Solid lines mark byte boundaries, dashed lines the 6-bit groups. Faded cells are not from the input: the zero bits and "=" of padding, and the empty slot of the missing byte.

Text version
Text:   Hi!
Bytes:  01001000 01101001 00100001
Groups: 010010 000110 100100 100001
Index:  18     6      36     33
Base64: S      G      k      h        → "SGkh"

Text:   Hi      (2 bytes → one "=" of padding)
Base64: SGk=

Try it: Base64 Encoder Try it: Binary Encoder / Decoder

Base64 is not encryption

Anyone can decode Base64 instantly with no key — it is a reversible encoding, like writing a number in hexadecimal. It hides nothing. A password, API key or token that is "protected" with Base64 is exposed to anyone who sees it. Use real encryption (AES, TLS) to protect data and Base64 only to transport it.

Try it: Base64 Decoder

When to use Base64

  • Embedding small images or fonts in HTML, CSS or JSON as data: URIs.
  • Sending binary content inside a JSON or XML payload, which can only hold text.
  • HTTP Basic authentication: the header carries Base64 of "username:password" (transport only — always over HTTPS).
  • JWT and other tokens, which use the URL-safe variant for their header, payload and signature.
  • Email attachments (MIME) and any legacy protocol that expects 7-bit text.
  • Not for large files where a binary channel exists — the 33% overhead and the extra encoding step are pure waste.

Base64 vs Base64URL

Standard Base64 uses "+" and "/", both of which have special meaning in URLs, and "=" padding, which is often percent-encoded. Base64URL (RFC 4648 §5) swaps them for "-" and "_" and usually drops the padding, so the result can be used in a URL, a file name or a JWT without further escaping. A decoder that expects one variant will reject the other, which is the most common reason a "valid" token fails to decode.

Try it: URL Encoder Try it: JWT Decoder

Encoding text: watch the character set

Base64 encodes bytes, not characters, so text must be turned into bytes first — almost always UTF-8. Arabic, Hindi and emoji become multi-byte sequences; if the encoder uses UTF-8 and the decoder assumes Latin-1 (or the reverse) the output is mojibake. When two systems disagree about a Base64 string, the character set is the first thing to check.

Try it: Base64 Encoder Try it: Hex Encoder

Why decoding fails

  • Wrong alphabet — a Base64URL string ("-", "_") fed to a standard decoder, or vice versa.
  • Missing or extra padding — some encoders omit "=", and strict decoders require a length that is a multiple of four.
  • Whitespace or line breaks inside the string — MIME wraps lines at 76 characters; strip them before decoding.
  • Double encoding — a string that was Base64-encoded twice looks valid but decodes to more Base64.
  • It was never Base64 — hex, a hash or a random token can look similar. Base64 uses exactly the 64 characters plus "=", and its length is a multiple of four when padded.

Try it: Base64 Decoder

Frequently asked questions

Is Base64 secure?

No. It is a reversible encoding with no key. It provides no confidentiality whatsoever; use encryption for that.

Why does Base64 end with "=" or "=="?

Padding. The input is processed in groups of three bytes; when one or two bytes are left over, "==" or "=" is appended so the decoder knows the true length.

How much bigger is Base64 output?

About 33% — every 3 bytes become 4 characters. A 300 KB image becomes roughly 400 KB of text.

Can Base64 encode an image or a PDF?

Yes — Base64 works on any bytes. That is how data: URIs embed images in HTML and how files travel inside JSON APIs.

What is the difference between Base64 and hex?

Both represent bytes as text. Hex uses 16 characters and doubles the size (2 characters per byte); Base64 uses 64 characters and grows the size by a third. Hex is easier to read; Base64 is more compact.

What is the difference between Base64 and URL encoding?

URL (percent) encoding escapes only the characters that are unsafe in a URL, leaving ordinary letters untouched. Base64 re-encodes every byte. They are often combined: a Base64 string may need percent-encoding before it goes into a query parameter, unless the URL-safe variant is used.

References

Practice exercise

Diagnose Base64 problems in a document upload API

Your task

You test the Documents service of a government e-services platform (fictional) before release 3.2. Applicants attach supporting documents in the mobile apps, which send each file inside a JSON request as a Base64 string; a partner travel agency’s system calls the same API directly with HTTP Basic authentication; and the receipt e-mail carries a link with a Base64 reference. The material below gives the requirement, an extract of the API contract, two platform notes and six observations collected on staging.

For each observation, decide whether it shows a defect, explain it with the Base64 rule behind it, and say where the fix belongs; then design the tests that would catch these problems next time. Work with pen and paper, the Base64 encoder and decoder (the decoder’s "Decode text as" setting offers Latin-1), the URL decoder and a boundary value analysis tool. Allow 40–45 minutes; the size calculation needs only multiplication and division.

Material
Government e-services platform (fictional) - Documents service, release 3.2
System testing on staging

Requirement DOC-4
  An applicant can attach a PDF, JPG or PNG file of up to 5 MB (5,000,000 bytes)
  to an application. A larger file is rejected with HTTP 413 and the error
  FILE_TOO_LARGE ("File is larger than 5 MB").

API contract (extract)
  POST /api/v3/applications/{id}/documents       Content-Type: application/json
    content  the file, Base64 (RFC 4648, standard alphabet, with padding)
    success  201 with the document id
    errors   400 INVALID_BASE64, 413 FILE_TOO_LARGE
  GET /api/v3/receipts?ref={ref}                 ref = 12 random bytes, Base64
  Callers: the iOS and Android apps (they check the 5 MB limit before uploading)
           and a partner travel agency’s system (HTTP Basic authentication)

Platform notes
  The API gateway rejects any request body larger than 6,000,000 bytes with its
  own HTTP 413 HTML page, before the request reaches the Documents service.
  Gateway logging is configured the same way on staging and production.

Sample upload (Base64 value shortened)
  {"fileName":"family-book.pdf","mimeType":"application/pdf","content":"JVBERi0xLjcK..."}
  Without the Base64 value, this body is 72 bytes.

Observations
O1  family-book.pdf, 4,800,000 bytes, passes the app’s check: HTTP 413 and the
    gateway’s HTML page. birth-certificate.pdf, 4,200,000 bytes: HTTP 201.
O2  Android app: every upload fails with 400 INVALID_BASE64; the same files
    upload from iOS. In the captured Android request, "content" has a \n after
    every 76 characters.
O3  Receipt links from two e-mails:
      GET /api/v3/receipts?ref=Lm0x8Qp/Vt2aKc3Q   200, receipt downloaded
      GET /api/v3/receipts?ref=k7Qd2+fA9w1Z0LsE   400 "Invalid reference"
    Service log for the second request: ref = "k7Qd2 fA9w1Z0LsE"
O4  Gateway access log, partner call:
      POST /api/v3/applications 201  Authorization: Basic YWdlbmN5MDc6VmlzYSNUZXN0MjY=
    Developer’s comment: "Fine to keep - the header is encoded, nobody can read it."
O5  The apps send the document owner’s name in a header, Base64-encoded because
    header values should stay ASCII:   X-Doc-Owner: 2LPYp9ix2Kk=
    The back-office review screen shows the owner as:   سارة
O6  A tester’s note on three successful uploads: "content looks corrupted - there
    are extra = signs at the end and the sizes do not match the files."
      file                     size (bytes)   content (characters)   ends with
      passport-photo.png             48,211                 64,284   ==
      salary-certificate.pdf        120,005                160,008   =
      lease-contract.jpg             96,300                128,400   (no =)
Steps
  1. Decode every Base64 value in the material and write down what each one contains.
  2. For O1, compute the Base64 length and the request body size of the largest allowed file, then the largest file the gateway lets through. Say which valid files fail and what DOC-4 expects instead.
  3. For O2 to O6, decide: defect or not, the Base64 rule that explains it, and where the fix belongs (Android app, the code that builds the link, gateway, back office, or nowhere).
  4. Derive test values for the file size with 2-value boundary value analysis (test each boundary value and its closest neighbour in the adjacent partition: here the largest size that is accepted and the next byte up), covering both the requirement boundary and the gateway boundary, and add sizes that exercise each padding case.
  5. List negative tests for the content field and the receipt reference with their expected results, and the questions you would send to the API owner.
Show the model answer
Model answer

Decoded values. JVBERi0xLjcK decodes to %PDF-1.7 followed by a line break: the header a PDF file starts with, so content really carries the file bytes. YWdlbmN5MDc6VmlzYSNUZXN0MjY= decodes to agency07:Visa#Test26, the partner’s user name and password. 2LPYp9ix2Kk= decodes to the Arabic name سارة (Sara) when its bytes D8 B3 D8 A7 D8 B1 D8 A9 are read as UTF-8, and to exactly سارة when the same bytes are read as Latin-1. The two receipt references decode to 12 random bytes each; nothing readable is expected in them.

O1: defect in the gateway configuration. Base64 turns every 3 bytes into 4 characters and rounds the last group up. The largest allowed file, 5,000,000 bytes, is 1,666,667 groups, so content is 6,666,668 characters (with one "=" of padding) and the body is 6,666,668 + 72 = 6,666,740 bytes, above the gateway’s 6,000,000. The largest file that passes: 6,000,000 − 72 = 5,999,928 characters are left for content; 5,999,928 ÷ 4 = 1,499,982 whole groups; 1,499,982 × 3 = 4,499,946 bytes. With the sample’s 72 bytes of JSON, every valid file from 4,499,947 to 5,000,000 bytes is therefore rejected (family-book.pdf at 4,800,000 bytes needs a 6,400,072-byte body, while birth-certificate.pdf at 4,200,000 bytes encodes to 5,600,000 characters and fits), and the applicant gets the gateway’s HTML page instead of a usable error. Files over the limit get the wrong response too: a 5,000,001-byte file sent by the partner’s system, which has no app check in front of it, never reaches the service, so the FILE_TOO_LARGE error that DOC-4 requires is never returned.

In ISTQB terms the failure is the 413 for a valid file; the defect is a body limit sized for the raw file instead of the encoded request. Report it against the gateway configuration, with DOC-4 as the test basis. How to fix it is the team’s decision: raise the limit above the largest encoded body, with room for long file names, or upload files as binary (for example multipart/form-data) instead of Base64 in JSON, which also removes the overhead of a third. Add one note for the developers: 5,000,000 and 5,000,001 bytes both encode to 6,666,668 characters, so the size rule must be checked on the decoded bytes, never on the length of the Base64 string.

O2: defect in the Android app. A line break every 76 characters is MIME-style wrapping. RFC 4648 §3.1 says encoders must not add line breaks unless the specification that uses Base64 asks for them, and the contract does not; §3.3 says decoders must reject characters outside the alphabet unless the specification says otherwise. So the service’s 400 INVALID_BASE64 is correct, and iOS shows that the files themselves are fine. You can see the two kinds of decoder side by side: encode a small file with the Base64 encoder’s "Line wrap" set to "76 chars (MIME)" and paste the result into the Base64 decoder, which is tolerant: it ignores the line breaks and reports how many whitespace characters it skipped. The Documents service is strict, as the RFC requires by default. Fix: the Android encoder must produce the value on one line. Ask the API owner to write "one line, no whitespace" into the contract.

O3: defect in how the receipt link is built. The second reference contains "+". The link puts standard Base64 into the query string without percent-encoding, and the service reads the query string with form-encoding rules, where "+" stands for a space; it receives k7Qd2 fA9w1Z0LsE and rejects it. The URL decoder reproduces this: with "Decode + as space" on, k7Qd2+fA9w1Z0LsE comes out as k7Qd2 fA9w1Z0LsE. The first reference works only because it has no "+"; its "/" is legal in a query string but would break the route if the reference ever moved into the path. Because the references are random bytes, whether a link fails looks random too, which is why this kind of defect is often reported as intermittent. Fix: Base64URL (RFC 4648 §5) for the reference, so that k7Qd2-fA9w1Z0LsE and Lm0x8Qp_Vt2aKc3Q need no escaping, or percent-encoding the value so that "+" becomes %2B (the URL encoder shows it), which is easier to forget. Links already sent by e-mail must keep working after the fix: ask how old references will be accepted.

O4: security defect, and the most serious of the six because it exposes credentials. The header decodes in seconds to agency07:Visa#Test26, so the gateway log keeps the partner’s password in readable form for anyone who can read the logs, and production logs the same way. The developer’s comment is wrong: Base64 is an encoding with no key, not encryption, and HTTPS protects Basic credentials only in transit, not after they are written to a log. Report it with high severity, and in the report give the user name but mask the password. Expected fix: mask or drop the Authorization header in the gateway log and in any other log or monitoring tool that records headers, and rotate the exposed credential. Confirmation test: repeat the partner call and check that the log shows no credential; as regression tests, check the other headers that carry secrets, such as API keys and Bearer tokens.

O5: defect in the back office. The apps are right: 2LPYp9ix2Kk= is the UTF-8 encoding of سارة (4 characters, 8 bytes). The back office turns the decoded bytes into text with Latin-1, one character per byte, so D8 becomes Ø and B3 becomes ³, which gives سارة; setting the Base64 decoder’s "Decode text as" to "Latin-1 / binary string" reproduces it exactly. A run of Ø or Ù, each followed by another symbol, is the typical sign of Arabic UTF-8 read as Latin-1. Fix: read the decoded bytes as UTF-8. Test data for the confirmation test and the regression suite: an Arabic name, a Hindi name in Devanagari (3 bytes per character in UTF-8), a Latin name with an accent such as José (é is 2 bytes), a name with an emoji if the field allows it (4 bytes), and an ASCII-only name, which must stay unchanged.

O6: not a defect. Padding follows the file size, because Base64 works in groups of 3 bytes: 48,211 leaves a remainder of 1 when divided by 3, so the value ends with "=="; 120,005 leaves 2, so it ends with "="; 96,300 leaves 0, so there is no padding. (A quick check: a number leaves the same remainder when divided by 3 as the sum of its digits.) The lengths are 4 × (size ÷ 3, rounded up): 16,071 × 4 = 64,284, 40,002 × 4 = 160,008 and 32,100 × 4 = 128,400, about a third more than each file, as expected. Close the note as not a defect with this calculation, and keep the three files: together they cover every padding case.

Test values for the file size (2-value BVA). Requirement boundary: 5,000,000 bytes, accepted with 201, and 5,000,001 bytes, rejected with 413 and FILE_TOO_LARGE; send both directly to the API, as the partner’s system does, and not only through the app. Gateway boundary for the sample file name, a technical boundary that the requirement does not mention: 4,499,946 bytes (body exactly 6,000,000, accepted) and 4,499,947 bytes (body 6,000,004, rejected by the gateway today, accepted once the defect is fixed). Padding cases: three sizes whose remainders when divided by 3 are 0, 1 and 2, such as the O6 files. Also run the gateway boundary with the longest file name allowed, because every extra byte of JSON lowers the largest file that fits, and ask what DOC-4 expects for an empty file. Keep 4,499,947 and 5,000,000 in the regression suite after the fix.

Negative tests for content, each expecting 400 INVALID_BASE64 and nothing stored, as the contract reads today: line breaks or spaces inside the value; the Base64URL characters "-" or "_"; a character outside the alphabet such as "*"; a length that is not a multiple of 4 (padding removed); and an "=" in the middle of the value. A double-encoded file is valid Base64 whose decoded bytes are Base64 text (it starts with SlZCRVJp instead of JVBERi0): it should be rejected as not a PDF if the service checks the file content against mimeType, so ask whether it does. For the receipt reference: references containing "+" and "/", and "=" if references can change length; a reference with a space; a percent-encoded one (%2B); and a well-formed but unknown reference, which must return a client error, never a 500 (the extract does not say which one, so ask).

Questions for the API owner: Should the service accept or reject missing padding and whitespace, and can the contract say so explicitly? What is the longest file name allowed, since it moves the gateway boundary? Does the service check that the decoded bytes match mimeType? Will receipt references switch to Base64URL, and how will links already sent keep working? And for files of up to 5 MB, is Base64 inside JSON the right channel at all, given its overhead of a third?

If your numbers differ, check that you rounded the number of groups up for the encoded length and down for the largest file that fits, and that you added the 72 bytes of JSON.

Your progress on this topic

Part of the QA Roadmap

Tools mentioned in this guide

Encode text or files to Base64 (standard or URL-safe) in your browser.

Encoding

Decode Base64 or Base64url strings to text, with automatic padding repair.

Encoding

Percent-encode text for safe use in URLs, query strings and form data.

Encoding

Convert text to hexadecimal bytes (UTF-8) with custom separators and casing.

Encoding

Decode JWT header and payload, inspect claims and check expiration — entirely in your browser.

Security

More guides

More guides