Golang SHA-256: Hash Strings, Files, and Verify Checksums

Deepak Prasad
Tested on RHEL 10.2 with Go 1.27.0
Package crypto/sha256 (Go 1.27.0 standard library)
Applies to Any host with Go installed
Privilege Normal user
Scope Hash strings and files with crypto/sha256, format digests as hexadecimal, stream with sha256.New and io.Copy, verify published checksums, and security boundaries for passwords and keyed authentication. Does not cover full HMAC or password-hash implementations.
Related guides Getting started with Go
Golang hide password input
Golang for loop
Golang string contains
go.mod file not found

Go provides SHA-256 in the standard-library crypto/sha256 package. Use sha256.Sum256 when the bytes are already in memory. Use sha256.New when you hash a stream such as a file.

Need Use
Hash []byte / string sha256.Sum256
Output standard hex checksum fmt.Sprintf("%x", sum) or hex.EncodeToString
Hash file or stream sha256.New + io.Copy
Verify known checksum Decode expected hex and compare digests

The sections below follow that table from one-shot hashing through verification and security limits.

SHA-256 produces a fixed-size digest from any input length. You cannot reverse the digest to recover the original bytes, which is why checksum files publish hashes instead of shipping the whole artifact twice.


Hash a string with sha256.Sum256

sha256.Sum256 hashes an entire byte slice in one call. Start with neutral input such as hello, not password-shaped text, because raw SHA-256 is not how you store passwords.

Convert strings with []byte("hello") or []byte(myString) so the hash covers the exact UTF-8 bytes you intend. Hashing a string literal and hashing the same text read from a file only match when every byte, including line endings, is identical.

go
package main

import (
	"crypto/sha256"
	"fmt"
)

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

Sample output:

output
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

sum has type [32]byte because SHA-256 always produces a 256-bit (32-byte) digest. That fixed array is the raw hash, not a hexadecimal string.

Even a small input change normally produces a very different digest. That avalanche behavior is what makes SHA-256 useful for spotting corrupted downloads or edited files, not for hiding secrets by itself.


Convert a SHA-256 digest to hexadecimal

Most tools and checksum files show SHA-256 as 64 lowercase hex characters. You can print directly from the array:

go
fmt.Printf("%x\n", sum)

When you need a string value, slice the array and use encoding/hex:

go
hex.EncodeToString(sum[:])

Both forms print the same 64-character line for hello. The raw digest is 32 bytes; hex encoding doubles each byte into two digits. Use sum[:] when an API expects []byte instead of [32]byte.

fmt %x and hex.EncodeToString default to lowercase hex, which matches sha256sum and most Linux checksum files. encoding/hex.DecodeString accepts uppercase and lowercase digits when you decode a published checksum.


Hash a file or large stream

Open the path you need to hash, stream its bytes into a hash.Hash, and read the digest from Sum. Replace example.txt with your file path.

Create a small test file so the sample output below is reproducible:

bash
printf 'config line\n' > example.txt
go
package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	f, err := os.Open("example.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%x\n", h.Sum(nil))
}

Sample output for that example.txt file (including the trailing newline):

output
99de1ff2ec2c2887e997209b7e79a5ae4b471fe23fa01e13a96100e208863f6b

That matches printf 'config line\n' | sha256sum for the same bytes.

A missing or extra newline is the most common reason a Go hash disagrees with a published checksum. Hash the exact bytes on disk, not the string you think the file contains.

Sum256 vs sha256.New

Sum256 is convenient when all bytes are already in a []byte. New returns a hash.Hash you feed incrementally with Write or io.Copy, which avoids loading a multi-gigabyte file into memory. The streaming pattern opens the file once, copies through a buffer into the hash, and reads h.Sum(nil) after io.Copy returns. Pick the API that matches how the data arrives, not whichever name looks shorter.

MD5 and SHA-1 remain in the standard library for legacy interoperability, but SHA-256 is the normal modern default when SHA-2 compatibility is required. Avoid MD5 and SHA-1 for new security-sensitive integrity work; keep them only when an older tool or protocol still mandates them.


Verify a file against a known SHA-256 checksum

Hash the file at path, decode the published hex, and compare the raw digests. The example below expects example.txt from the setup above and a bare 64-character digest string.

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() {
	const path = "example.txt"
	const wantHex = "99de1ff2ec2c2887e997209b7e79a5ae4b471fe23fa01e13a96100e208863f6b"

	want, err := hex.DecodeString(strings.TrimSpace(wantHex))
	if err != nil || len(want) != sha256.Size {
		fmt.Println("bad expected hex")
		return
	}
	got, err := fileSHA256(path)
	if err != nil {
		fmt.Println(err)
		return
	}
	if bytes.Equal(got[:], want) {
		fmt.Println("checksum OK")
	} else {
		fmt.Println("checksum mismatch")
	}
}

Sample output:

output
checksum OK

Matching a published checksum detects whether the downloaded bytes differ from the expected digest. That authenticates the download only when the expected checksum itself comes from a trusted source. An attacker-controlled file plus an attacker-controlled checksum string does not prove authenticity.

Trim spaces from copied hex text and confirm the decoded length is exactly sha256.Size (32 bytes) before you compare. Surface decode errors to the user instead of treating a partial buffer as a match.

If the checksum comes from a checksum file rather than a bare 64-character digest, extract the digest field according to that file's documented format before calling DecodeString.

The fileSHA256 helper above is the same streaming pattern as the file-hash example: open, io.Copy into sha256.New, copy h.Sum(nil) into a [32]byte. Reuse one helper for both publishing and verifying checksums so newline and encoding rules stay consistent across your tool.


Compare SHA-256 digests correctly

For ordinary file checksums, compare the 32-byte digests after you decode any hex:

go
bytes.Equal(got[:], want)

For ordinary checksum verification, a matching SHA-256 digest is treated as a match; the chance of unrelated data producing the same digest is negligible in practice.

For keyed message authentication, use HMAC-SHA256 from crypto/hmac and compare MACs with hmac.Equal. Do not concatenate a secret with message bytes and hash the result manually. hmac.Equal compares MAC bytes in constant time, which matters when the compared value could leak timing information to an attacker.

SHA-256 can also fingerprint content for cache keys: the same bytes yield the same digest, and even a small input change normally produces a very different digest. That is a side effect of hashing, not a separate API. For stored passwords, use a password-specific adaptive hash or KDF such as Argon2id or bcrypt rather than raw SHA-256, which an attacker can guess at GPU speed.


Common SHA-256 mistakes and security limits

Most day-to-day failures are format mistakes, not weaknesses in SHA-256 itself. The table below lists the confusions that show up most often when Go developers first work with checksums and digests.

Mistake Correct understanding
Expecting Sum256 to return a hex string It returns [32]byte
Reading a multi-GB file entirely with os.ReadFile Stream with sha256.New + io.Copy
Hash differs from sha256sum Check exact bytes, especially newline differences
Treating SHA-256 as encryption Hashing is one-way; it does not hide data by itself
Using raw SHA-256 for stored passwords Use a password-specific adaptive hash/KDF
Using a normal checksum as proof of authenticity Expected digest must come from a trusted source
Hashing secret + message manually Use HMAC for keyed authentication

References


Summary

sha256.Sum256 hashes strings and small []byte values in one step and returns [32]byte. That array is the raw 32-byte digest; readers usually want the 64-character hex line from fmt.Printf("%x", sum) or hex.EncodeToString(sum[:]).

Files and large bodies use sha256.New with io.Copy so memory stays bounded while you read from disk or any io.Reader. The one-shot and streaming APIs produce the same digest when the underlying bytes match, including every newline and space.

Verify downloads by hashing the file, decoding trusted hex, and comparing with bytes.Equal. A matching digest provides strong evidence that the file bytes match what the publisher hashed, but only when you obtained the digest from a trusted channel.

SHA-256 is for integrity fingerprints and checksum workflows, not password storage or keyed authentication on its own. Use Argon2id or bcrypt for passwords and HMAC-SHA256 when a secret key must prove who produced the hash.


Frequently Asked Questions

1. How do I calculate SHA-256 for a string in Go?

Convert the string to bytes and call sha256.Sum256 on that slice. Format the returned [32]byte digest with fmt.Printf %x or hex.EncodeToString(sum[:]).

2. Why does sha256.Sum256 return [32]byte?

SHA-256 always produces a 256-bit digest, which is 32 bytes. Go returns that as a fixed-size byte array rather than a hex string.

3. Why is a SHA-256 checksum 64 hexadecimal characters?

Each byte of the 32-byte digest is written as two hex digits, so 32 times 2 equals 64 characters in the usual lowercase hex representation.

4. What is the difference between sha256.Sum256 and sha256.New?

Sum256 hashes a complete []byte in one call. New returns a hash.Hash you feed with Write or io.Copy, which is what you want for files and large streams.

5. How do I calculate SHA-256 for a large file without loading it into memory?

Open the file, create sha256.New, and io.Copy from the file into the hash. Read h.Sum(nil) for the 32-byte digest after the copy finishes.

6. How do I verify a downloaded file's SHA-256 checksum?

Hash the file bytes, decode the published 64-character hex from a trusted source, and compare the raw digests with bytes.Equal after trimming surrounding whitespace.

7. Can I use SHA-256 to store passwords?

No. Use a password-specific adaptive hash or KDF such as Argon2id or bcrypt rather than raw SHA-256, which is fast and unsalted in a single call.
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 spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.

  • Go (programming language)
  • Python (programming language)
  • Java (programming language)
  • MongoDB
  • Kubernetes