Encoding, hashing and encryption are three different things

How encoding, hashing and encryption differ, what Base64, percent encoding and HTML escaping are actually for, and how to recognise an unknown string on sight.

A string that looks scrambled is not a string that is protected. Three separate operations produce unreadable output, and confusing them is where real security bugs come from.

Reversible?Needs a key?What it is for
EncodingYes, by anyoneNoGetting bytes through a channel that cannot carry them raw
HashingNoNoFingerprints, integrity checks, lookup keys
EncryptionYes, with the keyYesSecrecy

Encoding is a change of alphabet. Base64, percent encoding, hex and HTML entities write the same information differently so that a parser downstream does not choke on it. No secret is involved, so there is nothing to keep. A Caesar shift belongs here too: the Caesar Cipher tool brute forces all 25 shifts at once, a fair summary of how much a fixed substitution protects. Hashing maps any input to a fixed length digest and cannot be run backwards. Encryption is the only one of the three that provides confidentiality, and it always involves a key. If you cannot point at the key, nothing is being encrypted.

Base64 and what it costs

Base64 splits three bytes (24 bits) into four groups of six and writes each group as one character from a 64 character alphabet. Four characters per three bytes means the output is a third larger. That matters most when embedding files: the File to Base64 tool produces a data URL, so a 300 KB image lands in your HTML at roughly 400 KB, re-downloaded with every page.

When the input is not a multiple of three bytes, the final group is padded with =:

"abc"  ->  YWJj      (3 bytes, no padding)
"ab"   ->  YWI=      (2 bytes, one =)
"a"    ->  YQ==      (1 byte, two =)

The standard alphabet ends in + and /, a problem in a URL, because + decodes as a space in form data and / is a path separator. So a second alphabet exists: Base64url swaps + for - and / for _, and usually drops the padding. That is why a token copied out of a URL sometimes fails in a decoder set to the standard alphabet. The Base64 Encoder handles both, and handles Unicode correctly, which naive implementations often do not: text must be UTF-8 encoded before the Base64 step.

None of this is a security measure. A value a product calls an "encrypted" identifier in a URL is very often Base64 of a plain integer that anyone can read, change and re-encode in seconds. The same goes for the payload of a JSON Web Token: the JWT Decoder reads it without a key, because there is nothing to unlock.

Percent encoding, and which function to use

Percent encoding replaces a byte with % and two hex digits. It works on bytes rather than characters, so non-ASCII text is UTF-8 encoded first: é becomes %C3%A9.

Unreserved characters (A-Z, a-z, 0-9, -, ., _, ~) never need encoding. Reserved characters (: / ? # [ ] @ ! $ & ' ( ) * + , ; =) are the delimiters that give a URL its structure, and whether one needs encoding depends on whether it is being used as a delimiter or as data. That is the difference between the two JavaScript functions:

encodeURI("https://ex.com/s?q=cats & dogs")
// https://ex.com/s?q=cats%20&%20dogs   the & still splits the query

encodeURIComponent("cats & dogs")
// cats%20%26%20dogs                    safe as a single value

encodeURI is for a whole URL you want to make legal, so it leaves delimiters intact. encodeURIComponent is for one value going into a path segment or query parameter, so it encodes them. Use the second for anything you are inserting, and note that it still leaves !, ', (, ) and * alone.

Then there is the plus sign. Form bodies sent as application/x-www-form-urlencoded encode a space as +, and the convention leaked into query strings, so most servers decode + in a query as a space. A literal plus must be sent as %2B, which is why alex+billing@example.com so often arrives as alex billing@example.com with the tag destroyed. In a path segment, by contrast, + is just a plus and a space is %20. The URL Encoder makes that visible when a value survives one round trip and breaks on the next.

Escaping depends on where the value lands

There is no single "escaped for the web" form of a string. The context decides.

ContextCorrect treatment
HTML textEntities for &, < and >
Quoted attributeEntities for & and for whichever quote wraps it, and always quote the attribute
Inside <script>JavaScript string escaping, not entities
URL in href or srcPercent encode the value, attribute escape it, then check the scheme
CSS valueCSS escaping, and never build url() from user input

The script case catches people out. Script contents are not entity decoded, so an entity written there stays literal, and the HTML parser closes the block on the sequence </script wherever it appears, quoted string or not:

<script>var s = "</script>";</script>   <!-- block ends early -->
<script>var s = "<\/script>";</script>  <!-- correct -->

Escaping alone is not enough for URLs either. A value starting javascript: executes when clicked however carefully it was encoded, so validate the scheme against an allowed list. The HTML Entities tool covers the text and attribute cases and decodes entities back, the usual need when an API has double escaped something into &amp;amp;.

Hashing: which algorithm, and for what

AlgorithmDigestStatus
MD5128 bitBroken. Collisions are trivial
SHA-1160 bitBroken. Chosen prefix collisions are practical and affordable
SHA-256, SHA-512256, 512 bitFine for integrity and signatures
bcrypt, scrypt, Argon2variesPasswords only

MD5 and SHA-1 fail at collision resistance, meaning an attacker can construct two different inputs with the same digest. Anywhere a hash stands in for a document they are unusable: signatures, certificates, content addressing, deduplicating anything an attacker supplies. Neither is broken for preimages, so an old MD5 checksum still catches accidental corruption, but neither belongs in new work. Use SHA-256, which the Hash Generator computes for files as well as text.

Passwords are a different problem, and a fast hash is the wrong tool precisely because it is fast. Commodity hardware does billions of SHA-256 operations per second, so a leaked table of SHA-256 password hashes is a leaked table of passwords. bcrypt, scrypt and Argon2id are deliberately slow and tunable, with a work factor (and for the latter two a memory cost) you raise as hardware improves, and each stores a unique random salt in its output.

A hash is also not a signature. To prove a message came from someone holding a key, use HMAC-SHA-256 rather than hashing the secret and the message glued together, which is vulnerable to length extension.

What a hash cannot do

A hash cannot be reversed by calculation. It can be reversed by search whenever the set of possible inputs is small enough to enumerate: hash every four digit PIN and you have all ten thousand digests instantly, and the same goes for every postcode, phone number or address in a leaked list. Hashing an identifier does not anonymise it.

Salting, a unique random value stored with each record and mixed into the input, does not make one target harder to guess, but it forces an attacker to attack every record separately and makes precomputed tables worthless. For low entropy values, a pepper (a secret key held outside the database) is the part that genuinely helps.

Recognising an unknown string

ShapeTellExample
HexOnly 0-9a-f, even length. 32 chars is MD5, 40 is SHA-1, 64 is SHA-2565d41402abc4b2a76b9719d911017c592
Base64Mixed case with + and /, length a multiple of 4, may end = or ==SGVsbG8sIHdvcmxkIQ==
Base64urlThe same with - and _, usually unpaddedeyJzdWIiOiIxMjM0NSJ9
Percent encoded% followed by two hex digitsa%20b%26c
JWTThree Base64url chunks separated by full stops, starting eyJeyJhbGciOi...
bcryptExactly 60 characters, starting $2a$, $2b$ or $2y$ and a cost$2b$12$...
Argon2$argon2id$v=19$m=...,t=...,p=...$salt$hash

eyJ is worth memorising: it is Base64 of {", so any chunk starting that way is an encoded JSON object, token or not.

Decoding successfully is not proof the guess was right, because Base64 will turn any input into bytes. Check the result is plausible text, or starts with a known file signature: Base64 beginning iVBORw0KGgo is a PNG, /9j/ a JPEG, JVBERi0 a PDF and UEsDB a zip. If the bytes look like nothing, run the output through the Text to Binary tool to read the codes directly, or check whether the value was percent encoded before it was Base64 encoded, a common double wrapping.