Base64 Encoder / Decoder

Text, hex, binary or a whole file to Base64 and back, updating as you type

The bytes themselves
Nothing to show yet.

Three bytes go in and four characters come out. Hold on to that ratio and the rest of Base64 stops being mysterious: the padding, the 33% size increase, the reason line breaks turn up in email.

Input     M          a          n
Bytes     0x4d       0x61       0x6e
Bits      01001101   01100001   01101110
Regroup   010011  010110  000101  101110
Value     19      22      5       46
Output    T       W       F       u

Six bits hold a value from 0 to 63, which is exactly why the alphabet has 64 characters in it. A through Z covers 0 to 25, a through z covers 26 to 51, 0 through 9 covers 52 to 61, then + is 62 and / is 63.

Notice that the regrouped values ignore where one byte stopped and the next began. That is why nothing in Base64 lines up with anything: the second output character carries the last two bits of the first byte and the first four bits of the second, so changing one byte changes two characters, and changing the first byte of a long file changes only the first two characters of a very long string.

What the equals signs are for

Input that is not a multiple of three bytes runs out of bits partway through the final group. The leftover bits get padded with zeros, and one = per missing byte records how many were really there.

Man   3 bytes   TWFu   nothing missing
Ma    2 bytes   TWE=   one byte short
M     1 byte    TQ==   two bytes short

Padding carries nothing you could not work out from the length, so a lot of systems drop it. Paste input here with or without. The length itself is the real check: a Base64 value can be 0, 2 or 3 characters past a multiple of four, and never 1, because one character is six bits and six bits is not a byte. That is the error you get when a value has been truncated in transit.

Two alphabets, and you do not get to pick which one arrives

+ and / are harmless in most places and a nuisance in URLs, where / splits paths and + decodes back to a space inside a query string. RFC 4648 section 5 defines a second alphabet that swaps those two for - and _, usually with the padding stripped. JWTs use it. So does most of anything describing itself as URL-safe.

The two alphabets agree on 62 of their 64 characters and differ only on the last two, so there is never any question which one you pasted. Decoding needs no switch. Encoding is a decision, so that one gets one.

btoa is not a Base64 encoder for text

btoa('hello')   works
btoa('café')    InvalidCharacterError
btoa('日本')     InvalidCharacterError

btoa wants a string whose code units all sit below 256, and treats each one as a single byte. Anything above U+00FF throws. That is not a bug in the browser. It means btoa encodes bytes that happen to be living inside a string, and text is not bytes until you have chosen an encoding.

TextUTF-8 bytesBase64
Hi48 69SGk=
café63 61 66 c3 a9Y2Fmw6k=
日本e6 97 a5 e6 9c ac5pel5pys
🌞f0 9f 8c 9e8J+Mng==

The byte count above the box is worth watching for this reason. An emoji is one character and four bytes, and it is the byte count that sets the output length.

The encoding is the whole argument

Nearly every "the accents came back wrong" bug is one encoding going in and a different one coming out. The bytes were never damaged; they were read with the wrong table.

Hi   UTF-8       48 69         SGk=
Hi   UTF-16 LE   48 00 69 00   SABpAA==
Hi   UTF-16 BE   00 48 00 69   AEgAaQ==
Hi   Latin-1     48 69         SGk=

UTF-8 and Latin-1 agree for the first 128 characters and diverge immediately afterwards. That is what makes the bug so hard to spot: the test data was ASCII and worked, and the first customer with an accent in their surname found it. UTF-16 puts a zero byte beside every ASCII character, so a value that decodes with a null between each letter came from a Windows API or a Java String.getBytes() with no argument.

Switch the input format from Text to Hex to see the bytes exactly. Switching format converts what is in the box instead of reinterpreting it, so nothing is lost by looking.

Where the line breaks come from

Base64 in the wild often arrives wrapped. MIME, from RFC 2045, wraps at 76 characters with CRLF endings, which is where mail headers and email attachments get theirs. PEM, from RFC 7468, wraps at 64, so every certificate and private key you have ever opened looks the way it does.

-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
...64 characters a line, for as many lines as it takes...
-----END CERTIFICATE-----

Line breaks are not part of the value. Anything decoding correctly ignores them, and so does this tool, so you can paste a whole PEM block with its header lines removed and get the bytes back.

Reading output that was never text

Base64 carries bytes, and plenty of those bytes were never characters. Paste something binary here and the summary line names it from its signature, and the save button hands it back as a real file with the right extension.

iVBORw0KGgo   PNG
/9j/          JPEG
R0lGODlh      GIF
JVBERi0x      PDF
UEsDBBQ       ZIP, and everything built on it (.docx, .xlsx, .jar, .epub)
H4sI          gzip
AGFzbQ        WebAssembly

Those prefixes are stable because the first bytes of the file are always the first bytes of the encoding. If you spend any time reading Base64 out of logs, the first four characters tell you what you are holding before you decode anything.

A whole data: URL can go in the Base64 box as it stands. The data:image/png;base64, part is stripped and the media type it declared is reported back, which is a quick way to check whether the label matches the contents.

The 33% you pay for it

Four characters for every three bytes puts the output a third larger, rounded up to a multiple of four. A 1 MB file comes out as 1.33 MB of text, or about 1.37 MB once it has been wrapped at 76 columns with CRLF endings.

Inlining a handful of small icons as data URIs costs nothing anybody will notice. Inlining a 4 MB photograph makes the page slower than the extra request it was supposed to save, and the bytes can no longer be cached on their own or served in a different size to a phone.

Worth saying plainly, since the output looks scrambled enough to invite the assumption: Base64 is not encryption and was never meant to resemble it. There is no key. Anything encoded is readable by anyone who pastes it into a page like this one.