cryptographyhashhmacgosecurity

SHA-256, SHA-512, and HMAC Explained — Hash Functions for Every Language

·7 min read

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

go
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:

go
// 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.

go
// 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)

Try Hash Generator

Generate SHA-256, SHA-512, SHA-1 hashes and HMAC signatures. Code snippets for 5 languages.