Hash input guide

Hashing Text vs Bytes: Encoding Pitfalls Explained

A hash function does not see words, JSON meaning, hex values, or Base64 payloads. It receives an exact byte sequence. When software starts from text, an encoding such as UTF-8 first turns characters into bytes. Most mysterious hash mismatches come from comparing different bytes: hidden characters, line endings, Unicode forms, or encoded text that one system decoded before hashing and another system did not.

At a glance

Quick answer

  • Hash functions are deterministic for the same byte sequence, not for text that merely looks equivalent to a person.
  • When hashing text, record the character encoding and any normalization or newline rules that run before the digest calculation.
  • Hex and Base64 are textual representations of bytes; hashing their visible characters is different from decoding them and hashing the represented payload.
  • Debug a mismatch by comparing the pre-hash bytes or their hexadecimal dump before changing algorithms or assuming the hash implementation is broken.

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 6f

Make 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 81

Check 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 bf

Do 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 6f

Base64 text versus decoded bytes

Visible text: aGVsbG8=
Decoded bytes: 68 65 6c 6c 6f

Equivalent 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.