golang/go · error

crypto/md5: invalid hash state size

Error message

crypto/md5: invalid hash state size

What it means

Returned by md5 digest.UnmarshalBinary when the magic prefix is correct but the total byte length is not marshaledSize. The md5 layout is fixed-length (magic + 4 x uint32 state + 64-byte block buffer + uint64 length counter); any truncation or extra bytes fail this check.

Source

Thrown at src/crypto/md5/md5.go:86

func (d *digest) AppendBinary(b []byte) ([]byte, error) {
	b = append(b, magic...)
	b = byteorder.BEAppendUint32(b, d.s[0])
	b = byteorder.BEAppendUint32(b, d.s[1])
	b = byteorder.BEAppendUint32(b, d.s[2])
	b = byteorder.BEAppendUint32(b, d.s[3])
	b = append(b, d.x[:d.nx]...)
	b = append(b, make([]byte, len(d.x)-d.nx)...)
	b = byteorder.BEAppendUint64(b, d.len)
	return b, nil
}

func (d *digest) UnmarshalBinary(b []byte) error {
	if len(b) < len(magic) || string(b[:len(magic)]) != magic {
		return errors.New("crypto/md5: invalid hash state identifier")
	}
	if len(b) != marshaledSize {
		return errors.New("crypto/md5: invalid hash state size")
	}
	b = b[len(magic):]
	b, d.s[0] = consumeUint32(b)
	b, d.s[1] = consumeUint32(b)
	b, d.s[2] = consumeUint32(b)
	b, d.s[3] = consumeUint32(b)
	b = b[copy(d.x[:], b):]
	b, d.len = consumeUint64(b)
	d.nx = int(d.len % BlockSize)
	return nil
}

func consumeUint64(b []byte) ([]byte, uint64) {
	return b[8:], byteorder.BEUint64(b[0:8])
}

func consumeUint32(b []byte) ([]byte, uint32) {
	return b[4:], byteorder.BEUint32(b[0:4])

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Store and retrieve the full marshaledSize bytes unchanged; verify byte-for-byte length.
  2. If the buffer transits a length-prefixed protocol, check the length prefix matches marshaledSize.
  3. Re-hash from the original input if the state is unrecoverable.
  4. Pin producer and consumer Go major versions when persisting MD5 state long-term.

Example fix

// before
d.UnmarshalBinary(b[:len(b)-2]) // truncated -> error
// after
d.UnmarshalBinary(b) // full marshaledSize
Defensive patterns

Strategy: validation

Validate before calling

if len(b) != marshaledSize {
    return fmt.Errorf("md5 state size mismatch: got %d want %d", len(b), marshaledSize)
}

Prevention

When it happens

Trigger: Passing bytes whose magic is valid but length differs from the fixed marshaledSize: a truncated buffer, an extra byte appended, or bytes from a build whose marshaled layout differs.

Common situations: Database BLOB column truncating; a framing bug that drops or duplicates bytes; version skew between the Go build that marshaled the state and the one unmarshaling it.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/656013d0cd68b9e9. Report an issue: GitHub.