Golang SHA-256: Sum256, hex, files, streams, verification, and security notes

Golang SHA-256 with crypto/sha256: Sum256 and hex, sha256.New streaming io.Copy, file checksums and verification, compare files, subtle comparison, HMAC sign/verify, MD5 vs SHA-1 vs SHA-256, passwords use bcrypt; quick reference table and official pkg.go.dev links.

Published

Updated

Read time 10 min read

Reviewed byDeepak Prasad

Golang SHA-256: Sum256, hex, files, streams, verification, and security notes

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' '…' | sha256sum for 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 — use Write / io.Copy, then Sum(nil) for streaming or large inputs.
  • Display: fmt.Printf("%x", sum[:]) or hex.EncodeToString(sum[:]).
  • Compare digests: bytes.Equal(a[:], b[:]) after decoding any hex you trust from disk.
  • Keyed integrity: crypto/hmac with sha256.New as 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

sha256.Sum256(data []byte) [32]byte hashes the entire slice at once.

go
package main

import (
	"crypto/sha256"
	"fmt"
)

func main() {
	sum := sha256.Sum256([]byte("this is a password"))
	fmt.Printf("%x\n", sum)
}
Output

Run prints a 64-character hex line. For this exact UTF-8 string (no trailing newline), the digest is:

text
289ca48885442b5480dd76df484e1f90867a2961493b7c60e542e84addce5d1e

Small edits change the digest completely:

go
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)
}
Output

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[:].

go
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[:]))
}
Output

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.

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

go
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.Reader chains).
  • The file (or object) may not fit comfortably in memory.
  • You need to Reset and reuse a single hash instance in a tight loop.
  • You are implementing crypto/hmac.New (HMAC builds on hash.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.

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

  1. Generate the SHA256 hash of the local file.
  2. Compare it with the expected checksum.
  3. If both values match, the file content is unchanged.
go
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:

go
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)
}
Output

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

go
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))
}
Output

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:

go
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)
}
Output

Run prints a 64-character fingerprint and a user-profile: cache key prefix.

A simple pattern is:

go
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).
  • Sum256 returns [32]byte; hash.Sum(nil) returns []byte of length 32 for a fresh sha256.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


Frequently Asked Questions

1. Should I use sha256.Sum256 or sha256.New?

Use Sum256 when the whole message is already in memory. Use New when you stream chunks, hash a file with io.Copy, or build the message across multiple Write calls.

2. Why fmt.Printf with %x?

Sum256 returns [32]byte; %x formats those bytes as lowercase hexadecimal, matching sha256sum and many checksum files.

3. Is SHA-256 encryption?

No. It produces a digest you cannot reverse to recover the message; it does not provide confidentiality by itself.

4. Does a small input change make a similar hash?

No. Avalanche means tiny edits produce unrelated digests.

5. Should I use SHA-256 for passwords?

No. Use a password hash such as bcrypt or argon2 from golang.org/x/crypto; they add salt and tuning parameters so rainbow tables and brute force are impractical.

6. How do I compare a SHA-256 hash safely?

For public checksums, bytes.Equal on the raw digests is fine. For secret-derived digests (for example HMAC outputs used as tokens), prefer crypto/subtle.ConstantTimeCompare to reduce timing leaks.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …