Notes › Resources › Three Hash Implementations on One Page: Two Public-Domain TXTs as Byte Corpus, Measuring the Block Ledger and Encoding Mutations of file-md5 Character by Character

Three Hash Implementations on One Page: Two Public-Domain TXTs as Byte Corpus, Measuring the Block Ledger and Encoding Mutations of file-md5 Character by Character

Two public-domain TXTs from the resource pages serve as byte corpus: file-md5 carries three in-page hash implementations that cross-check against standard ones with all 54 comparisons equal, and the block ledger, six byte mutations, size rounding, row ids and progress frames are measured item by item.

Why this page is worth a ledger pass

Two TXT files downloaded from resource pages sit on hand: one is 2840 bytes, the other 4995. Opening the hash tool page, its own script already carries three hash implementations, used as fallbacks only when the browser-native interface is unavailable.

Three implementations written inside the page

The page under test is the file MD5 calculator, whose template is 456 lines and 18467 bytes, with the hand-written md5Bytes, sha1Hex and sha256Hex inside the page. Extracted word for word into pure Node, the three are cross-checked against node:crypto's md5, sha1 and sha256 over 18 inputs: all 54 comparisons equal, 0 mismatches. The inputs include both full TXTs, each file's first 64 and last 64 bytes, a middle 1000-byte slice, 0 bytes, 1 byte and 8 RFC vectors.

  • Output code-point length is always 32 / 40 / 64 characters, that is 128 / 160 / 256 bits.
  • First 8 characters of the two MD5 digests: ee965b6b and cd254166; first 8 of SHA-1: f544f5af and a7b9c271; first 8 of SHA-256: 9d6a51bc and 3c2f351d.
  • With hasSubtleCrypto() stubbed to false, the in-page SHA branch and the crypto.subtle branch are character-for-character identical on the same buffer; MD5 never enters calcSHA.
  • The DOM layer measured under a different reading: headless Chrome 154 reports hasSubtleCrypto() as true.

The block ledger of the in-page MD5

For 2840 bytes, measured under the page's padding rule, there are 44 full blocks plus a 24-byte tail block; with padding, 45 blocks and 2880 bytes. The 4995-byte file is 78 full blocks plus a 3-byte tail block; with padding, 79 blocks and 5056 bytes. The 0x80 marker is written at index 2840 and at index 4995; the length field writes two little-endian 32-bit words, low words 22720 and 39960, high words both 0. The page's padLen is written as ((len+8)>>6)+1, whose standard form is ceil((len+9)/64); compared one by one over lengths 0..2000, disagreements: 0. An extra block needs len%64 to reach 56, and neither tail block, 24 nor 3, reaches it.

Block ledger of the 2840-byte file 44 full blocks 24-byte tail block 45 blocks with padding, 2880 bytes

Figure 1: the ledger of one TXT cut into 64-byte blocks.

What one changed byte does

  • Flipping one bit at index 0, at index 1000, and in the last byte: the byte count is unchanged, but 28 to 32 of MD5's 32 hex characters change; SHA-1 changes up to 40; SHA-256 changes 57 to 62.
  • Deleting the first byte takes 2840 to 2839; appending a line break takes it to 2841; adding a BOM takes it to 2843; all three digests change.
  • LF to CRLF stretches the two files to 2926 and 5127 bytes; the increments 86 and 132 equal the LF counts of the two files exactly, and converting back restores the original digests.
  • Re-encoding to GBK or GB18030 was not measured, because Node's built-in TextEncoder only speaks UTF-8 and ships no GBK encoder. What was measured is reading the UTF-8 bytes as GBK: one file yields 1618 code units containing 18 replacement characters, re-encoded to 4375 bytes; the other yields 2693 code units with 20 replacements, re-encoded to 7715 bytes.

Block-level middle states give the mechanism: an isomorphic copy prints the chaining variable after every block, and the 2840 material has 45 blocks and 46 states. Changing 1 byte inside the last block leaves the first 45 states byte-for-byte identical; changing the first byte leaves only the initial vector identical; changing the byte at index 1420 leaves the first 23 states identical. The 4995 material has 79 blocks and 80 states, with the same three readings 79, 1 and 40.

How the size column rounds

formatSize uses three thresholds, 1024, 1048576 and 1073741824; the first tier keeps the plain B, the other two print with toFixed(1) and toFixed(2):

input bytesprinted
10231023 B
10241.0 KB
10485751024.0 KB
10485761.00 MB
10737418231024.00 MB
10737418241.00 GB

Inside the KB tier, 4095 prints 4.0 KB and 5119 prints 5.0 KB; 3583 and 3584 both print 3.5 KB. The per-row size column and the stats area's total size call the same function; the only difference is per-file versus summed input. Six files of 1075 bytes each print 1.0 KB per row while their sum, 6450 bytes, prints 6.3 KB; two files of 1126 bytes print 1.1 KB per row and 2252 prints 2.2 KB. With both materials in the table, the column shows 2.8 KB and 4.9 KB and the total of 7835 bytes shows as 7.7 KB.

One row per file, and both copies of a duplicate count

The row id is stitched from Date.now(), an underscore and a random segment; a full id measured 20 characters long. Stubbing Date.now to one fixed millisecond while letting the random segment use real values, adding 10, 100 and 1000 rows within the same millisecond yields 0 duplicates each time, and the random-segment length distribution is all 6 digits. Stubbing the random segment to one fixed value too, 5 enqueue calls produce only 1 id. Substituting the real clock 2000 times, every random segment is 6 digits; short tails can only be produced with stub values: 0.5 gives 1 digit, 0 gives an empty string that leaves the whole id at 14 characters, and a tiny value gives 000000.

The DOM layer measured directly: adding the same file twice gives two rows with the same digest; 3 files in the table leave ids distinct at 3 and red-flagged duplicate rows at 2, and the stats block reads 「文件总数:3、总大小:9 B、重复文件:2、重复组:1」. The page only counts with red flags; it deletes no rows and exports nothing.

Where that 50% progress bar comes from

In the computing frame the MD5 cell is replaced by a width:50% bar while the other two cells show 「计算中...」. That 50% has nothing to do with real workload; this is simply what the frame writes in the code. The whole probe run spans 57 frames, with done rows and pending rows living in the same frame. This batch comes from the headless Chrome 154 probe page fed synthetic bytes, and it does not mix with the two pure-Node readings above.

The byte ledger of the two TXTs

Both files were downloaded from resource pages: the Baijiaxing 《百家姓》 file is 2840 bytes, 1104 UTF-16 code units, 1104 code points, cut into 87 segments by LF with 84 non-empty and 3 empty, no CRLF, no BOM, its last byte is a line break, and 2604 non-ASCII bytes; the Dizigui 《弟子规》 file is 4995 bytes with both code units and code points at 1783, cut into 133 segments, 109 non-empty and 24 empty, likewise no CRLF and no BOM, with 4818 non-ASCII bytes. In both files code units equal code points, and the non-ASCII byte counts are multiples of 3: 868 and 1606 three-byte characters, 716 and 1240 Han characters in the U+4E00 to U+9FFF range, and 152 and 366 others. The digest only sees the byte stream, not lines: 87 segments or 133 segments both reduce, in the block ledger, to just the two denominators 2840 and 4995.

Frequently asked questions

Q: I only changed the encoding form, so why did the whole digest change?

A digest function reads a byte sequence: a BOM moves the byte count from 2840 to 2843, LF to CRLF moves it to 2926, the input is no longer the same string, and all three digests change with it.

Q: Two rows of the same file, why are the digests identical?

The page does not deduplicate by content: every enqueue takes its own row and computes its own digest, so two rows match naturally; the stats just book them as 2 duplicate files in 1 duplicate group, with red flags grouped by MD5.

Q: Such a small file, why does it still show a 50% cell?

The 50% is a placeholder written into the computing frame, unrelated to file size; in the probe every file passes through three frames, and the MD5 cell of that frame is exactly what the DOM shows.

Resource notes

The two resource links exist only to name where the byte corpus came from. Both files are used here as bytes and lines and nothing else: this article quotes no surname list and no sentence from them, and offers no reading of their content.

📎 Related articles

Shenglü Qimen and Liweng Duiyun Fed Whole Into the Text Diff Page: 154 Common Lines, 665 Added, 401 Removed, and the Stats Line Gives Only Two Numbers 2026-11-08 Measuring a Pinyin Table With Liweng Duiyun: 7160 Hits, 21 Off-Table Chars, 232 Polyphones 2026-11-07 Does the Thousand Character Classic Hold 1000 Distinct Characters: Word Count Measures 996 in Range, 1000 Real Han, 995 Distinct 2026-11-05 Pasting All 120 Chapters of Dream of the Red Chamber Into a Character Frequency Counter: Why Rank 1 Is the Comma at 59,260 Hits 2026-11-04 SOS is 9 tones, the full alphabet is 104: how Morse timing is calculated 2026-10-21