SHA-256 in Go lives in the standard library package crypto/sha256. It implements FIPS 180-4 and outputs a 32-byte (256-bit) digest—often shown as 64 hexadecimal characters. This guide covers one-shot hashing, hex formatting, files and streams, verification and comparison, how SHA-256 fits next to MD5 and SHA-1, and mistakes to avoid (especially password storage). For periodic re-hashing of changing files, you can pair digests with a time.Ticker.
Examples were run with Go 1.24 on Linux. One-shot hex lines match
printf '%s' '…' | sha256sumfor the same byte sequence (watch for accidental newlines).
SHA256 in Go at a Glance
sha256.Sum256([]byte)→[32]byte— best when the whole input is already in memory.sha256.New()→hash.Hash— useWrite/io.Copy, thenSum(nil)for streaming or large inputs.- Display:
fmt.Printf("%x", sum[:])orhex.EncodeToString(sum[:]). - Compare digests:
bytes.Equal(a[:], b[:])after decoding any hex you trust from disk. - Keyed integrity:
crypto/hmacwithsha256.Newas the hash — not raw SHA-256 of a secret alone.
Quick reference table for common SHA256 tasks
| Task | API | Notes |
|---|---|---|
Hash a string / small []byte |
sha256.Sum256 |
No extra Write; result is [32]byte. |
| Show hex | fmt.Sprintf("%x", sum[:]) or hex.EncodeToString |
Lowercase %x; decode with hex.DecodeString when verifying text checksums. |
| Hash a file | io.Copy(sha256.New(), file) |
O(1) memory vs reading whole file. |
| Incremental / protocol framing | h := sha256.New(); h.Write(...) |
Call Write multiple times, then h.Sum(nil). |
| Compare two digests | bytes.Equal / subtle.ConstantTimeCompare |
Use subtle for secrets. |
| Passwords | Not SHA-256 | Use bcrypt or Argon2. |
Hash a String Using SHA256
Using sha256.Sum256 (recommended for simple inputs)
sha256.Sum256(data []byte) [32]byte hashes the entire slice at once.
package main
import (
"crypto/sha256"
"fmt"
)
func main() {
sum := sha256.Sum256([]byte("this is a password"))
fmt.Printf("%x\n", sum)
}Run prints a 64-character hex line. For this exact UTF-8 string (no trailing newline), the digest is:
289ca48885442b5480dd76df484e1f90867a2961493b7c60e542e84addce5d1eSmall edits change the digest completely:
package main
import (
"crypto/sha256"
"fmt"
)
func main() {
lower := sha256.Sum256([]byte("this is a password"))
upper := sha256.Sum256([]byte("This is a password"))
fmt.Printf("lower: %x\n", lower)
fmt.Printf("upper: %x\n", upper)
}Run prints two unrelated digests.
Convert the SHA256 output to a hexadecimal string
Sum256 returns a fixed array. Slice to []byte for APIs that need a slice: sum[:].
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
sum := sha256.Sum256([]byte("hello"))
fmt.Printf("fmt %%x: %x\n", sum)
fmt.Println("hex.Encode:", hex.EncodeToString(sum[:]))
}Both lines show 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 for hello.
Hash Files with SHA256
Calculate the SHA256 checksum of a file
Stream through hash.Hash so you do not load the entire file into RAM.
package main
import (
"crypto/sha256"
"fmt"
"io"
"os"
)
func main() {
payload := []byte("config line\n")
f, err := os.CreateTemp("", "cfg-*.txt")
if err != nil {
fmt.Println(err)
return
}
name := f.Name()
defer os.Remove(name)
if _, err := f.Write(payload); err != nil {
fmt.Println(err)
return
}
f.Close()
in, err := os.Open(name)
if err != nil {
fmt.Println(err)
return
}
defer in.Close()
h := sha256.New()
if _, err := io.Copy(h, in); err != nil {
fmt.Println(err)
return
}
fmt.Printf("%x\n", h.Sum(nil))
}Run prints 99de1ff2ec2c2887e997209b7e79a5ae4b471fe23fa01e13a96100e208863f6b for the embedded payload (same as printf 'config line\n' | sha256sum).
Verify a downloaded file against a known checksum
Decode the published hex, hash the file bytes, then compare. Trim spaces and validate hex length before production use.
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"strings"
)
func fileSHA256(path string) ([32]byte, error) {
f, err := os.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return [32]byte{}, err
}
var out [32]byte
copy(out[:], h.Sum(nil))
return out, nil
}
func main() {
// Expected digest for our temp payload line (see previous example).
const wantHex = "99de1ff2ec2c2887e997209b7e79a5ae4b471fe23fa01e13a96100e208863f6b"
f, _ := os.CreateTemp("", "verify-*.txt")
defer os.Remove(f.Name())
_, _ = f.Write([]byte("config line\n"))
_ = f.Close()
want, err := hex.DecodeString(strings.TrimSpace(strings.ToLower(wantHex)))
if err != nil || len(want) != sha256.Size {
fmt.Println("bad expected hex")
return
}
got, err := fileSHA256(f.Name())
if err != nil {
fmt.Println(err)
return
}
if bytes.Equal(got[:], want) {
fmt.Println("checksum OK")
} else {
fmt.Println("checksum mismatch")
}
}Run prints checksum OK.
SHA256 for Streams and Large Data
Using sha256.New with io.Copy
sha256.New() implements hash.Hash: Write, Sum, and Reset. io.Copy repeatedly reads into a buffer and writes into the hash, which is the standard pattern for large bodies and io.Reader pipelines.
When to use sha256.New instead of sha256.Sum256
- The input arrives in chunks (network frames,
io.Readerchains). - The file (or object) may not fit comfortably in memory.
- You need to
Resetand reuse a single hash instance in a tight loop. - You are implementing
crypto/hmac.New(HMAC builds onhash.Hash).
If you already hold the full []byte, Sum256 is shorter and avoids an extra allocation from Sum.
Compare and Validate SHA256 Hashes
Check whether two files are identical using SHA256
Equal digests mean equal inputs with overwhelming probability (collision resistance is part of SHA-256’s design goal; accidental collisions are not a practical issue for integrity checks).
The helper below matches fileSHA256 from Verify a downloaded file against a known checksum; it is repeated so this program stays copy-paste complete.
package main
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"os"
)
func fileSHA256(path string) ([32]byte, error) {
f, err := os.Open(path)
if err != nil {
return [32]byte{}, err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return [32]byte{}, err
}
var out [32]byte
copy(out[:], h.Sum(nil))
return out, nil
}
func main() {
a, _ := os.CreateTemp("", "cmp-a-*")
b, _ := os.CreateTemp("", "cmp-b-*")
defer os.Remove(a.Name())
defer os.Remove(b.Name())
payload := []byte("same-bytes")
_, _ = a.Write(payload)
_, _ = b.Write(payload)
_ = a.Close()
_ = b.Close()
ha, _ := fileSHA256(a.Name())
hb, _ := fileSHA256(b.Name())
fmt.Println("same files:", bytes.Equal(ha[:], hb[:]))
}Run prints same files: true.
Validate user input against a known checksum
Treat the expected digest as untrusted text: normalize case, trim spaces, check hex.DecodedLen(len(hexString)) == sha256.Size after decode, and surface parse errors to the user instead of comparing partial buffers.
For digests derived from secrets (for example comparing an HMAC to a client header), compare with subtle.ConstantTimeCompare on equal-length byte slices to reduce timing side channels.
Common SHA256 Use Cases in Go
SHA256 is commonly used when you need a fixed-length fingerprint of some data. In Go, it is useful for verifying file integrity, validating request payloads, detecting content changes, and generating cache keys.
However, SHA256 is a hashing algorithm, not encryption. You can compare a SHA256 hash with another hash, but you cannot recover the original input from the digest alone.
File integrity verification
One of the most common SHA256 use cases is verifying whether a file was downloaded, copied, or transferred correctly. Many Linux ISO images, software packages, and release artifacts provide a .sha256 checksum file for this reason.
The idea is simple:
- Generate the SHA256 hash of the local file.
- Compare it with the expected checksum.
- If both values match, the file content is unchanged.
package main
import (
"crypto/sha256"
"fmt"
"io"
"log"
"os"
)
func main() {
payload := []byte("release artifact bytes\n")
expected := fmt.Sprintf("%x", sha256.Sum256(payload))
f, err := os.CreateTemp("", "artifact-*.bin")
if err != nil {
log.Fatal(err)
}
path := f.Name()
defer os.Remove(path)
if _, err := f.Write(payload); err != nil {
log.Fatal(err)
}
if err := f.Close(); err != nil {
log.Fatal(err)
}
file, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer file.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, file); err != nil {
log.Fatal(err)
}
checksum := fmt.Sprintf("%x", hasher.Sum(nil))
fmt.Println("SHA256 checksum:", checksum)
if checksum == expected {
fmt.Println("File verification successful")
} else {
fmt.Println("File verification failed")
}
}Streaming with io.Copy matches the pattern in Calculate the SHA256 checksum of a file; it avoids loading the whole file into memory.
Here expected is precomputed from the same payload bytes so the demo succeeds; in production you would load the published hex from a checksum file or release API and compare after normalizing spaces and case.
Run prints the checksum line and File verification successful.
This is useful for:
- verifying downloaded files
- checking backup integrity
- detecting corrupted files
- validating release artifacts
API request signing and validation
SHA256 is also used in API request validation. For secure request signing, you should usually use HMAC-SHA256, not plain SHA256.
Plain SHA256 only proves that some data produced a specific hash. It does not prove who created the hash. HMAC-SHA256 uses a shared secret key, so the server can verify that the request came from a trusted client.
Example:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
secretKey := []byte("my-secret-key")
message := []byte("POST:/api/orders:{\"order_id\":123}")
mac := hmac.New(sha256.New, secretKey)
mac.Write(message)
signature := hex.EncodeToString(mac.Sum(nil))
fmt.Println("HMAC-SHA256 signature:", signature)
}Run prints a line such as HMAC-SHA256 signature: c1e884b231f9633595a250d5affd164eb71960b60014e468d8ac106fd7acaa6f for the key and message shown.
On the server side, generate the HMAC again using the same secret key and request payload. Then compare both signatures with hmac.Equal (preferred over bytes.Equal for MACs):
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func isValidSignature(message, receivedSignature string, secretKey []byte) bool {
mac := hmac.New(sha256.New, secretKey)
mac.Write([]byte(message))
expectedSignature := mac.Sum(nil)
receivedSignatureBytes, err := hex.DecodeString(receivedSignature)
if err != nil {
return false
}
return hmac.Equal(expectedSignature, receivedSignatureBytes)
}
func main() {
secret := []byte("my-secret-key")
msg := "POST:/api/orders:{\"order_id\":123}"
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(msg))
sig := hex.EncodeToString(mac.Sum(nil))
fmt.Println("valid:", isValidSignature(msg, sig, secret))
fmt.Println("invalid:", isValidSignature(msg, "deadbeef", secret))
}Run prints valid: true then invalid: false.
hmac.Equal compares MACs in constant time; use it instead of bytes.Equal for signature bytes.
This pattern is useful for:
- validating webhook requests
- signing REST API payloads
- verifying request authenticity
- protecting internal service-to-service communication
Content fingerprinting and cache keys
SHA256 can also be used to create a unique fingerprint for a piece of content. If the content changes, the SHA256 hash also changes.
This is useful for generating cache keys, detecting duplicate content, or checking whether data has changed since the last processing run.
Example:
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
content := []byte(`{"user_id":101,"role":"admin","active":true}`)
sum := sha256.Sum256(content)
fingerprint := hex.EncodeToString(sum[:])
cacheKey := "user-profile:" + fingerprint
fmt.Println("Fingerprint:", fingerprint)
fmt.Println("Cache key:", cacheKey)
}Run prints a 64-character fingerprint and a user-profile: cache key prefix.
A simple pattern is:
hash := sha256.Sum256([]byte(data))
key := fmt.Sprintf("cache:%x", hash)Here data is your serialized payload (for example JSON bytes). In real code you must import fmt and crypto/sha256.
Use this when the same input should always generate the same key, but any change in input should produce a different key.
SHA256 vs MD5 vs SHA1 in Go
Packages: crypto/md5, crypto/sha1, crypto/sha256.
| Algorithm | Output size | Guidance |
|---|---|---|
| MD5 | 16 bytes | Broken for collision resistance; avoid for security contexts. Legacy checksums only. |
| SHA-1 | 20 bytes | Deprecated for TLS and code signing; avoid for new security designs. |
| SHA-256 | 32 bytes | Default choice today for integrity and as the hash inside modern signatures. |
Which hash algorithm should you choose?
For new integrity or signature schemes, prefer SHA-256 (or SHA-512 from crypto/sha512 if you need a wider block). Use MD5 or SHA-1 only when you must interoperate with an older system that still mandates them, and document the risk.
Common Mistakes and Security Notes
Why SHA256 should not be used for password hashing
Passwords need slow, salted, memory-hard functions so offline guessing is expensive. SHA-256 is fast and unsalted in a single call, so attackers can try billions of guesses per second on GPUs. Use golang.org/x/crypto/bcrypt or Argon2 instead.
SHA256 output size and format explained
- Raw digest: 32 bytes (
sha256.Size). - Lowercase hex: 64 characters (each byte maps to two nibbles).
Sum256returns[32]byte;hash.Sum(nil)returns[]byteof length 32 for a freshsha256.New()instance.
Single source of truth (no second copy of full examples)
The quick reference table at the top lists APIs and when to use them. Runnable programs appear under each heading above (string hash, hex encoding, temp-file checksum, download verification, streaming notes, two-file compare, HMAC sign and verify, cache fingerprint). A second full copy of those programs at the end of the page had drifted out of sync (for example a non-existent example.tar.gz path and fragment-only “verify checksum” snippets), so this article keeps one canonical set of examples.
Summary
Use sha256.Sum256 for small in-memory messages; use sha256.New with Write / io.Copy for files and streams. Format with %x or encoding/hex. Verify downloads by decoding expected hex and using bytes.Equal on 32-byte digests; use subtle.ConstantTimeCompare when comparing secret MACs. Prefer SHA-256 over MD5 or SHA-1 for new work; never use plain SHA-256 for passwords—use bcrypt or Argon2. For keyed integrity, reach for HMAC-SHA256 rather than hashing secrets with raw SHA-256 alone.
References
- Package crypto/sha256
- Package crypto/hmac
- Package crypto/subtle
- Package golang.org/x/crypto/bcrypt
- FIPS 180-4 (SHA family)

