Skip to content

Instantly share code, notes, and snippets.

@pravdomil
Last active July 9, 2025 14:27
Show Gist options
  • Save pravdomil/bc6c24815057bc8c6c0472cb0aedc748 to your computer and use it in GitHub Desktop.
Save pravdomil/bc6c24815057bc8c6c0472cb0aedc748 to your computer and use it in GitHub Desktop.
A Go CLI that computes, verifies and initializes SHA-256 checksums for files on macOS by storing the digest in the user.checksum.sha256 extended attribute.
package main
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"golang.org/x/sys/unix"
)
const attrName = "user.checksum.sha256"
// computeSHA256Binary returns the raw 32-byte SHA-256 digest of the file at path.
func computeSHA256Binary(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// getXAttrBinary reads exactly 32 bytes from the named xattr (assuming SHA-256 raw digest).
// Returns ENOATTR if the attribute is not present.
func getXAttrBinary(path, attr string) ([]byte, error) {
buf := make([]byte, 32)
n, err := unix.Lgetxattr(path, attr, buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
// setXAttrBinary writes the raw bytes into the named xattr (using Lsetxattr so symlinks are not followed).
func setXAttrBinary(path, attr string, data []byte) error {
return unix.Lsetxattr(path, attr, data, 0)
}
// verifyFile reads (or initializes) the SHA-256 checksum xattr on the given file.
func verifyFile(path string) (stored []byte, actual []byte, err error) {
// Try to read existing checksum attribute
stored, err = getXAttrBinary(path, attrName)
if err != nil && !errors.Is(err, unix.ENOATTR) {
// some error other than “no attribute”
return nil, nil, err
}
missing := errors.Is(err, unix.ENOATTR)
// Compute the actual checksum
actual, err = computeSHA256Binary(path)
if err != nil {
return nil, nil, err
}
if missing {
// No attr → set it to the computed value
if err = setXAttrBinary(path, attrName, actual); err != nil {
return nil, nil, err
}
// stored stays nil, actual is what we just set
return nil, actual, nil
}
// Attr existed → return both for comparison
return stored, actual, nil
}
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <file-or-dir> [...]\n", filepath.Base(os.Args[0]))
os.Exit(1)
}
var failures []string
for _, root := range os.Args[1:] {
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.Mode().IsRegular() {
return nil
}
stored, actual, err := verifyFile(path)
if err != nil {
return err
}
if stored == nil {
fmt.Printf("➕ %s\n", path)
} else if bytes.Equal(stored, actual) {
fmt.Printf("✔ %s\n", path)
} else {
fmt.Printf("❌ %s\n", path)
failures = append(failures, path)
}
return nil
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error walking %s: %v\n", root, err)
os.Exit(1)
}
}
if len(failures) > 0 {
fmt.Fprintln(os.Stderr, "Failed checksums:")
for _, f := range failures {
fmt.Fprintln(os.Stderr, f)
}
os.Exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment