henrygd/beszel · error

unsupported hash length: %d (expected 40 for SHA1 or 64 for

Error message

unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)

What it means

Thrown by downloadFile when the expected checksum string length is neither 40 (SHA1) nor 64 (SHA256) hex characters. The tool selects the hash algorithm purely by length, so any other length is rejected as a configuration error and the temp file is cleaned up.

Source

Thrown at agent/tools/fetchsmartctl/main.go:86

	tmp := dest + ".tmp"
	f, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
	if err != nil {
		return fmt.Errorf("open tmp: %w", err)
	}

	// Determine hash algorithm based on length (SHA1=40, SHA256=64)
	var hasher hash.Hash
	if shaHex := strings.TrimSpace(shaHex); shaHex != "" {
		cleanSha := strings.ToLower(strings.ReplaceAll(shaHex, " ", ""))
		switch len(cleanSha) {
		case 40:
			hasher = sha1.New()
		case 64:
			hasher = sha256.New()
		default:
			f.Close()
			os.Remove(tmp)
			return fmt.Errorf("unsupported hash length: %d (expected 40 for SHA1 or 64 for SHA256)", len(cleanSha))
		}
	}

	var mw io.Writer = f
	if hasher != nil {
		mw = io.MultiWriter(f, hasher)
	}
	if _, err := io.Copy(mw, resp.Body); err != nil {
		f.Close()
		os.Remove(tmp)
		return fmt.Errorf("write tmp: %w", err)
	}
	if err := f.Close(); err != nil {
		os.Remove(tmp)
		return fmt.Errorf("close tmp: %w", err)
	}

	if hasher != nil && shaHex != "" {

View on GitHub (pinned to b38fb7dafa)

Solutions

  1. Count the checksum characters: must be exactly 40 (SHA1) or 64 (SHA256) hex digits
  2. Re-copy the checksum, excluding any filename or whitespace artifacts
  3. If only SHA512 is published, compute a SHA256 locally instead of passing it
  4. Strip prefixes like 'sha256=' before passing

Example fix

// before
cleanSha := strings.ToLower(strings.ReplaceAll(shaHex, " ", ""))
// after
cleanSha := strings.ToLower(strings.TrimSpace(shaHex))
if i := strings.IndexByte(cleanSha, ' '); i >= 0 {
	cleanSha = cleanSha[:i] // drop trailing filename from 'checksum  file' paste
}
Defensive patterns

Strategy: validation

Validate before calling

sha := strings.TrimSpace(shaInput)
if sha != "" && len(sha) != 40 && len(sha) != 64 {
	return fmt.Errorf("checksum must be 40 (SHA1) or 64 (SHA256) hex chars, got %d", len(sha))
}
for _, r := range sha {
	if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
		return fmt.Errorf("checksum contains non-hex character %q", r)
	}
}

Type guard

func isHexLen(s string, n int) bool {
	if len(s) != n {
		return false
	}
	_, err := hex.DecodeString(s)
	return err == nil
}

Try / catch

if err := downloadFile(url, dest, sha); err != nil {
	if strings.Contains(err.Error(), "unsupported hash length") {
		fmt.Printf("bad checksum %q (len %d): use SHA1(40) or SHA256(64)\n", sha, len(strings.TrimSpace(sha)))
	}
	return err
}

Prevention

When it happens

Trigger: Passing a shaHex argument that is empty is fine (skips verification), but a non-empty checksum of wrong length — e.g. truncated SHA256, SHA512 (128 chars), MD5 (32 chars), or containing stray non-hex text pasted from a checksums page.

Common situations: Copy-pasting the wrong column from a checksums file; using a SHA512 checksum; shell variable truncation; including a filename in the pasted checksum line.

Related errors


AI-assisted analysis of henrygd/beszel@b38fb7dafa (2026-08-31). Data as JSON: /api/errors/6da1f00ad2b87383. Report an issue: GitHub.