kopia/kopia · error

error hashing

Error message

error hashing

What it means

VerifyAndStrip validates an HMAC-SHA256 signature appended to the end of an input byte range and, while doing so, streams the payload into both the HMAC and the output writer via io.CopyN. This error wraps any failure of that copy — typically the input is SHORTER than expected (fewer than p bytes available), the underlying reader fails, or the output writer fails.

Solutions

  1. Check for the wrapped cause: an io.ErrUnexpectedEOF/EOF usually means the input is truncated — delete the corrupted cache entry and re-fetch
  2. Verify input.Length() is accurate and the signature suffix (last sha256.Size bytes) is present before calling VerifyAndStrip
  3. Re-download or re-verify the blob from the primary storage if the cache copy is truncated
  4. If the output writer is the failure, ensure it is open and writable before verification

Example fix

// before
if _, err := io.CopyN(io.MultiWriter(h, output), r, int64(p)); err != nil {
    return errors.Wrap(err, "error hashing")
}
// after
if _, err := io.CopyN(io.MultiWriter(h, output), r, int64(p)); err != nil {
    return errors.Wrapf(err, "error hashing %d-byte payload (input length %d)", p, input.Length())
}
Defensive patterns

Strategy: validation

Validate before calling

// verify the input plausibly contains payload + signature before verifying
if input.Length() < sha256.Size {
    return errors.New("input too short to contain HMAC signature")
}

Try / catch

if err := h.VerifyAndStrip(input, output, secret); err != nil {
    if stderrors.Is(err, io.ErrUnexpectedEOF) || stderrors.Is(err, io.EOF) {
        // treat as corrupted/truncated cache entry: invalidate and re-fetch
    }
    return fmt.Errorf("verification failed: %w", err)
}

Prevention

When it happens

Trigger: Calling Verify (or reading blobs from cache) where input.Length() - sha256.Size exceeds the actual readable bytes, the underlying storage reader errors mid-copy, or the destination output writer fails while draining the payload.

Common situations: Truncated or corrupted cache files whose declared length exceeds real content; storage backend read errors during verification; concurrent modification shrinking the data between Length() and read.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/14341fc2144e2bdb. Report an issue: GitHub.

Appendix: source

Thrown at internal/hmac/hmac.go:38

	var hash [sha256.Size]byte

	output.Write(h.Sum(hash[:0])) //nolint:errcheck
}

// VerifyAndStrip verifies that given block of bytes has correct HMAC-SHA256 checksum and strips it.
func VerifyAndStrip(input gather.Bytes, secret []byte, output *gather.WriteBuffer) error {
	if input.Length() < sha256.Size {
		return errors.New("invalid data - too short")
	}

	p := input.Length() - sha256.Size

	h := hmac.New(sha256.New, secret)
	r := input.Reader()

	if _, err := io.CopyN(io.MultiWriter(h, output), r, int64(p)); err != nil {
		return errors.Wrap(err, "error hashing")
	}

	var sigBuf, actualSignature [sha256.Size]byte

	validSignature := h.Sum(sigBuf[:0])

	n, err := r.Read(actualSignature[:])
	if err != nil || n != sha256.Size {
		return errors.Wrap(err, "error reading signature")
	}

	if hmac.Equal(validSignature, actualSignature[:]) {
		return nil
	}

	return errors.New("invalid data - corrupted")
}

View on GitHub (pinned to 82495e54b5)