A hash function receives bytes, not visual text
MD5, SHA-1, SHA-256, and SHA-512 operate on bytes. Text mode therefore has an extra step before the hash begins: the string is encoded as UTF-8. VetaTool can also decode pasted Hex or Base64 representations before hashing, but it is not a general file-streaming hashing tool.
That distinction matters whenever two systems compare digests. The algorithm name can match perfectly while the digest differs because the systems supplied different bytes. Treat the digest as the final step of a data pipeline, not as the first place to debug.
ASCII text and UTF-8 bytes
Text: hello
UTF-8: 68 65 6c 6c 6fMake the text-to-byte encoding explicit
ASCII characters use the same byte values in UTF-8, but non-ASCII characters do not fit in one byte. The character é in NFC form is U+00E9 and becomes the UTF-8 bytes C3 A9. A visually identical decomposed form can be written as U+0065 followed by U+0301, producing the different bytes 65 CC 81.
Both strings can render as the same glyph. A hash function still sees two different byte sequences unless the application normalizes them to the same Unicode form before encoding. Do not add normalization silently: it changes the data contract and must match every producer and verifier that participates in the checksum.
Same-looking Unicode, different UTF-8 bytes
NFC: é -> c3 a9
NFD: e + ◌́ -> 65 cc 81Check invisible bytes and line endings
Whitespace that is hard to see is still input. A trailing space adds 20 in UTF-8, a tab adds 09, LF adds 0A, and a Windows CRLF line ending adds 0D 0A. Copying a line from a terminal, reading a file with its final newline, or converting line endings during checkout can therefore change a checksum without changing the visible words.
A UTF-8 byte-order mark is another common boundary problem. U+FEFF encodes as EF BB BF at the start of text. Some readers remove it as metadata while others preserve it as part of the string. Zero-width spaces and non-breaking spaces create the same class of mismatch: invisible or subtle characters become real bytes before hashing.
Line-ending bytes
LF: 0a
CRLF: 0d 0a
UTF-8 BOM: ef bb bfDo not confuse encoded text with decoded payload bytes
Hexadecimal and Base64 often describe binary data using printable characters. The visible hex text 68656c6c6f is ten ASCII characters. Decoding it produces five bytes, 68 65 6C 6C 6F, which spell hello in UTF-8. Hashing the ten visible characters and hashing the five decoded bytes are different operations.
Base64 has the same boundary. The text aGVsbG8= is eight ASCII characters, while Base64 decoding produces the five hello bytes. VetaTool makes this pre-hash step explicit: Text mode hashes the visible representation, while Hex bytes and Base64 bytes validate and decode the representation before hashing. Match that choice to the other system before comparing digests.
Hex text versus decoded bytes
Visible text: 68656c6c6f
Text bytes: 36 38 36 35 36 63 36 63 36 66
Decoded bytes: 68 65 6c 6c 6fBase64 text versus decoded bytes
Visible text: aGVsbG8=
Decoded bytes: 68 65 6c 6c 6fEquivalent data structures can still serialize differently
A hash of JSON text covers the serialization, not the abstract object a parser would produce. Whitespace, indentation, line endings, property order, escape spelling, and numeric formatting can all change the text bytes even when an application considers the parsed data equivalent.
If a protocol needs stable hashes for structured data, define a canonical serialization before hashing and use the same rules everywhere. Simply running a formatter is not a universal canonicalization rule because different formatters can make different choices and because some domains need explicit ordering or number representation constraints.
Same JSON values, different text
{"a":1,"b":2}
{
"b": 2,
"a": 1
}Use a byte-first workflow for hash mismatches
When a known checksum does not match, confirm the complete input pipeline before switching libraries or algorithms. The fastest diagnostic is usually to capture the bytes immediately before the digest operation on both sides and compare them as hexadecimal data.
- Confirm both systems use the same digest algorithm and the same output representation, such as lowercase hexadecimal versus Base64.
- Confirm whether the source is text or binary. For text, record the character encoding used to create bytes.
- Compare input length and a hexadecimal byte dump so spaces, BOMs, line endings, and Unicode differences become visible.
- For hex or Base64, write down whether each side hashes the encoded characters or decodes them first.
- For structured formats, define any normalization or canonical serialization before hashing instead of relying on visual equivalence.
- Only after the input bytes match should you investigate the digest implementation itself.