SHA (Secure Hash Algorithm)

Overview

SHA (Secure Hash Algorithm) is a family of cryptographic hash functions published by NIST. SHA-0, SHA-1 and the SHA-2 family were designed by the National Security Agency (NSA). SHA-3 was not: it is the Keccak algorithm, designed by Bertoni, Daemen, Peeters and Van Assche, selected through an open public competition in 2012 and standardized as FIPS 202 in 2015. Hash functions are one-way functions that take input data of any size and produce a fixed-size hash value (digest).

SHA algorithms are used for data integrity verification, digital signatures, password hashing, and blockchain technology. They ensure that data hasn't been tampered with by producing a unique fingerprint for any given input.

SHA Variants

Variant Output Size Block Size Status
SHA-1 160 bits 512 bits Deprecated
SHA-256 256 bits 512 bits Secure
SHA-224 224 bits 512 bits Secure
SHA-384 384 bits 1024 bits Secure
SHA-512 512 bits 1024 bits Secure
SHA-512/256 256 bits 1024 bits Secure (resists length extension)
SHA3-256 256 bits 1088 bits (rate) Secure (sponge, not Merkle-Damgård)
SHA3-512 512 bits 576 bits (rate) Secure (sponge, not Merkle-Damgård)
SHAKE128 / SHAKE256 Extendable (any length) 1344 / 1088 bits (rate) Secure (extendable-output function)

SHA-1 and SHA-2 are built on the Merkle-Damgård construction. SHA-3 uses a sponge construction instead — a genuinely different design, chosen deliberately so that a future break of SHA-2 would not also break its replacement. SHA-3 is not "more secure" than SHA-2 in any measurable sense today; it is insurance.

How It Works

  1. Padding: Add padding to make input length a multiple of block size
  2. Message Schedule: Break message into blocks
  3. Compression Function: Process each block through compression rounds
  4. Final Hash: Combine all block hashes into final digest

Implementation


import hashlib

def sha256_hash(data):
    """Compute SHA-256 hash"""
    return hashlib.sha256(data.encode()).hexdigest()

def sha512_hash(data):
    """Compute SHA-512 hash"""
    return hashlib.sha512(data.encode()).hexdigest()

# Example usage
message = "Hello, SHA!"
hash_256 = sha256_hash(message)
hash_512 = sha512_hash(message)

print(f"SHA-256: {hash_256}")
print(f"SHA-512: {hash_512}")

# Verification
def verify_integrity(original, received, hash_value):
    """Verify data integrity"""
    computed_hash = sha256_hash(received)
    return computed_hash == hash_value
                

Hash Function Properties

  • Deterministic: Same input always produces same output
  • Fast Computation: Hash can be computed quickly
  • Pre-image Resistance: Hard to find input given hash
  • Collision Resistance: Hard to find two inputs with same hash
  • Avalanche Effect: Small input change causes large hash change

Applications

  • Data Integrity: Verify files haven't been modified
  • Digital Signatures: Sign documents and messages
  • Key Derivation: As the underlying hash inside HMAC, HKDF and PBKDF2
  • Blockchain: Bitcoin and other cryptocurrencies
  • Version Control: Git historically used SHA-1 for object hashes. Since 2017 it has used SHA-1DC (SHA-1 with collision detection, which rejects SHAttered-style colliding inputs), and Git has supported a full SHA-256 object format since version 2.29 (2020)

Security Considerations

  • SHA-1: Broken and retired. A real collision was produced in 2017 (SHAttered), and Leurent & Peyrin demonstrated a practical chosen-prefix collision in 2020 — the class of attack that lets an attacker forge certificates. NIST has set 31 December 2030 as the date SHA-1 is fully phased out of federal use.
  • SHA-256: Secure, and the right default for most applications.
  • SHA-512: Equally secure. Note it is often faster than SHA-256 on 64-bit hardware, so "use SHA-512 when you need more security" is the wrong reason to pick it — pick it for speed on 64-bit platforms, or pick SHA-512/256 when you also want length-extension resistance.
  • Length extension: SHA-1 and SHA-2 are Merkle-Damgård constructions, so given H(secret || m) and the length of secret, an attacker can compute H(secret || m || padding || m') without knowing the secret. Never authenticate a message by hashing a secret prefix. Use HMAC, or a hash that is not vulnerable (SHA-3, SHA-512/256, BLAKE2/BLAKE3).

Do not use SHA for password storage

This is the most common way general-purpose hash functions get misused. SHA-256 is designed to be fast — that is the entire point of a hash function — and commodity GPUs evaluate it billions of times per second. Adding a salt defeats precomputed rainbow tables, but it does nothing at all about throughput: an attacker who steals your database simply brute-forces each salted hash individually, very quickly.

Password storage needs a deliberately slow, memory-hard key derivation function, with tunable cost parameters you increase as hardware gets faster:

  • Argon2id (RFC 9106) — the current first choice, winner of the Password Hashing Competition
  • scrypt — memory-hard, widely available
  • bcrypt — older but still acceptable; note its 72-byte input limit
  • PBKDF2-HMAC-SHA256 — the weakest of the four (not memory-hard), but the option to reach for when FIPS compliance requires it. Use a high iteration count.

# WRONG - fast hash, brute-forces at billions of guesses/sec even with a salt
import hashlib
stored = hashlib.sha256(salt + password.encode()).hexdigest()

# RIGHT - slow, memory-hard, tunable cost
from argon2 import PasswordHasher          # pip install argon2-cffi
ph = PasswordHasher()
stored = ph.hash(password)                 # salt is generated and embedded automatically
ph.verify(stored, password_attempt)        # raises on mismatch

# Standard-library fallback if you cannot add a dependency
stored = hashlib.scrypt(password.encode(), salt=salt, n=2**15, r=8, p=1)
                

Related Algorithms

Explore other encryption algorithms: