DevKitHub

Your checksum does not match. Here are the four reasons.

2 min read

Hash implementations are either bit-exact or obviously broken — there is no partly-correct hash. So when two digests disagree, the inputs disagree.

Every conforming implementation of MD5 or SHA-256 produces identical output for identical bytes. That is the entire point of a published specification with test vectors. So a mismatch is never "their MD5 is different from ours" — it means the two sides are hashing different bytes. In practice it is one of four things.

1. A trailing newline

This is by far the most common. echo adds a newline, so the shell is hashing one more byte than you think.

bash
echo abc | md5sum
# 0bee89b07a248e27c83fc3d5951213c1

printf %s abc | md5sum
# 900150983cd24fb0d6963f7d28e17f72   ← the RFC 1321 vector

Use printf %s or echo -n. The same applies to text files: most editors add a trailing newline on save, and a file that "looks identical" in a diff may not be byte-identical.

2. Character encoding

Hashes are defined over bytes, and text is not bytes until you choose an encoding. é is two bytes in UTF-8 and one in Latin-1, so two systems that disagree about encoding are hashing genuinely different input.

This shows up between a JVM defaulting to the platform charset and a service that assumed UTF-8, and between a database column stored as latin1 and an application reading it as UTF-8. Fix it by making the encoding explicit on both sides rather than relying on a default.

3. Line endings

A file with Windows CRLF line endings has one extra byte per line compared with the same file using Unix LF. For a thousand-line file that is a thousand bytes of difference, and a completely different digest.

Git's core.autocrlf converts line endings on checkout, which means the file on disk can legitimately differ from the file in the repository — and the checksum in your CI will differ from the one on your laptop. This is worth ruling out early on any Windows-plus-Linux pipeline.

4. Hashing the wrong thing

Less common but more confusing: the two sides are hashing different representations of the same value. Some possibilities worth checking:

  • One side hashes a JSON string, the other hashes an object serialised with different key order or spacing.
  • One side hashes a file, the other hashes a Base64 encoding of it.
  • One side includes a trailing null terminator from a C string.
  • One side hashes the hex digest of another hash rather than its raw bytes.

The last one is a classic in signature code: hash(hex(hash(x))) and hash(hash(x)) are both plausible-looking, and they are not the same.

How to isolate it in two minutes

Start from a known-good value. abc hashes to 900150983cd24fb0d6963f7d28e17f72 under MD5 — that is published in RFC 1321. If a system cannot reproduce that, the problem is in how it is being called, not in the algorithm.

Then compare byte counts before comparing digests. If the two sides disagree on length, you have found it without needing to think about hashing at all.

bash
printf %s "$value" | wc -c    # bytes, not characters
Hash GeneratorShows the UTF-8 byte count alongside all three digests, which is usually enough to spot the mismatch immediately.

Tools for this

Next in this path

NextWhy your contrast checker disagrees with the accessibility audit