Base64 Encode / Decode
Encoding, not encryption. The difference matters.
Runs locallySGVsbG8sIOWPsOWMlyEgR3LDvMOfZSBmcm9tIGEgY2Fmw6kg4oCUIDMgaXRlbXMsIE5UJDEyMC4=
- UTF-8 bytes in56
- Base64 characters out76
- Size change+36%
Your text was converted to 56 UTF-8 bytes before encoding, because Base64 encodes bytes and not characters. That step matters here: this text contains 3 character(s) above U+00FF, which make btoa() throw outright, and 3 character(s) between U+0080 and U+00FF, which btoa() encodes without complaining but as the wrong bytes.
Runs entirely in your browser — open DevTools and check the Network tab
Paste text to get its Base64 form, or press the swap button to go the other way. Two things separate this from the first result you will find: the text is converted to UTF-8 before encoding, so Chinese and accented letters survive intact, and the decoder accepts the dirty input you actually get in real life — wrapped lines, missing padding, the URL-safe alphabet — while telling you exactly what it repaired.
It is an alphabet change, not a lock
The single most expensive misunderstanding about Base64 is that it hides something. It does not. There is no key, no secret and no party who can decode it that anyone else cannot. It rewrites the same bytes using 64 characters that survive being sent through systems designed for plain English text. Anyone holding the string holds the contents.
The consequence shows up in real systems. An HTTP header of the form Authorization: Basic is a username and a password joined by a colon and run through exactly this operation — which is why that scheme is only safe over TLS, and why a captured header is a captured password. A JSON Web Token has three segments separated by dots, and the middle one is not encrypted either: it is Base64 holding the account identifier, the expiry and whatever claims the issuer put there. The signature at the end proves the token was not altered; it does not conceal a single character of it.
The right mental model is a transport wrapper. If the content needs to stay private, something else has to do that job before this step, and the wrapper simply carries the already-protected result.
Four characters for every three bytes, and where the third comes from
Base64 works in units of six bits, because six bits is exactly enough to select one of sixty-four characters. Storage works in units of eight bits. Those two numbers first agree at twenty-four bits, which is three bytes on one side and four characters on the other, and that ratio is the whole arithmetic of the format.
Four divided by three is one and a third, so the output is always a third larger than the input. That is a floor rather than an estimate: no input encodes better, because the mapping is fixed. It is why an image inlined into a stylesheet costs a third more than the identical file requested on its own, and why a mail server refuses an attachment that seemed comfortably under the limit before it was encoded.
The counter above shows the real numbers for whatever is in the box, including the byte count that the percentage is measured against. For very short inputs the padding pushes the ratio higher than a third; the longer the input, the closer it settles to exactly thirty-three percent.
Two alphabets that look identical until they do not
RFC 4648 defines two variants. The standard one ends its table with a plus sign and a forward slash. The URL-safe one ends with a hyphen and an underscore instead. Everything else — all sixty-two other characters — is byte-for-byte the same table in both.
That shared prefix is the source of a specific and annoying class of bug. A short string, or a string whose bytes happen not to land on the last two slots, comes out identical under both variants. It passes every test on the developer machine, ships, and then breaks weeks later on the one input that produces a slash. The switch above makes the choice explicit, and the note underneath tells you whether this particular result actually used either of the two differing characters — which is the only way to know whether your test proved anything.
The URL-safe variant exists because a plus sign means a space when a query string is parsed, and a forward slash separates path segments. Putting standard Base64 into a URL therefore requires percent-encoding it a second time, and forgetting that second step is a common cause of values that arrive subtly altered. Filenames have the same problem with the slash.
What the equals signs are for, and when to drop them
Padding carries no data whatsoever. Its only job is to make the string a multiple of four characters so a decoder reading four at a time knows where the end is, and to record whether the final group held one byte, two, or three. One equals sign means the last group held two bytes; two of them mean it held one.
Because the length already implies that information, plenty of specifications simply drop it. JSON Web Tokens are required to. So the honest position is that padding is optional in practice and mandatory in some parsers, which is exactly why the switch above exists rather than a fixed answer. Turning it off here shows you how many characters were dropped rather than silently shortening the string.
One length is impossible: a string whose character count leaves a remainder of one when divided by four cannot exist, because a single leftover character carries no complete byte. Paste one and you get told the exact length and why it cannot work, rather than having the stray character quietly discarded and handing back bytes that are missing the end. That silent truncation is a real behaviour in several widely used libraries.
Text has to become bytes first, and that step is where tools break
Base64 has no opinion about language, because it never sees language. It sees bytes. So encoding text requires a decision that happens before Base64 is involved at all: which bytes represent this text. This page answers UTF-8, always, and reports the byte count it produced.
The browser has a built-in function for this called btoa, and an enormous number of online encoders are a thin wrapper around it. It has two distinct failure modes and the second is far worse than the first. Given a character above U+00FF — any Chinese, Japanese or Korean text, any emoji — it throws an error outright, which at least tells you something is wrong. Given a character between U+0080 and U+00FF, such as an e with an acute accent or a German sharp s, it does not complain: it encodes the single Latin-1 byte instead of the two UTF-8 bytes, and the string decodes later into a different letter.
The second case is the dangerous one because nothing anywhere reports a problem. A customer name with an accent goes through an encoder, comes back through a decoder on another system, and arrives spelled wrong. The note under the output here names both counts for the text you pasted, so you can see immediately whether that specific text would have survived a btoa-based tool.
Data URIs, and why a page can refuse to load one
A data URI puts a whole file inside a URL: the scheme, a media type, the word base64, a comma, and then the encoded bytes. Browsers accept them in an image source, a stylesheet background, or a link, which makes them useful for tiny assets that are not worth a separate request and for anything that must survive being copied as a single line of text.
The costs are worth stating before you reach for one. The payload is a third larger, it cannot be cached independently of the document that contains it, and it cannot be compressed as effectively as the original binary. A data URI that saves one request but adds forty kilobytes to every page load is a bad trade.
There is also a security boundary that catches people out. A Content-Security-Policy that lists allowed sources does not implicitly allow the data scheme; it has to be named. A page that renders your inline image perfectly in development and shows a broken icon in production is usually hitting exactly this, and the browser console will say so if you look. Never allow the data scheme for scripts — that turns a decoded string into executable code, and it is the reason the default is to refuse.
Real Base64 arrives dirty, so decoding is deliberately forgiving
Strings copied from the real world are almost never clean. A certificate in PEM form is wrapped at sixty-four characters. A MIME body is wrapped at seventy-six. A token pulled from a browser session has had its padding stripped. A value lifted from a query string is in the URL-safe alphabet. Each of those is valid data in a form that a strict decoder rejects.
The decode direction here accepts all four. Whitespace is removed, both alphabets are accepted, missing padding is restored, and the three settings above are ignored entirely — which the page says out loud rather than leaving you to wonder why the alphabet switch did nothing. Every repair is reported: how many whitespace characters were dropped, how many equals signs were added back, how many URL-safe characters were seen. Fixing the input silently would be almost as unhelpful as rejecting it, because the repair is often the clue to what upstream system produced the value.
When a character genuinely does not belong to either alphabet, the message names its position, the character itself and its code point. In practice the culprit is a quotation mark or a comma that came along when the value was copied out of a JSON file or a log line, and knowing it is the eighth character makes that obvious immediately.
Decoded bytes are not always text, and pretending otherwise is a lie
Base64 encodes bytes, so decoding gives you bytes back. Whether those bytes are readable text is a separate question with a real chance of being no. The string may be a PNG, a PDF, a compressed archive or a cryptographic signature, none of which have a text form at all.
The usual behaviour is to decode as UTF-8 with errors replaced, which produces a wall of diamond question marks and an interface that looks like it succeeded. This page decodes strictly instead: if the bytes are not valid UTF-8, it says so and prints them as hexadecimal, with the byte count beside it. You still get the data — often the byte count alone is enough to identify what you are holding — and you are not misled into thinking the content was garbled when it was never text.
The other common cause of that outcome is legacy encoding. Text produced by systems that predate widespread UTF-8 adoption is stored in encodings such as Big5 or Shift-JIS, and those byte sequences are frequently invalid UTF-8. Getting hexadecimal rather than a text answer is a meaningful result in that case: it tells you the problem is upstream in the encoding, not in the Base64.
What people paste in here is very often a credential
This is the reason the local-only property matters more on this page than on most. The strings people bring to a Base64 decoder are disproportionately sensitive: session tokens whose middle segment they want to read, Basic authorization headers containing a live password, signed assertions from a single sign-on flow, configuration blobs holding an API key. A token pasted into a hosted decoder is a working credential handed to a stranger, and it stays working until it expires.
What protects you here is not a promise about retention. It is that there is no code path capable of sending the box contents anywhere. The encoder and the decoder together are a couple of hundred lines of script that shipped inside this document and execute on your own processor; once loading finishes, the page issues no further request of any kind.
That claim is unusually easy to falsify, which is the point of making it. Put the machine into aeroplane mode and keep encoding and decoding — both directions carry on working, because neither of them ever needed anything from us in the first place.
Frequently asked questions
Is Base64 a way to hide a password or an API key?
No, and treating it as one is a genuine security incident waiting to happen. It is a reversible rewriting of the same bytes with no key involved, so anyone who has the string has the contents. Encrypt first if the value must stay private; Base64 then carries the encrypted result safely through systems that only handle text.
Why is the encoded version a third bigger than what I started with?
Because four output characters carry three input bytes, and four divided by three is one and a third. That ratio is fixed by the format rather than by this implementation, so no encoder anywhere produces a smaller result. Very short inputs come out slightly worse than a third because of the padding at the end.
Standard or URL-safe — how do I know which one the other system wants?
If the value will sit in a URL path, a query string or a filename, choose URL-safe; otherwise standard. When documentation just says Base64 without specifying, assume standard and test with input long enough to actually produce a plus sign or a slash, because short samples come out identical under both and prove nothing.
Another decoder rejects this string but yours accepted it. Which one is wrong?
Neither, usually. Strict decoders refuse input with line breaks, absent padding or URL-safe characters, all of which are ordinary in real data. This page accepts them and lists each repair it made underneath the result, so you can see exactly what would have to change for the strict tool to take it.
Why did I get hexadecimal instead of readable text?
Because the decoded bytes are not valid UTF-8, so there is no text to display. That normally means you are holding a binary file such as an image or a certificate, or text stored in a legacy encoding like Big5. Showing the bytes is more useful than showing a row of replacement characters that pretends the decode worked.
Can I encode a file with this rather than typing text?
Not on this page — the box takes text, and the file case needs a different interface with a size warning, since a one-megabyte file becomes roughly 1.37 megabytes of characters that no browser wants in a textarea. It is on the roadmap as its own tool. For now, text and anything you can paste as text will work here.
Should I keep the equals signs at the end or remove them?
Keep them unless whatever consumes the value tells you not to. They carry no data, but strict parsers reject input without them. JSON Web Tokens are the main exception: their specification requires the padding to be stripped, which is why so many tokens end without one.
If I decode a session token here, does it reach you in any form?
No. Both directions run inside this tab and the page issues no request after it has loaded, so the token is never transmitted. Do check that for yourself rather than believing it: switch the network off entirely and both encoding and decoding keep working exactly as before.


