DD
DevDash
securityjavascriptpythonbeginners

SHA-256 Hashing Explained: What It Is and How to Use It

SHA-256 Hashing Explained: What It Is and How to Use It

SHA-256 produces a fixed-length 256-bit (32-byte) hash from any input. The same input always produces the same hash. Different inputs produce completely different hashes. You cannot reverse a hash back to the original input. These properties make SHA-256 the workhorse of modern security.

Hashing Is Not Encryption

This is the most common misconception. They are fundamentally different:

HashingEncryption
Reversible?NoYes (with the key)
Output lengthFixed (256 bits for SHA-256)Proportional to input
Key required?NoYes
PurposeVerify integrityProtect confidentiality

Encryption is a two-way process: you encrypt data with a key and decrypt it with the same (or related) key. Hashing is a one-way process: you hash data and get a fingerprint. You cannot "unhash" it.

When someone says they "decrypt" a hash, they mean they looked it up in a precomputed table (rainbow table) or brute-forced it. The hash function itself is not reversible.

SHA-256 in JavaScript (SubtleCrypto)

Modern browsers and Node.js include SHA-256 via the Web Crypto API. No libraries needed.

Browser

async function sha256(message) {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

const hash = await sha256('hello world');
console.log(hash);
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

crypto.subtle.digest returns an ArrayBuffer. The conversion to hex string is the boilerplate part -- the actual hashing is a single function call.

Node.js

import { createHash } from 'crypto';

function sha256(message) {
  return createHash('sha256').update(message).digest('hex');
}

console.log(sha256('hello world'));
// "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Node's crypto module is synchronous and straightforward. For hashing files:

import { createHash } from 'crypto';
import { createReadStream } from 'fs';

function hashFile(filePath) {
  return new Promise((resolve, reject) => {
    const hash = createHash('sha256');
    const stream = createReadStream(filePath);
    stream.on('data', chunk => hash.update(chunk));
    stream.on('end', () => resolve(hash.digest('hex')));
    stream.on('error', reject);
  });
}

const fileHash = await hashFile('./package.json');

SHA-256 in Python

import hashlib

# Hash a string
message = 'hello world'
hash_hex = hashlib.sha256(message.encode('utf-8')).hexdigest()
print(hash_hex)
# "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Hash a file

import hashlib

def hash_file(filepath):
    sha256 = hashlib.sha256()
    with open(filepath, 'rb') as f:
        for chunk in iter(lambda: f.read(8192), b''):
            sha256.update(chunk)
    return sha256.hexdigest()

print(hash_file('document.pdf'))

Reading in chunks avoids loading the entire file into memory. This handles multi-gigabyte files without issue.

HMAC (Hash-Based Message Authentication)

When you need to verify both integrity and authenticity (e.g., webhook signatures):

import hashlib
import hmac

secret = b'my-webhook-secret'
message = b'{"event": "payment.completed"}'

signature = hmac.new(secret, message, hashlib.sha256).hexdigest()
print(signature)

# Verify (timing-safe comparison)
expected = '...'  # from the webhook header
is_valid = hmac.compare_digest(signature, expected)

Always use hmac.compare_digest() instead of == for signature verification. String comparison with == is vulnerable to timing attacks.

SHA-256 on the Command Line

macOS

echo -n "hello world" | shasum -a 256
# b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9  -

# Hash a file
shasum -a 256 document.pdf

Linux

echo -n "hello world" | sha256sum
# b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9  -

# Hash a file
sha256sum document.pdf

Windows (PowerShell)

$bytes = [System.Text.Encoding]::UTF8.GetBytes("hello world")
$hash = [System.Security.Cryptography.SHA256]::Create().ComputeHash($bytes)
[System.BitConverter]::ToString($hash) -replace '-',''

# Hash a file
Get-FileHash document.pdf -Algorithm SHA256

Important: The -n flag in echo -n prevents a trailing newline. Without it, you hash "hello world\n" which produces a completely different hash. This is the #1 reason people get different hashes across tools.

Use Cases

1. Password Storage

Never store passwords in plaintext. Hash them. But do not use raw SHA-256 for passwords -- use a purpose-built password hashing function:

# DON'T do this for passwords
hashlib.sha256(password.encode()).hexdigest()  # Too fast, vulnerable to brute force

# DO this instead
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())

SHA-256 is too fast for password hashing. An attacker can compute billions of SHA-256 hashes per second on a modern GPU. bcrypt, scrypt, and Argon2 are intentionally slow, making brute-force attacks impractical.

SHA-256 is still used in password hashing -- bcrypt and PBKDF2 use it internally -- but always through a key-stretching wrapper.

2. File Integrity Verification

Download a file, hash it, compare against the published hash:

# Download the file
curl -O https://example.com/release-v2.0.tar.gz

# Verify integrity
echo "a1b2c3d4...expected-hash...  release-v2.0.tar.gz" | shasum -a 256 -c
# release-v2.0.tar.gz: OK

This is how you verify that a downloaded file was not tampered with in transit.

3. Content-Addressable Storage

Git uses SHA hashes to identify every commit, tree, and blob:

echo -n "hello" | git hash-object --stdin
# ce013625030ba8dba906f756967f9e9ca394464a

The hash IS the address. If the content changes, the address changes. This makes it impossible to silently alter history.

4. API Webhook Verification

Services like Stripe, GitHub, and Shopify sign webhook payloads with HMAC-SHA256:

import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhookSignature(payload, signature, secret) {
  const expected = createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

5. Blockchain

Every Bitcoin block header is hashed twice with SHA-256. The "mining" process is finding an input whose double-SHA-256 hash starts with a certain number of zero bits.

Quick Reference

For quick hash generation during development -- checking a webhook signature, verifying a file download, or comparing two strings -- devdash.io has a hash generator that computes SHA-256, SHA-512, MD5, and other algorithms in the browser. Useful when you do not want to open a terminal.

SHA-256 vs Other Hash Functions

AlgorithmOutput SizeSpeedStatus
MD5128-bitFastBroken (collisions found). Do not use for security.
SHA-1160-bitFastBroken (SHAttered attack). Deprecated.
SHA-256256-bitFastSecure. Industry standard.
SHA-512512-bitFastSecure. Faster than SHA-256 on 64-bit CPUs.
SHA-3VariableModerateSecure. Different internal design than SHA-2.
BLAKE3256-bitVery fastSecure. Modern alternative, parallelizable.

For almost all applications in 2026, SHA-256 is the right choice. It is fast, universally supported, and has no known practical attacks. Use SHA-512 if you need a larger hash or are on 64-bit hardware where it is actually faster. Use BLAKE3 if you need maximum throughput for large data.

Related Tools

Want API access + no ads? Pro coming soon.