Security·6 min read·By the StackUtils Team

SHA-256 and Hashing Explained: What Makes Data Tamper-Proof

Hashing is not encryption — and confusing the two causes serious security bugs. Learn how SHA-256, MD5, and bcrypt work, when to use each, and why you should never store plain-text passwords.

Hashing vs Encryption: The Critical Difference

This confusion causes real security vulnerabilities. Understanding the distinction is non-negotiable for any developer working with user data.

Hashing
  • One-way — cannot be reversed
  • Same input always produces same output
  • Fixed-length output regardless of input size
  • Designed to detect tampering, not protect secrets

Use for: Passwords, file integrity, digital signatures, MACs

Encryption
  • Two-way — can be decrypted with the right key
  • Requires a key for both operations
  • Output size related to input size
  • Designed to keep data secret, not verify integrity

Use for: Storing sensitive data you need to retrieve (e.g. API keys, PII)

How SHA-256 Works

SHA-256 is part of the SHA-2 family, designed by the NSA and published by NIST in 2001. It takes any input and produces a fixed 256-bit (32-byte) output — always exactly 64 hexadecimal characters.

SHA-256(“hello”) =

2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

SHA-256(“hello!”) =

ce06092fb948d9af675054ec2c3064ccfb4b41bc3d001cd4a61b4f5db31ce6f5

↑ One character change → completely different hash (avalanche effect)

The key properties that make SHA-256 useful are:

  • Deterministic — same input always gives the same hash
  • Avalanche effect — a single bit change cascades to a completely different output
  • Pre-image resistance — given a hash, you cannot find the original input
  • Collision resistance — it is computationally infeasible to find two different inputs with the same hash

Algorithm Comparison

AlgorithmOutputSpeedStatusUse case
MD5128 bitsVery fastBrokenFile checksums (non-security), legacy systems
SHA-1160 bitsFastBrokenGit commit IDs (collision-resistant enough for that), legacy TLS
SHA-256256 bitsFastSafeDigital signatures, HMAC, certificate fingerprints, data integrity
SHA-512512 bitsFast on 64-bitSafeWhen extra margin is needed; slightly larger output
bcryptvariable bitsIntentionally slowSafePassword storage — adjustable cost factor
Argon2idvariable bitsIntentionally slowSafePassword storage — winner of the Password Hashing Competition, preferred over bcrypt

Why You Must Not Use SHA-256 for Passwords

SHA-256 is too fast for passwords. A modern GPU can compute billions of SHA-256 hashes per second, making brute-force attacks trivial for any password under 12 characters.

Password hashing algorithms like bcrypt and Argon2id are intentionally slow and memory-intensive. The cost factor lets you tune how long each hash takes — typically 100–300ms on your server. That is imperceptible to users but makes offline brute-force attacks take years instead of minutes.

// Node.js — never do this for passwords:
const hash = crypto.createHash('sha256').update(password).digest('hex'); // ❌

// Use bcrypt instead:
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12); // cost factor 12 → ~250ms ✅
const valid = await bcrypt.compare(inputPassword, hash);

// Or Argon2id (preferred):
import argon2 from 'argon2';
const hash = await argon2.hash(password, { type: argon2.argon2id }); // ✅
const valid = await argon2.verify(hash, inputPassword);

HMAC: Hashing with a Secret Key

A plain hash has no secret — anyone can compute SHA-256(data) if they know the data. HMAC (Hash-based Message Authentication Code) combines the hash function with a secret key, producing a tag that proves both the data's integrity and that the signer held the key.

HMAC-SHA256 is used in JWT signatures (HS256), webhook signature verification (GitHub, Stripe, etc.), and API request signing (AWS Signature v4).

import crypto from 'crypto';

// Sign a webhook payload
const signature = crypto
  .createHmac('sha256', process.env.WEBHOOK_SECRET!)
  .update(rawBody)
  .digest('hex');

// Verify — use timingSafeEqual to prevent timing attacks
const expected = Buffer.from(signature);
const received = Buffer.from(req.headers['x-signature'] as string, 'hex');
const valid = crypto.timingSafeEqual(expected, received);

Generate and verify hashes instantly

Compute MD5, SHA-1, SHA-256, SHA-512, and more — directly in the browser with no data sent to any server.

Open Hash Generator →