Encoding Chain Decoder — Every Layer Named, Every Guess Labelled
Encoding Chain Decoder
Text that has been through more than one encoder is the normal case, not the exotic one: a JSON body inside a Base64 token, a URL inside a log line, a form value that was percent escaped and then Base64 encoded so it could travel in a header. This page unwinds those layers one at a time, names the technique it used at each step, prints how confident it is and why, and lists the techniques it rejected at that step so you can disagree with it.
It stops when the text stops looking encoded, when it reaches the step limit, or when the next step would bring back text it has already seen. Nothing is guessed silently: a technique whose alphabet overlaps ordinary words is never applied on its own, it is only offered.
All processing happens locally in your browser — your data is never uploaded to any server.
Only apply a layer on its own when the confidence is at least:
Stop after
Runs the layer limit above in one go. The same table is built either way, so you can switch to stepping at any point.
Or pick a technique by hand:
This list is what the page can see in the text right now. The rotation ciphers are always in it, because they cannot be detected at all; so is anything that only becomes recognisable once the whitespace is taken out of a wrapped text, which is marked as such and never applied on its own.
Encoded text
This input is longer than this page likes to run without asking, and the estimate above says it may take . Click Unwind the whole chain again to go ahead anyway, or shorten the text first.
What the page sees in the text right now
(type or paste something first)
(the chain will appear here)
Layers, in the order they came off
#
Technique
Confidence
Input → result
Size
Why this one
Per layer
Copy takes the text as it stood after that layer. Roll back drops that layer and every layer above it, which is how you undo more than one step at a time.
Result after 0 layer(s)(nothing yet)
What is an encoding chain, and why does it need its own decoder?
A layer is one encoder applied to text. A chain is two or more of them applied one after another. Almost every everyday example looks like this: someone needs to put a piece of binary — an image, a signed token — into a place that only carries text, so they Base64 it. That Base64 result then has to travel in a URL, so it gets percent escaped. That whole URL then gets put into a log line or a configuration file, so the backslashes or the quotes get escaped too. What you are looking at when you hit the problem is the outermost layer, and it is unreadable for the simple reason that the inner layers are still on.
Single purpose decoders do not help with that. A Base64 decoder handed JTI2bHQlM0JiJTI2Z3QlM0JoaSUyNmx0JTNCJTJGYiUyNmd0JTNC will produce a perfectly correct answer that is still unreadable, because the percent escapes and the character references are still on top of the text you wanted. What you actually need is something that decides, at every step, which encoder to take off — and this page is that decision made visible.
Encoding is not encryption, and this page does not break anything. Every technique on this page is a public, reversible way of writing the same information down. Base64 has no key. Percent escaping has no key. If the innermost layer is encrypted, this page will decode the outer layers and hand you ciphertext, which it will say plainly rather than pretend is a result. There is nothing here to run and nothing to hide.
The worked examples on this page
Every row below is a real input from this page's data file, and the middle column is what the page's own unwinder finds in it. The last column is the text that is left when it stops.
What the input is
Layers found
Input, quoted as it arrives
Left when it stops
These rows are rendered by the page from its own case list and re-run on every visit, so they cannot drift away from what the code does. The self-test below checks each one.
How the page decides which layer to take off
Look at the shape, not the meaning. Each technique has a signature — a percent escape, an even run of hex digits, an alphabet and a length that fit Base64. The page lists every technique whose signature matches the text, with a confidence between 0 and 1.
Decode, then check whether the result is text. A technique is only worth applying if it produces something readable. Every candidate is actually run, and its output is scored on how much of it is printable characters.
Apply only what clears the bar, in order of confidence. The highest confidence candidate above your threshold is applied, and the process repeats on the result. Below the threshold, techniques are listed but not applied.
One rule that this page adds on top. Even when a technique clears the threshold, this page will not apply it on its own unless the result is genuinely readable text — replacement characters and control characters count against it. Without that rule, the ordinary word cafe would be validly decoded as three bytes that are only printable in Latin-1, and getUserName would be validly decoded as eight bytes of noise. The detector is right that those strings fit the alphabet; this page decides that fitting the alphabet is not enough. You can still apply them by hand, and the candidate list tells you it declined and why.
Stop, deliberately. When no technique clears the bar, when the text stops changing, when the step limit is reached, or when the next step would bring back text that has already appeared in this chain. That last one matters: ROT13 is its own inverse, so a chain allowed to take that step twice would never end.
A wrapped text is offered, never applied. If the text is cut over several lines, the detector sees whitespace, reads ordinary prose, and finds nothing. So the page takes the whitespace out of a copy of the text, asks the detector again, and offers whatever it finds as a step to be chosen by hand. Taking whitespace out of your text is a rewrite of your text, and this page does not rewrite anything you did not ask it to rewrite.
What this page will not do
It decodes, and nothing else. The text you paste is never executed: no eval, no new Function, no script injection, no opening a document. It is never sent anywhere: no upload, no request, no analytics. Other than the page-bottom ad slot and the share-button row (those are the only two third-party widgets this page loads), no font or script is fetched from another host. Decoded output is written into the page as text, so an HTML tag that comes out of a layer is displayed as a tag rather than turned into one. You can verify all of that: press Self-test and it checks this page's own source for any of those constructs and fails if it finds one.
What it can recognise, and what it cannot
Technique
How sure, on its own
The catch
Text quoted on this page is written for this site. The Base64, Base32, Base58, Base91 and quoted-printable alphabets and the uuencode block format are the published ones; the decoding follows those definitions rather than any particular tool's quirks. Reference vectors for the alphabets live on the sibling pages, which carry the RFC 4648 test vectors.
Text that arrives wrapped over several lines
A blob that has been through an email message, a PEM file, a token in a header or a hash pasted out of a terminal is usually wrapped: the encoder broke it at a fixed column — 76 characters is the MIME convention — and put a line break where the column ended. Those line breaks are not part of the data. They exist so that a long line survives transport, and every decoder that reads such a blob is expected to ignore them.
This page does not ignore them on its own, and the reason is the rule it uses everywhere else. Whitespace is the strongest single sign that a string is prose rather than data, and that is the test that keeps The quick brown fox jumps over the lazy dog. from being read as anything at all. A wrapped blob looks exactly like prose to that test, so the page finds nothing in it — and then does the honest half of the work: it works out what it would find if the whitespace were taken out, lists that in the hand picker marked whitespace out first, and stops there. It is listed rather than applied because removing whitespace changes your text, and this page only changes text when it can prove what the change is. Here it can prove what the change is, and still declines to make it for you, because a text with a line break in the middle of a value and a text with a line break between two fields look the same from the inside.
The two steps when the page stops on a wrapped text. Switch to Step through by hand, press Apply the next layer once (that is what gives the page a chain to add to, and it changes nothing), then pick the entry marked whitespace out first and press Apply it. The row that comes off says how many whitespace characters were removed, and reports its input size as the text you pasted, so the difference between the two counts in that row is exactly the whitespace that came out.
The same reasoning covers the cases that go the other way. A uuencoded block carries a begin line and a length character on every line, so it is recognisable as it stands and is applied on its own; a quoted-printable body keeps its own soft line breaks and is decoded by its own decoder, which knows that a single trailing = at the end of a line is a continuation rather than data. Where a shape test cannot tell prose from data, the page declines and shows you the alternative rather than choosing for you.
What this page will run, and what it refuses to
The table below is built from the same constants the page enforces, so a row and a warning on this page cannot disagree. The two figures that surprise people are the Base58 and Base62 rows: those two techniques are converted by long division whose cost grows with the square of the length, so this page only runs them on a short string and says so in the step instead of appearing to hang.
The estimates are deliberately pessimistic. They come from a timing harness rather than from your machine, and a desktop browser is usually several times faster than the figure shown.
🔷 Encoding Tools
Unwinds text that was encoded more than once, one layer at a time.