How to Generate a SHA-256 Hash: Online, Command Line and Code — and Why Results Differ
SHA-256 turns any input into a fixed 256-bit fingerprint, written as 64 hexadecimal characters. Generating one takes a single command on every platform. Getting the hash you expected is the harder part: a trailing newline, a different text encoding or a Base64 output where hex was wanted all produce a perfectly valid, completely different value. Below are the commands for each platform and language, followed by the byte-level reasons two correct-looking hashes disagree.
What the SHA-256 hash algorithm produces
SHA-256 is one of the SHA-2 functions defined by NIST in FIPS 180-4. It reads input as bytes, processes them in 512-bit blocks and produces a 256-bit digest. Hex encoding writes each byte as two characters, so the result is always 64 hex characters, whether the input was one byte or a 4 GB disk image.
Three properties make it useful. It is deterministic: the same bytes always produce the same hash. A one-bit change in the input changes roughly half of the output bits. And there is no known practical way to find two inputs with the same hash, or to work backwards from a hash to the input.
The point that matters in practice is the first word of that description: bytes. SHA-256 never sees text. It sees whatever bytes your tool produced from the text, and that is where almost every mismatch comes from.
SHA-256("") = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
SHA-256("abc") = ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Keep these known values handy. Hashing abc in a new tool and comparing against the value above is the quickest way to confirm the tool is doing what you think.
On the command line: Linux, macOS and Windows
# Linux (GNU coreutils)
sha256sum file.iso
printf '%s' 'hello' | sha256sum
# macOS (sha256sum also ships with recent macOS; shasum always does)
shasum -a 256 file.iso
printf '%s' 'hello' | shasum -a 256
# Anywhere OpenSSL is installed
openssl dgst -sha256 file.iso
# Windows PowerShell — SHA256 is the default algorithm
Get-FileHash .\file.iso
Get-FileHash .\file.iso -Algorithm SHA256 | Select-Object -ExpandProperty Hash
# Windows cmd
certutil -hashfile file.iso SHA256
PowerShell and certutil print upper-case hex, while sha256sum prints lower case. Hex is case-insensitive, so 2CF24D… and 2cf24d… are the same hash. Compare them case-insensitively instead of as raw strings, or a correct download will look corrupted.
To check a download against a published checksum file, let the tool do the comparison instead of eyeballing 64 characters. sha256sum -c reads lines of the form <hash> <filename> and reports OK or FAILED for each file.
sha256sum -c SHA256SUMS
# ubuntu.iso: OK
echo "2cf24dba...9824 file.txt" | sha256sum -c -
The newline trap: why echo gives the wrong hash
The most common SHA-256 bug is not in any library. echo appends a newline, so echo hello | sha256sum hashes six bytes, hello\n, not five.
echo 'hello' | sha256sum
# 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 (hello + \n)
echo -n 'hello' | sha256sum
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 (hello)
printf '%s' 'hello' | sha256sum # portable: no newline, no escape handling
echo -n is not portable. Some shells print -n literally, and escape handling differs between sh, bash and zsh. Prefer printf '%s' in scripts.
Files have the same problem in disguise. Most editors add a newline at the end of the file when saving, so a file containing hello usually hashes like hello\n. Windows line endings add a carriage return before every newline, so the same text saved on two machines can hash differently. Git's core.autocrlf setting can rewrite line endings on checkout, which explains a checksum that passes on a Linux CI runner and fails on a Windows laptop.
In code: Python, Node.js, the browser and PHP
Every language makes you choose an encoding when you hash a string. Use UTF-8 unless a protocol says otherwise, and hash files as raw bytes, not as decoded text.
import hashlib
hashlib.sha256('hello'.encode('utf-8')).hexdigest()
# '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
# Large files: stream them instead of reading everything into memory
h = hashlib.sha256()
with open('file.iso', 'rb') as f:
for chunk in iter(lambda: f.read(1 << 20), b''):
h.update(chunk)
print(h.hexdigest())
# Python 3.11+
with open('file.iso', 'rb') as f:
print(hashlib.file_digest(f, 'sha256').hexdigest())
// Node.js
const { createHash } = require('node:crypto');
createHash('sha256').update('hello', 'utf8').digest('hex');
// Browser (Web Crypto) — async, and only in secure contexts (HTTPS or localhost)
async function sha256Hex(text) {
const bytes = new TextEncoder().encode(text); // always UTF-8
const digest = await crypto.subtle.digest('SHA-256', bytes);
return [...new Uint8Array(digest)]
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
<?php
echo hash('sha256', 'hello'); // 64 hex characters
echo hash_file('sha256', 'file.iso');
echo base64_encode(hash('sha256', 'hello', true)); // raw bytes -> Base64
crypto.subtle is undefined on a page served over plain HTTP from a real hostname. If a hashing snippet works on localhost and throws in staging, check the protocol before you look for a bug.
Hex, Base64 and raw bytes are the same hash
The 32-byte digest can be written in several ways. Hex is the usual form, but HTTP headers such as Digest and Subresource Integrity attributes use Base64, and some APIs return raw bytes.
hex : 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 (64 chars)
base64 : LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ= (44 chars)
<script src="app.js"
integrity="sha256-LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="></script>
A frequent mistake is Base64-encoding the hex string rather than the raw digest. That produces an 88-character value that no verifier will accept. Encode the bytes: digest() in Node, hash(..., true) in PHP, .digest() rather than .hexdigest() in Python.
When SHA-256 is the wrong tool
Speed is a feature for checksums and a liability for passwords. If a table of SHA-256 password hashes leaks, every guess costs the attacker one fast hash, and a salt only stops them from using precomputed tables. Password storage wants a function that is slow on purpose, Argon2id, scrypt or bcrypt, as the OWASP Password Storage Cheat Sheet recommends.
A plain hash does not authenticate anything either. Anyone can recompute SHA-256(message) after changing the message. When both sides share a secret, for example to sign webhooks, use HMAC-SHA256. Also avoid SHA-256(secret + message): SHA-2 is vulnerable to length-extension attacks, which HMAC was designed to prevent.
import hmac, hashlib
sig = hmac.new(b'shared-secret', body_bytes, hashlib.sha256).hexdigest()
ok = hmac.compare_digest(sig, received_signature) # constant-time comparison
Finally, SHA-256 proves integrity, not origin. A checksum published on the same server as the download detects a corrupted transfer but not a compromised server. For that you need a signature, such as a GPG-signed SHA256SUMS file.
A checklist when two hashes disagree
Work through the byte-level differences in this order. It resolves nearly every mismatch: a trailing newline (echo versus printf), CRLF versus LF line endings, a UTF-8 BOM at the start of the file, a different text encoding (UTF-16 in PowerShell 5 redirection, Latin-1 in older systems), leading or trailing whitespace from copy-paste, upper- versus lower-case hex, and hex versus Base64 output.
Hash abc in both tools first. If both give ba7816bf…15ad, the tools agree and the difference is in the input bytes. Then hex-dump the input with xxd file | head or Format-Hex to see exactly what is being hashed.
Sources and further reading
NIST FIPS 180-4, Secure Hash Standard, defines SHA-256 and the other SHA-2 functions. RFC 2104 defines HMAC. The GNU coreutils manual documents sha256sum and its -c check mode. MDN documents SubtleCrypto.digest() and its secure-context requirement. The OWASP Password Storage Cheat Sheet covers why fast hashes are unsuitable for passwords.
Frequently asked questions
How do I generate a SHA-256 hash of a string?
On Linux or macOS run `printf '%s' 'your text' | sha256sum` (or `shasum -a 256`). In Python use `hashlib.sha256(text.encode()).hexdigest()`, and in Node.js `createHash('sha256').update(text).digest('hex')`. Avoid `echo` without `-n`, because the added newline changes the hash.
Why do I get a different SHA-256 hash for the same text?
The tools are hashing different bytes. The usual causes are a trailing newline, Windows CRLF line endings, a UTF-8 byte-order mark, or a different text encoding. Hash `abc` in both tools to confirm they agree, then hex-dump the input.
How long is a SHA-256 hash?
The digest is 256 bits, which is 32 bytes. Written in hex it is 64 characters; in Base64 it is 44 characters including one `=` of padding.
Can a SHA-256 hash be decrypted or reversed?
No. SHA-256 is a one-way function, not encryption, so there is no key and nothing to decrypt. Short or common inputs can be guessed by brute force or looked up in precomputed tables, which is why passwords need a slow, salted algorithm instead.
How do I check a file's SHA-256 checksum on Windows?
In PowerShell run `Get-FileHash .\file.iso`; SHA-256 is the default. In cmd use `certutil -hashfile file.iso SHA256`. Both print upper-case hex, so compare case-insensitively with the published value.
Is SHA-256 good enough for storing passwords?
No. It is fast by design, so leaked hashes can be brute-forced quickly even with a salt. Use Argon2id, scrypt or bcrypt, which are deliberately slow and memory- or cost-hardened.