Four boxes, four fillings: the same hello goes in as four different strings
I cut this page's script block out verbatim and ran it as-is in Node, replacing only the hash-library step with the built-in crypto computing MD5 over UTF-8 bytes; the concatenation and slicing lines were not touched by one line. The only line worth measuring: const text = pre + raw + suf;. The template measures 3635 bytes across 84 lines, and every line ends in CRLF. The substitution touches the library call only; the concatenation, the slicing and the fields the page actually runs stay exactly as they are on disk.
With the raw field fixed to hello, the four ways of filling capture the hashed strings verbatim as hello, salt1hello, hellosalt2, salt1hellosalt2: no separator sits between the three segments and nothing is trimmed.
| Prefix salt | Suffix salt | String fed to the hash | 32-char lowercase |
|---|---|---|---|
| empty | empty | hello | 5d41402abc4b2a76b9719d911017c592 |
salt1 | empty | salt1hello | 95a2e8514b40b95ef41fb464a6e382ee |
| empty | salt2 | hellosalt2 | d25f7bdb12f78be4238273d49591f882 |
salt1 | salt2 | salt1hellosalt2 | 7be7c42377de79b9012a2fba496208c6 |
Dedup counts 4/4, all different; swapping the two salts builds salt2hellosalt1, and its measured result differs from the both-filled one too — change the order, change the string. This page, the online text MD5 tool, has exactly one hash call site: calcMd5(). Read together, the table says the hash never sees three fields: it sees a single string assembled in the order prefix, raw, suffix.
Figure 1: the order of the three segments, and the two routes to equal values
Boundary shift: the same finished string
Filling the three boxes with all five splits such as ab|c|, a|bc|, |abc|, every one of the five captured values is abc, and the 32-char result always equals 900150983cd24fb0d6963f7d28e17f72. The matching Chinese set 测试|文|一下 and 测|试文|一下 both join into 测试文一下, and both give 8f76011a697386edf2f3132d4e4f57de. The function only looks at the string once it is assembled, so where the boundary sits is outside its scope; the salt swap above changes the string itself, so it changes the result too. After the join nothing remembers where the split was: only the assembled characters survive into the digest, which is why the shifting routes collapse into one value while the swap does not.
The textarea never splits by line
The function body has no split and no loop over lines; a newline is just one more ordinary code unit inside the string:
| Input (JSON-escaped) | Code units | Newline units | 32-char lowercase |
|---|---|---|---|
"alpha\nbeta\ngamma" | 16 | 2 | b81e722771977ee32a6822155f761acd |
"alphabetagamma" | 14 | 0 | 11db5b10e1c21d2e7e4dfa597552e331 |
"alpha\nbeta" | 10 | 1 | 6d76416219367970d143155ecd77d609 |
"alpha\r\nbeta" | one extra \r | 1 | 0ff5fc67c03065f1c72d4ac72aeefdbf |
Samples of 1 to 6 lines run 5, 10, 16, 22, 30, 35 code units long with newline units from 0 to 5, yet every one of them makes exactly 1 hash call and yields 1 result. The intro sentence "Batch processing: supports generating MD5 for multi-line text" is judged [conflict]: the output is only 4 single-value read-only boxes. Pasting a list therefore never becomes a list of digests: the lines keep their order inside one flat string and share one output.
Listeners: 1 versus 0 versus 0
#md5-input carries 1 input listener while the two salt boxes carry 0 each; the listener body reads verbatim if (this.value) calcMd5();. Typing hello one character at a time from an empty state triggers 5 runs, capturing h, he, hel, hell, hello in turn; backspacing one character at a time adds 4 more, while the final delete-to-empty step makes 0 calls and 0 toasts, leaving the screen parked on the result of h, 2510c39011c5be704182423e3a695e91; the total count is 9 = 5 + 4. That asymmetry is the whole story of the three fields: one box drives itself, the other two wait for the button.
Figure 2: how the input listener counts triggers
Empty input is stopped by the guard, and the boxes keep the previous values
The guard reads verbatim if (!raw) { UU.toast('请输入要加密的文本', 'error'); return; }: it blocks whenever the raw field is empty no matter what the salt boxes hold, while an empty salt box still joins the concatenation as an empty segment. Three measured frames — frame A prefills ab, hello, cd, captures abhellocd, 32-char lowercase 07663b565bdbcf17432e20d56eb1a51b, and the results container flips to visible; frame B clears the input field by typing, the listener sees empty and makes no call, not even a toast; frame C clicks the button, the toast counter goes +1, none of the assignments after return run, and the four read-only boxes plus the container state stay character-for-character identical to frame A. d41d8cd98f00b204e9800998ecf8427e is indeed the MD5 of the empty string, with 0 character differences from the intro's example constant, yet empty input on this page never reaches the hash call site, so that example value has no path to reproduce here. The clear button takes a different route: it wipes the values of 7 ids in one go. The two routes leave very different screens behind: the guard path preserves the previous run exactly where it was, while the clear path erases it.
Chinese and emoji enter the hash as UTF-8 bytes
中 is 1 code unit / 3 bytes (e4b8ad), 测试文本 is 4 code units / 12 bytes (e6b58be8af95e69687e69cac), and a single U+1F600 emoji and the supplementary-plane character at U+20000 are both 2 code units / 4 bytes. The two routes agree on 6/6 samples, and the 5 vectors published in RFC 1321 hit 5/5 — what enters the hash is always bytes, never a character count on the screen.
Only three sentences registered against the intro
- "Case toggle": there is no toggling action at all; two uppercase values and two lowercase values are written into 4 boxes inside the same single click. [partially consistent]
- "The 16-char digest takes the middle 16 characters":
substring(8, 24)grabs characters 9 through 24 on a 1-based count, 16 long, and on 6 inputs it matches the same window of the 32-char digest digit for digit 6/6. The earlier SHA-256 versus MD5 comparison article already covered this line, so it stays unexpanded here. [consistent] - "Batch processing" is judged against the newline table above, [conflict]. The intro's security notice block and the closing judgment sentence of its salt entry are registered only, without commentary; the file page (file MD5 calculation) is not tested in this article, and nothing here extends to other hash pages.
FAQ
Q: Does it matter whether the salt goes before or after?
Look at the string that comes out: only the prefix salt gives salt1hello, only the suffix salt gives hellosalt2, two strings, two results. A shift is worth the same value only when the finished string is identical; swapping the two salts does not build the same string, so the result differs too.
Q: Can I paste 3 lines and get 3 hashes?
No — measured as 1 call and 1 result. Deleting the newlines changes which result you get, and CRLF versus LF are two different strings as well.
Q: I edited the salt and the output did not move — did it skip the calculation?
Neither salt box has a listener, so an edit without a click computes nothing; when empty input is stopped by the guard the page only fires a toast, and the four boxes keep their values from the previous run.