What is a Cryptographic Hash?
A cryptographic hash function takes an input of any length and produces a fixed-length output (the digest). The same input always produces the same output, but even a tiny change in the input completely changes the output. Crucially, the process is one-way — you cannot reconstruct the input from the hash.
SHA-256 produces a 256-bit (32-byte, 64 hex character) digest. SHA-512 produces a 512-bit (64-byte, 128 hex character) digest. SHA-1 produces a 160-bit (20-byte, 40 hex character) digest.
SHA-256 vs SHA-512
SHA-256 is the standard choice for most applications. It is fast, widely supported, and provides strong security (no known practical attacks).
SHA-512 is faster than SHA-256 on 64-bit processors because its internal operations work on 64-bit words. The larger output is overkill for most applications but used in high-security contexts.
SHA-1 is broken for cryptographic purposes — practical collision attacks exist. Avoid it for new code. It only appears in legacy systems and Git object identifiers (which are being migrated to SHA-256).
Plain Hash vs HMAC
A plain hash is deterministic and public — anyone can compute SHA-256 of "hello". This is useful for file integrity checks and deduplication but provides no authentication.
HMAC (Hash-based Message Authentication Code) binds a secret key to the hash:
HMAC(key, message) = Hash(key XOR opad || Hash(key XOR ipad || message))Only parties who know the key can generate or verify an HMAC. This is how Stripe, AWS, and GitHub webhook signatures work.
Go Hash Examples
import (
"crypto/sha256"
"crypto/sha512"
"crypto/hmac"
"encoding/hex"
)
// SHA-256
func sha256Hex(s string) string {
h := sha256.Sum256([]byte(s))
return hex.EncodeToString(h[:])
}
// SHA-512
func sha512Hex(s string) string {
h := sha512.Sum512([]byte(s))
return hex.EncodeToString(h[:])
}
// HMAC-SHA-256
func hmacSHA256(message, key string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(message))
return hex.EncodeToString(mac.Sum(nil))
}
// Verify HMAC (use hmac.Equal to prevent timing attacks)
func verifyHMAC(message, key, expected string) bool {
actual := hmacSHA256(message, key)
expectedBytes, _ := hex.DecodeString(expected)
actualBytes, _ := hex.DecodeString(actual)
return hmac.Equal(actualBytes, expectedBytes)
}Verifying Webhook Signatures
Most webhook providers (Stripe, GitHub, Shopify) send an HMAC-SHA-256 signature in a header so you can verify the payload was not tampered with:
// GitHub webhook verification
func verifyGitHubWebhook(payload []byte, secret, signature string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}SHA-256 is Not for Passwords
SHA-256 is too fast — a modern GPU can compute billions of SHA-256 hashes per second, making brute-force attacks cheap. For passwords, use bcrypt, scrypt, or Argon2id. These algorithms are intentionally slow and memory-hard.
// DO NOT use SHA-256 for passwords
// badHash := sha256.Sum256([]byte(password)) — WRONG
// Use bcrypt instead
import "golang.org/x/crypto/bcrypt"
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)