Skip to content
Encoding2026-08-286 min read

Base64 Decode Shows Garbled Text or Black Question Marks for Chinese — How to Fix

Symptom: garbage or question marks after decoding

You feed a Base64 string into a decoder expecting Chinese, but get:

中文

or a row of black question-mark boxes �, sometimes �. This means the decoding succeeded, but the bytes were interpreted with the wrong character set.

Root cause: Base64 encodes bytes, not characters

This is the trap almost everyone hits. Base64 only translates a byte sequence into printable characters — it has no idea what text those bytes represented.

  • Chinese is usually 3 bytes in UTF-8, but 2 bytes in GBK;
  • If the encoder turned "你好" into bytes using GBK, but the decoder reads those bytes as UTF-8, you get mojibake;
  • And vice versa.

So the garble is not a Base64 problem — it is an encoding mismatch between the encode and decode ends.

How to fix it

Method 1: online tool (auto UTF-8 by default)

Open the Base64 Encode/Decode tool on ToolVault:

  1. Paste the content into the input;
  2. The tool restores bytes to characters as UTF-8 by default, so Chinese displays correctly;
  3. If the original data was GBK, switch the charset option before decoding;
  4. Everything runs locally in your browser — no upload, safe for sensitive strings.

Method 2: command line

# Standard decode (shows Chinese when terminal locale is UTF-8)
echo '5ZG95ZG9' | base64 -d
# output: 你好

# Force UTF-8 if the locale is off
echo '5ZG95ZG9' | base64 -d | iconv -f UTF-8 -t UTF-8

Method 3: specify the charset explicitly in code

import base64
raw = base64.b64decode("5ZG95ZG9")
text = raw.decode("utf-8")   # be explicit, don't rely on defaults
print(text)                  # 你好
// Browser / Node: TextDecoder with explicit encoding
const bytes = Uint8Array.from(atob('5ZG95ZG9'), c => c.charCodeAt(0));
const text = new TextDecoder('utf-8').decode(bytes);
console.log(text); // 你好

Why you should not "guess" the encoding

When mojibake appears, some people try GBK, GB2312, UTF-8 one by one. It偶尔 works on tiny samples but is unreliable in production:

  • Mixed Chinese/English data may look "mostly fine" with a few boxes — very hard to debug;
  • The correct approach is to fix and declare the charset at the encode end (UTF-8 is the default best practice) and keep the decode end consistent.

Tracing the mojibake to its hop

Garbled Chinese after Base64 round-trip is always a UTF-8 assumption breaking somewhere. Locate the hop by what the garbage looks like: 中文 (ä-style) means UTF-8 bytes were interpreted as Latin-1 somewhere — the classic Java new String(bytes) default-charset trap or an HTTP header declaring ISO-8859-1. 涓枃 (E6B8-style) is the reverse: GBK bytes read as UTF-8. ??? means lossy conversion already happened — the original bytes are gone and no re-decode can recover them. Each pattern indicts a specific layer (transport, storage, or display), which is why the fix starts with identifying the pattern, not with changing encodings at random.

The three checkpoints that catch 90% of cases

(1) Encode side: TextEncoder in JS, -E UTF-8 in iconv, charset declared in the HTTP header — one declaration, checked once. (2) Transport: URLs force percent-encoding of non-ASCII, so Base64-with-Chinese in a query string needs encodeURIComponent — forgetting it silently decodes to garbage on the server. (3) Decode side: the mirror of encode — atob gives Latin-1 bytes in JS, so the correct decode is TextDecoder on the Uint8Array, not string concatenation. The Base64 tool does the TextDecoder path correctly and shows the round-trip — a 10-second differential against your own pipeline isolates the broken hop.

When the data is already garbled

If ??? or U+FFFD replacement characters have replaced the original bytes, the information is destroyed — set expectations accordingly. If the damage is the ä-style double-encoding, recovery is mechanical: encode the string as Latin-1 bytes, decode those bytes as UTF-8. That one-liner has rescued countless mis-migrated databases.

Prognosis differs by corruption source

Not all mojibake is equal — where the bytes got mangled determines whether recovery is possible:

SourceSignatureRecoverable?
Transport (charset header mismatch)consistent pattern, whole documentYes — re-decode with the right charset
Storage (DB column truncation)cut at a byte boundary, tail missingNo — data past the cut is gone
Generation (double-encoding)w6E-style sequences appearing twiceYes — decode repeatedly until stable

The diagnostic shortcut: garbled throughout means transport (fixable), garbled ending means truncation (lost), garbled stacked means double-encoding (fixable). Triage before promising anyone anything.

Do not confuse charset mojibake with Base64URL / PEM dialect errors

Three failure classes look similar in a chat screenshot and need different first moves:

What you seeLikely classFirst move
中文, 渭, or � after a successful decodeCharset (UTF-8 / GBK / Latin-1)This article
Decoder throws / refuses alphabet with - _ or missing =Base64URL vs standardBase64 vs Base64URL, toggle URL-safe
String has two dots (a.b.c)JWT segmentsJWT decoder — do not MIME-decode the whole token
BEGIN CERTIFICATE / broken after “removing newlines”PEM packagingCertificate decoder

If the Base64 layer already produced the correct UTF-8 bytes and the UI still shows garbage, the bug is downstream (terminal locale, HTTP charset, DB column collation) — not another Base64 pass.

FAQ

What are the black boxes (�)?

That is the Unicode replacement character U+FFFD, meaning the decoder hit a byte it could not represent in the current charset. It is a charset mismatch, not data loss.

I typed Chinese when encoding, why is decoding garbled?

When you "type Chinese", the program first turns it into bytes using some charset, then Base64. If that charset differs from the decoder's, it breaks. Standardizing on UTF-8 eliminates this class of bugs.

Are online tools safe?

If you use a local, no-upload tool (like the ToolVault one above), data is processed only in your own browser and never leaves your device — safe for sensitive strings.

Is garbled Chinese the same as a JWT that “won’t decode”?

Usually no. JWT failures are alphabet/padding/segment issues (Base64URL). Chinese mojibake means the bytes after Base64 were read with the wrong charset. Fix the dialect first, then the charset — in that order.

Implementation notes

For Base64url debugging such as JWTs, the site's decoder accepts a deep link — /en/tools/jwt-decoder?token=... — so you can jump in from api-tester or other tools with the token in hand instead of copying back and forth. The parameter is read exactly once on mount; after that the input belongs entirely to the user, so a deep link and a manual paste never overwrite each other.


Provided by ToolVault. Related tools: URL Encode/Decode, JWT Decoder, Regex Tester. Visit the home page for more developer tools.