If you've ever embedded an image directly in HTML, looked at an Authorization: Basic header, or peeked at raw email source, you've encountered Base64. Base64 is a way of writing any sequence of bytes using only 64 printable ASCII characters, so that binary data can travel safely through systems built for text. For example, the three-byte string Hi! becomes the four-character string SGkh, and a tiny PNG image becomes a run of letters and digits you can paste straight into an <img> tag. This guide explains how the encoding works bit by bit, where it is used, and why it is not a form of encryption.
What is Base64?
Base64 is a binary-to-text encoding scheme. It takes input bytes (values from 0 to 255), regroups their bits into 6-bit chunks, and writes each chunk as one character from a fixed 64-character alphabet: A-Z, a-z, 0-9, + and /. The output is plain text that survives email gateways, JSON strings, XML documents, URLs (with a small tweak) and copy-and-paste.
The name comes from the size of the alphabet. 64 is 26, so every output character carries exactly six bits of information. That is the whole trick: three bytes are 24 bits, and 24 bits divide neatly into four 6-bit groups, so every three input bytes become four output characters.
Base64 is standardised in RFC 4648, which also defines the URL-safe variant covered later in this article.
The Base64 alphabet
Each 6-bit value from 0 to 63 maps to one character. This is the complete standard alphabet:
| Value | Char | Value | Char | Value | Char | Value | Char |
|---|---|---|---|---|---|---|---|
| 0 | A | 16 | Q | 32 | g | 48 | w |
| 1 | B | 17 | R | 33 | h | 49 | x |
| 2 | C | 18 | S | 34 | i | 50 | y |
| 3 | D | 19 | T | 35 | j | 51 | z |
| 4 | E | 20 | U | 36 | k | 52 | 0 |
| 5 | F | 21 | V | 37 | l | 53 | 1 |
| 6 | G | 22 | W | 38 | m | 54 | 2 |
| 7 | H | 23 | X | 39 | n | 55 | 3 |
| 8 | I | 24 | Y | 40 | o | 56 | 4 |
| 9 | J | 25 | Z | 41 | p | 57 | 5 |
| 10 | K | 26 | a | 42 | q | 58 | 6 |
| 11 | L | 27 | b | 43 | r | 59 | 7 |
| 12 | M | 28 | c | 44 | s | 60 | 8 |
| 13 | N | 29 | d | 45 | t | 61 | 9 |
| 14 | O | 30 | e | 46 | u | 62 | + |
| 15 | P | 31 | f | 47 | v | 63 | / |
Three details are worth noticing. Uppercase letters come first, so A is 0 and a is 26. Digits come after all the letters, so 0 is 52, not zero. And the two symbols + (62) and / (63) fill the last two slots. The equals sign = is not in the alphabet at all: it is a padding marker that appears only at the very end of the output, which is why a Base64 string can end in = or == but never contains one in the middle.
How Base64 encoding works, step by step
The algorithm is short:
- Take the input three bytes (24 bits) at a time.
- Split those 24 bits into four 6-bit groups.
- Look each group up in the alphabet to get one character.
- If the final chunk is short (one or two bytes), pad it with zero bits and append
=for each missing byte.
Worked example: encoding Hi!
Step 1 - bytes. In ASCII, H is 72, i is 105 and ! is 33.
Step 2 - binary.
H = 72 = 01001000
i = 105 = 01101001
! = 33 = 00100001
Step 3 - concatenate the 24 bits and regroup them into four 6-bit groups.
01001000 01101001 00100001 (24 bits as 3 bytes)
010010 000110 100100 100001 (the same 24 bits as 4 groups of 6)
Step 4 - convert each group to decimal and look it up in the table.
010010 = 16 + 2 = 18 -> S
000110 = 4 + 2 = 6 -> G
100100 = 32 + 4 = 36 -> k
100001 = 32 + 1 = 33 -> h
Result: SGkh. You can confirm it on the command line: printf 'Hi!' | base64 prints SGkh, and pasting Hi! into the Base64 encoder gives the same answer.
When the input is not a multiple of three bytes
Encode Hi (two bytes, 16 bits). Three 6-bit groups need 18 bits, so two zero bits are appended, and the fourth character that would normally exist is replaced by =:
01001000 01101001
010010 000110 1001(00) -> 18, 6, 36 -> S G k
Result: SGk=
Encode H (one byte, 8 bits). Two groups need 12 bits, so four zero bits are appended and two characters are replaced by ==:
01001000
010010 00(0000) -> 18, 0 -> S A
Result: SA==
The rule: one = means the last chunk held two bytes, == means it held one byte, and no padding means the input length was a multiple of three. Because the padding tells the decoder how many bytes to produce, some decoders reject strings whose padding has been stripped. See the errors section below.
Why Base64 exists
Many protocols were designed for text, not bytes:
- Email (SMTP) was originally limited to 7-bit ASCII.
- URLs may only contain a restricted set of characters.
- JSON and XML are text formats with no way to hold raw bytes.
- Some systems interpret particular byte values as control characters: a zero byte terminates a C string, and
0x0Ais a line break.
Base64 sidesteps all of that by using only characters that every text-based system treats as ordinary data.
Base64 is encoding, not encryption
Search engines see a steady stream of queries for "base64 encryption" and "base64 decrypt", so it is worth being blunt: Base64 is not encryption and provides no confidentiality whatsoever.
Encryption needs a key. Without the key, the data is unreadable. Base64 has no key: the alphabet is public, the algorithm is public, and every programming language ships a decoder. Anyone who sees a Base64 string can turn it back into the original bytes in a millisecond:
atob('dXNlcm5hbWU6cGFzc3dvcmQ=') // "username:password"
That is exactly why HTTP Basic authentication is only safe over HTTPS. The header contains the credentials in a trivially reversible form; TLS is what actually protects them in transit.
Base64 looks scrambled to a human, which is where the confusion comes from. But "hard for a person to read" and "hard for a computer to reverse" are unrelated properties, and obscurity is not security. Never use Base64 to hide passwords, API keys, personal data or anything else.
What to use instead:
- To hide data from anyone without a key: real encryption such as AES-GCM (symmetric) or an RSA/elliptic-curve scheme (asymmetric), through a vetted library.
- To prove data has not been altered: a keyed HMAC or a digital signature.
- To store passwords: a slow password hash such as bcrypt, scrypt or Argon2 - never Base64, and never a plain hash either.
Base64 still has a role next to encryption: ciphertext is binary, so it is often Base64-encoded so it can be stored in a text field or JSON. The encryption is what protects it; the Base64 is only packaging.
Size overhead: why Base64 makes data about 33% bigger
Every 3 bytes become 4 characters, so the output is 4/3 of the input, or 33.3% larger, before padding. Padding adds at most two characters. If the output is also line-wrapped for email (a line break every 76 characters), the total overhead rises to about 37%.
| Input size | Base64 output | Growth |
|---|---|---|
| 3 bytes | 4 characters | +33% |
| 100 bytes | 136 characters | +36% |
| 10 KB (10,240 bytes) | 13,656 characters | +33% |
| 1 MB image | about 1.33 MB | +33% |
Compression recovers some of this if the transport is gzipped, but not all of it, because Base64 spreads eight bits of information over a character that could hold eight bits and then uses only six of them.
Common uses of Base64
Data URIs in HTML and CSS
A data URI embeds a file directly in a page instead of linking to it. The format, from RFC 2397, is data:[<media type>][;base64],<data>:
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" alt="1x1 pixel" />
This saves an HTTP request for small icons, but the data cannot be cached separately from the page and it inflates the HTML or CSS by a third, so it is best reserved for assets of a few kilobytes at most. Our Image to Base64 tool produces a ready-to-paste data URI from an image file, and Base64 to Image does the reverse so you can preview and download an image that arrived as a string.
HTTP Basic authentication
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
The encoded part is simply username:password. As explained above, this is encoding, not encryption, so the header is only as private as the connection carrying it.
Email attachments (MIME)
Email bodies must be text, so attachments are Base64-encoded with a Content-Transfer-Encoding: base64 header. RFC 2045 requires the encoded output to be broken into lines of no more than 76 characters, each ending in CRLF:
Content-Type: image/png; name="pixel.png"
Content-Transfer-Encoding: base64
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGA
WjR9awAAAABJRU5ErkJggg==
A decoder handling MIME content must ignore those line breaks; a strict decoder that sees them as illegal characters will fail.
Binary data in JSON and XML
JSON has strings, numbers, booleans, arrays and objects, but no byte type. When an API needs to carry a thumbnail, a file upload, a public key or an encrypted blob, it sends a Base64 string:
{
"avatar": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..."
}
JWTs and other tokens
A JSON Web Token is three Base64url-encoded segments joined with dots: a header, a payload and a signature. That is why you can read a JWT's claims without any secret, as our JWT tokens explained article covers, and why the JWT Decoder works entirely in your browser.
Base64 variants: standard, URL-safe and MIME
| Variant | Alphabet | Padding | Line breaks | Where you see it |
|---|---|---|---|---|
| Standard (RFC 4648 section 4) | A-Z a-z 0-9 + / |
= required |
None | General purpose, data URIs, Basic auth |
| URL-safe / base64url (RFC 4648 section 5) | A-Z a-z 0-9 - _ |
Usually omitted | None | JWTs, URLs, file names, cookies |
| MIME (RFC 2045) | A-Z a-z 0-9 + / |
= required |
Every 76 characters | Email attachments |
URL-safe Base64 (base64url)
The standard alphabet has three characters that cause trouble inside a URL: / is the path separator, + is decoded as a space in query strings, and = separates keys from values. You can percent-encode them (+ becomes %2B, / becomes %2F), but that is easy to get wrong in one direction and not the other - see URL encoding explained.
Base64url avoids the problem by swapping + for - and / for _, and by dropping the = padding entirely, since the decoder can infer the length from the string. JWTs, OAuth state values, signed URLs and many API keys use this form.
Because the alphabet is different, a standard decoder will reject a base64url string (Java's standard decoder, for example, throws Illegal base64 character 2d when it meets a -). Use the matching decoder: base64.urlsafe_b64decode in Python, Base64.getUrlDecoder() in Java, Buffer.from(s, 'base64url') in Node.js, and Base64Url or WebEncoders.Base64UrlDecode in .NET.
Base64 and Unicode: encode to UTF-8 first
Base64 works on bytes, not characters. A text string has to be turned into bytes with a character encoding before it can be Base64-encoded, and that encoding should almost always be UTF-8.
This is the source of the most common Base64 bug in JavaScript. The browser's btoa() function expects a "binary string" in which every character is a code unit from 0 to 255. Pass it €, an emoji or any character above U+00FF and it throws InvalidCharacterError: The string to be encoded contains characters outside of the Latin1 range. Worse, btoa('é') does not throw, but it encodes the single Latin-1 byte E9 (giving 6Q==) rather than the UTF-8 bytes C3 A9 (giving w6k=), so a UTF-8 decoder on the other side produces garbage.
The fix is to convert to UTF-8 bytes explicitly:
function encodeUtf8Base64(str) {
const bytes = new TextEncoder().encode(str); // UTF-8 bytes
let binary = '';
for (const b of bytes) binary += String.fromCharCode(b);
return btoa(binary);
}
function decodeUtf8Base64(b64) {
const binary = atob(b64);
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
encodeUtf8Base64('café €') // "Y2Fmw6kg4oKs"
decodeUtf8Base64('Y2Fmw6kg4oKs') // "café €"
In Node.js, Buffer.from(str, 'utf8').toString('base64') does this in one step, and in Python base64.b64encode(s.encode('utf-8')) does the same. Our Base64 encoder converts text to UTF-8 before encoding, so accented characters and emoji round-trip correctly. For the background on why the bytes differ, see character encoding: ASCII, Unicode and UTF-8.
Common Base64 decoding errors
| Symptom | Typical message | Cause | Fix |
|---|---|---|---|
| Invalid length | .NET: Invalid length for a Base-64 char array or string. Python: number of data characters (5) cannot be 1 more than a multiple of 4 |
The string was truncated, or the length is wrong because of stray characters | Check the whole string was copied; strip whitespace |
| Illegal character | .NET: The input is not a valid Base-64 string as it contains a non-base 64 character... Java: Illegal base64 character 2d JavaScript: InvalidCharacterError |
A base64url string fed to a standard decoder; a data:image/png;base64, prefix left on; line breaks or spaces inside the string |
Use the base64url decoder; remove the prefix; remove whitespace |
| Missing padding | Python: binascii.Error: Incorrect padding |
= was stripped (normal for base64url) but the decoder insists on it |
Append = until the length is a multiple of 4 |
| Decodes to garbled text | No error | The bytes were decoded with the wrong character set | Decode the bytes as UTF-8 |
| Decodes to a broken image | No error | Any of the above, or the wrong media type in a data URI | Check it with Base64 to Image |
A one-line padding repair in Python is s + '=' * (-len(s) % 4). Browsers are lenient about padding: atob('SGk') returns Hi without complaint, but atob('S') throws because a single leftover character cannot represent a whole byte.
When not to use Base64
- Large files. The 33% overhead adds up; upload binary with
multipart/form-dataor a direct binary body instead. - When the transport already supports binary. HTTP, WebSockets, gRPC and every modern database handle raw bytes.
- As a security measure. It provides none.
- For storage. Store files as files or as binary columns, not as Base64 text.
- In hot paths. Encoding and decoding are cheap, but not free.
Practical examples
JavaScript
// ASCII-only text
btoa('Hello World'); // "SGVsbG8gV29ybGQ="
atob('SGVsbG8gV29ybGQ='); // "Hello World"
// Node.js, any text, any variant
Buffer.from('Hello World', 'utf8').toString('base64'); // "SGVsbG8gV29ybGQ="
Buffer.from('Hello World', 'utf8').toString('base64url'); // "SGVsbG8gV29ybGQ"
Buffer.from('SGVsbG8gV29ybGQ=', 'base64').toString('utf8');
Python
import base64
encoded = base64.b64encode('Hello World'.encode('utf-8')).decode('ascii') # 'SGVsbG8gV29ybGQ='
decoded = base64.b64decode(encoded).decode('utf-8') # 'Hello World'
# URL-safe variant
base64.urlsafe_b64encode(b'\xfb\xff').decode('ascii') # '-_8='
C# / .NET
using System.Text;
string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes("Hello World")); // SGVsbG8gV29ybGQ=
string decoded = Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
Command line
printf 'Hello World' | base64 # SGVsbG8gV29ybGQ=
echo 'SGVsbG8gV29ybGQ=' | base64 --decode # Hello World
Frequently Asked Questions
What is Base64?
Base64 is a binary-to-text encoding that represents any sequence of bytes using 64 printable ASCII characters (A-Z, a-z, 0-9, + and /). It exists so that binary data such as images, files and cryptographic keys can pass through systems that only handle text, including email, JSON, XML and URLs. It is fully reversible and adds about 33% to the size of the data.
Is Base64 encryption?
No. Base64 is encoding, not encryption. There is no key, the alphabet and algorithm are public, and every language has a built-in decoder, so anyone who sees a Base64 string can recover the original data instantly. If you need to keep data secret, use real encryption such as AES-GCM; if you need to detect tampering, use an HMAC or a digital signature; Base64 can then be used to carry the resulting bytes as text.
Why does Base64 make data bigger?
Each Base64 character can represent only 64 values, which is six bits, while a byte holds eight bits. Encoding therefore needs four characters for every three bytes, a ratio of 4:3, so the output is about 33% larger than the input. Padding adds up to two more characters, and MIME line wrapping every 76 characters pushes the overhead to about 37%.
What characters does Base64 use?
Standard Base64 uses the 26 uppercase letters A-Z (values 0 to 25), the 26 lowercase letters a-z (26 to 51), the digits 0-9 (52 to 61), + (62) and / (63). The = sign is used only as padding at the end. The URL-safe variant replaces + and / with - and _ so the output can be placed in a URL or file name without escaping.
What is URL-safe Base64?
URL-safe Base64, also called base64url and defined in RFC 4648 section 5, is standard Base64 with + replaced by -, / replaced by _, and the = padding usually omitted. Those changes remove the characters that have special meaning in URLs and query strings. It is the encoding used for the three parts of a JWT, for many API keys and for signed URL parameters, and it needs a base64url-aware decoder because the alphabet differs from the standard one.
How do I Base64 encode in JavaScript or Python?
In the browser, btoa('text') encodes and atob('dGV4dA==') decodes, but only for characters in the 0 to 255 range; for other text, convert to UTF-8 bytes with TextEncoder first. In Node.js use Buffer.from(text, 'utf8').toString('base64'). In Python use base64.b64encode(text.encode('utf-8')) and base64.b64decode(data).decode('utf-8'). For quick one-off jobs, the Base64 encoder and decoder does the same in your browser.
Why does my Base64 string end with = ?
The = characters are padding. Base64 processes input three bytes at a time, and when the input length is not a multiple of three the final group is incomplete. One = means the last group contained two bytes, and == means it contained one byte. The padding tells the decoder how many bytes to produce; base64url strings usually omit it because the decoder can work it out from the length.
What is Base64 used for?
Common uses include data URIs that embed images in HTML or CSS, email attachments (MIME), HTTP Basic authentication headers, binary fields in JSON and XML APIs, the three segments of a JSON Web Token, encoded certificates and keys in PEM files, and storing small binary values in text-only places such as environment variables and configuration files.
Summary
Base64 turns bytes into text using a 64-character alphabet: three bytes in, four characters out, with = padding when the input does not divide by three. Use it when binary data must live in a text-only context such as JSON, email, a URL or an HTML attribute. Remember that it is encoding rather than encryption, that it enlarges data by about a third, that text should be converted to UTF-8 before encoding, and that the URL-safe variant needs a matching decoder.
Ready to try it? Encode or decode text with the Base64 Encoder/Decoder, turn a file into a data URI with Image to Base64, or preview a string as a picture with Base64 to Image.