Skip to content
pwnsy
cryptographybeginner#hashing#sha-256#cryptography#collision-resistance#integrity

How Hashing Works: Cryptographic Hash Functions Explained

A plain guide to cryptographic hash functions: one-way and collision resistance, SHA-2 and SHA-3, and why hashing is not encryption.

Hashing is one of the most useful and most misunderstood tools in cryptography. It sits under file downloads, digital signatures, password storage, blockchains, and version control. It is also constantly confused with encryption, which leads to real security mistakes. The distinction is simple once you see it: encryption is reversible if you hold the key, and hashing is reversible for no one.

A cryptographic hash function takes an input of any size, a password, a file, a whole disk image, and produces a fixed-size output called a hash or digest. The same input always yields the same digest, and even a tiny change to the input produces a completely different one. Think of it as a fingerprint for data: compact, unique in practice, and derived deterministically from the whole thing.

The defining property: it only goes one way

The feature that separates a hash from encryption is that it is a one-way function. Given the input, computing the hash is easy. Given the hash, recovering the input is infeasible. There is no key that reverses it, because reversal is not part of the design. When you hash "hunter2" to a 256-bit value, that value is a dead end. You cannot decrypt it back, because nothing was encrypted.

This is why hashing suits situations where you want to check something without storing it. A server can store the hash of your password and verify a login by hashing what you type and comparing, all without ever keeping your actual password. A download page can publish a file's hash so you can confirm your copy is bit-for-bit identical, without the page keeping your copy.

The security properties that make a hash cryptographic

Plenty of functions produce fixed-size outputs. A cryptographic hash has to withstand adversaries who actively try to break it. Three properties define that resistance.

PropertyPlain meaningWhy it matters
Preimage resistanceGiven a hash, you cannot find any input that produces itProtects stored hashes from being reversed
Second-preimage resistanceGiven one input, you cannot find a different input with the same hashStops targeted forgery of a specific file
Collision resistanceYou cannot find any two different inputs that share a hashUnderpins digital signatures and integrity

A fourth property, the avalanche effect, ties them together in practice: changing a single bit of input flips roughly half the output bits. Similar inputs produce completely unrelated digests, which is what makes the function unpredictable and hard to attack.

Collision resistance is the property that ages the fastest. When researchers find a practical way to construct two different inputs with the same hash, the function is broken for security use. This is exactly what happened to MD5 and SHA-1.

Retired hashes: MD5 and SHA-1

MD5 and SHA-1 are broken for security. Practical collision attacks exist for both, meaning an attacker can craft two different files with the same digest. Do not use them to verify authenticity, sign data, or protect anything that matters. Move to SHA-256 or SHA-3. You may still see MD5 as a non-security checksum for accidental corruption, but never trust it against an adversary.

The families you should use

Two standardised families cover almost all modern needs, both published by NIST.

SHA-2 is the workhorse. It is a family that includes SHA-256 and SHA-512 (the number is the digest length in bits). SHA-256 is everywhere: TLS certificates, software signing, Bitcoin, Git object identifiers in newer setups. It is fast, well-studied, and has no practical weaknesses. For most integrity and fingerprinting work, SHA-256 is the right default.

SHA-3 is the newer standard, finalised in 2015. It is built on a completely different internal design (a sponge construction called Keccak) rather than the structure SHA-2 uses. SHA-3 is not "better" than SHA-2 for everyday purposes; both are secure. SHA-3 exists partly as insurance, so that if a structural weakness were ever found in the SHA-2 family, a mature and unrelated alternative would already be standardised and deployed.

FunctionOutput sizeStatus
MD5128-bitBroken, do not use for security
SHA-1160-bitBroken, do not use for security
SHA-256 (SHA-2)256-bitRecommended default
SHA-512 (SHA-2)512-bitRecommended, larger margin
SHA-3224 to 512-bitRecommended, distinct design

Inside the function: how the bytes actually get mixed

You do not need to implement a hash function to use one safely, but knowing the shape of the machinery explains why the security properties hold and why some designs aged badly. Two construction styles dominate the standardised functions.

The Merkle-Damgard construction underlies MD5, SHA-1, and the SHA-2 family. It works by breaking the input into fixed-size blocks and feeding them one at a time through a compression function, carrying a running internal state from block to block. The state after the last block, sometimes with a finalisation step, becomes the digest. The input is padded first, and the padding encodes the original length, which closes off a class of trivial forgeries. This design is efficient and easy to reason about, and it is why SHA-256 processes a one-byte file and a one-gigabyte file with the same core loop, just more iterations of it.

The sponge construction underlies SHA-3 and its Keccak core. Instead of a compression function with a carried state, a sponge has a single large internal state and two phases. In the absorbing phase it mixes input blocks into part of the state and runs a permutation between blocks. In the squeezing phase it reads output blocks out of the state, running the permutation again between each. The sponge is flexible enough to produce variable-length output, which is why SHA-3 ships extendable-output functions (SHAKE128 and SHAKE256) alongside the fixed-length digests.

The Merkle-Damgard design carries one structural quirk worth naming: the length-extension property. Because the digest is the full internal state after the last block, someone who knows the hash of a secret concatenated with a message can, without knowing the secret, compute the hash of that message plus an extension. This never lets an attacker recover the secret, and it does not break preimage or collision resistance. It does break naive authentication schemes that hash a secret and a message together. That specific weakness is one reason HMAC exists and why you should not roll your own keyed hash by concatenation. SHA-3 sponges are not vulnerable to length extension, and neither are the truncated SHA-2 variants such as SHA-512/256.

A worked walkthrough: verifying a download

Concrete steps make the abstraction stick. Suppose a project publishes a release archive and, next to it, a file containing the SHA-256 digest of that archive. Here is what verification actually does, and where trust comes from at each step.

  1. You download the archive over the network. Any corruption in transit, a truncated file, a flipped bit, a tampered payload, changes the bytes you receive.
  2. You compute the SHA-256 of the bytes on your disk. On the command line this is one call to a standard tool, and it reads the whole file to produce the digest.
  3. You compare your computed digest against the published one. Because of the avalanche effect, a single altered byte anywhere in the archive produces a completely different digest, so a match to all 64 hex characters is strong evidence the file is intact.

The subtle part is where the published digest comes from. If you download both the archive and its digest from the same server over the same connection, an attacker who can tamper with the archive can usually tamper with the digest too, and you have verified nothing against them. The digest defends against accidental corruption on its own, and against a malicious tamperer only when its integrity is anchored somewhere the attacker does not control: a digital signature over the digest, a checksum served from a separate trusted channel, or a value you already hold. This is the difference between a checksum and an authenticated checksum, and it is why signatures and hashing are usually deployed together rather than one instead of the other.

What a hash match actually proves

Matching a published digest confirms your copy equals the copy the publisher hashed. It says nothing about whether that original copy was trustworthy, and nothing about a tamperer who could rewrite the published digest too. Anchor the digest in a signature or a separate trusted source before you treat a match as proof against an adversary.

The birthday problem and why output size matters

Collision resistance is where output length earns its keep, and the reason is a counterintuitive result called the birthday problem. To find a collision by brute force you do not need to try all possible outputs. Because a collision is any two inputs that match, the effort scales with the square root of the output space, not the space itself. A hash with an n-bit output offers roughly n bits of preimage resistance but only about n/2 bits of collision resistance.

For SHA-256 that means preimage attacks face a work factor around 2 to the 256, which is far beyond reach, while a birthday collision search faces around 2 to the 128, still comfortably infeasible. For a 128-bit hash like MD5, the birthday bound sits near 2 to the 64, which modern hardware can chew through, and that is before you account for the cryptographic weaknesses that let attackers do far better than brute force. This is the arithmetic behind the guidance to prefer at least 256-bit digests for anything where collision resistance matters, such as signatures and content addressing.

Output sizePreimage work (approx)Collision work (approx)
128-bit (MD5 length)2^1282^64
160-bit (SHA-1 length)2^1602^80
256-bit (SHA-256)2^2562^128
512-bit (SHA-512)2^5122^256

These are the theoretical brute-force ceilings for an ideal function. The reason MD5 and SHA-1 are retired is that cryptanalysis found shortcuts well below even the birthday bound, so their real collision cost is far lower than the table suggests.

Hashing is not encryption

This deserves its own section because the confusion causes real bugs. Encryption is reversible by design: the whole point is that the right key turns ciphertext back into plaintext. Hashing is irreversible by design: there is no key and no way back. If someone says they will "encrypt" passwords so they can email a user their forgotten password, they are describing reversible storage, which is the wrong model. Passwords should be hashed, so that not even the service can recover them.

Use encryption when you need to get the data back. Use hashing when you only need to verify or fingerprint it. See Symmetric vs Asymmetric Encryption for the encryption side of the picture.

Where hashing shows up

  • Integrity verification. Publish a file's SHA-256 alongside the download. Recompute it after downloading and compare. A match means the bytes are intact.
  • Digital signatures. You do not sign a whole document, you sign its hash. The hash stands in for the document, which is why collision resistance is essential to signatures. See How Digital Signatures Work.
  • Password storage. Servers store a transformed version of your password so a database leak does not hand over the plaintext, though this needs a special kind of hash, discussed below.
  • Message authentication. Combining a hash with a secret key produces an HMAC, which proves both integrity and authenticity. See What Is HMAC.
  • Deduplication and identifiers. Content-addressed systems and version control use hashes as unique names for data.

The one place a plain hash is wrong: passwords

A fast hash like SHA-256 is excellent for file integrity and terrible for passwords, and the reason is the same for both: it is fast. An attacker who steals a database of SHA-256 password hashes can try billions of guesses per second on commodity hardware. Password hashing needs to be deliberately slow and salted.

Rule of thumb for passwords

Never store passwords with a bare SHA-256 or SHA-3. Use a dedicated password hashing function (Argon2id is the current recommendation) with a unique salt per user. Fast general-purpose hashes are for integrity and signatures, not credentials.

How to use hashing safely

  1. Default to SHA-256 for integrity and fingerprinting. Reach for SHA-512 or SHA-3 when you want extra margin or design diversity.
  2. Never use MD5 or SHA-1 for anything security-relevant. Treat their presence in a codebase as a finding to fix.
  3. Hash passwords with Argon2, scrypt, or bcrypt, always salted, never with a plain fast hash.
  4. Compare hashes with a constant-time comparison when verifying secrets, so timing does not leak how much of a value matched.
  5. Remember hashing does not hide data. If the input space is small or guessable, an attacker can just hash the candidates and compare. Hashing is a fingerprint, not a hiding place.

Weak hash usage, broken algorithms, unsalted credentials, and non-constant-time checks all surface as tracked vulnerabilities. You can see how hashing failures turn into real CVEs on our Exploit Intelligence dashboard, which shows how often a retired algorithm lingers long past its expiry date.

Weak hash usage does not always announce itself. Here are concrete signals that a codebase or system is using hashing in a way that will not hold up.

  • String literals naming a broken algorithm. Searching for md5, sha1, or MessageDigest.getInstance("MD5") and finding them in code paths that verify authenticity, sign data, or store credentials is an immediate finding.
  • A password column that stores fixed-length hex with no per-row salt. Sixty-four hex characters that are identical for two users who chose the same password reveal a plain, unsalted SHA-256 and a rainbow-table exposure.
  • Verification using an ordinary equality check on a secret. A byte-by-byte comparison that returns early on the first mismatch can leak, through timing, how many leading bytes matched, which chips away at a MAC or token check.
  • Home-grown keyed hashing by concatenation. Code that computes the hash of a secret joined to a message, rather than calling a real HMAC, is both reinventing a primitive and, on Merkle-Damgard functions, exposed to length extension.
  • A single hash pass used as a slow function. Password code that calls SHA-256 once, or even a few thousand times in a hand-rolled loop, instead of a memory-hard function tuned with real parameters, is far too cheap to resist offline cracking.

Weak hash usage, broken algorithms, unsalted credentials, and non-constant-time checks all surface as tracked vulnerabilities. You can see how hashing failures turn into real CVEs on our Exploit Intelligence dashboard, which shows how often a retired algorithm lingers long past its expiry date.

Common misconceptions

A handful of wrong mental models cause most hashing bugs. Naming them directly is the fastest way to avoid them.

"A hash hides the data." A hash reveals nothing about a large, unpredictable input, but it hides nothing when the input space is small. If a field can only hold one of a few thousand values, an attacker hashes every candidate and matches yours in seconds. Hashing a short PIN, a yes or no flag, or an email address from a known list conceals almost nothing. Treat a bare hash as a fingerprint, and reach for salting, keying, or encryption when the input is guessable.

"Adding a salt makes a fast hash safe for passwords." A salt defeats precomputed rainbow tables and stops identical passwords from sharing a digest. It does nothing to slow a targeted guess against one account, because the attacker simply hashes each guess with that user's known salt. Speed is the separate problem, and only a deliberately slow, memory-hard function solves it.

"More hashing rounds always help." Iterating a fast hash many times raises the cost linearly, which helps a little, but memory-hard functions such as Argon2 and scrypt raise the attacker's cost far more per unit of defender effort by forcing large memory use that specialised cracking hardware cannot cheaply parallelise. Reaching for a purpose-built function beats stacking rounds of the wrong one.

"A checksum and a cryptographic hash are the same thing." A CRC32 or a simple checksum catches accidental corruption and is fast, but it is trivial to forge on purpose, so it protects against noisy channels and nothing else. A cryptographic hash resists an adversary who is actively trying to produce a matching output. Using a checksum where you needed collision resistance is a silent failure until someone exploits it.

How hashing evolved

The lineage of standard hashes is a slow retreat from broken designs toward larger margins and more design diversity. MD5 arrived in the early 1990s and was ubiquitous for a decade before practical collision techniques rendered it unusable for security. SHA-1 followed as a stronger replacement and held longer, but theoretical collision attacks matured into demonstrated, practical ones, and the ecosystem spent years migrating certificates, signatures, and version-control identifiers away from it.

SHA-2 was standardised as that migration target and remains the workhorse. Its larger digests and refined compression function have resisted the attacks that felled its predecessors. NIST ran a public competition, much like the one that produced AES, to pick a structurally different backup, and the Keccak sponge won and became SHA-3. The motivation was insurance: if a deep flaw were ever found in the Merkle-Damgard lineage, a mature and unrelated alternative would already be deployed. The password-hashing world followed a parallel arc, moving from plain hashes to bcrypt, then to memory-hard scrypt, and to Argon2 as the winner of a dedicated password-hashing competition. The throughline is that hash security is a moving target, and the safe default is always the current standard with generous output size.

Frequently asked questions

Can two different files ever have the same SHA-256 hash? In theory yes, because there are infinitely many possible inputs and a finite number of outputs, so collisions must exist. In practice, finding a pair with a matching SHA-256 digest is computationally infeasible with any known method, which is exactly what collision resistance guarantees. For MD5 and SHA-1, by contrast, anyone can now construct such pairs, which is why they are retired.

Why can I not reverse a hash even when I know the algorithm? Knowing the algorithm is the whole point: hashes are designed to be public and still one-way. The function throws away structure as it mixes bits, so many inputs map to any given output and there is no arithmetic that walks the process backward. Cracking a hash means guessing inputs and hashing them forward until one matches, which only works when the input is small or predictable.

Is a longer hash always more secure? A longer digest raises the collision and preimage work factors, so within a given secure family, larger is a safer margin. It is not a rescue for a broken algorithm: a 512-bit output from a flawed design is still broken. Choose a sound function first, then pick an output size generous enough for your threat model.

What is the difference between a hash and an HMAC? A plain hash proves integrity: it detects whether data changed. An HMAC combines a hash with a secret key and proves both integrity and authenticity: it detects change and confirms the message came from someone holding the key. Use a plain hash for public integrity checks and an HMAC when you need to know a message was not forged. See the linked HMAC guide for detail.

Should I use SHA-3 instead of SHA-2 for new systems? Either is a sound choice, and both are secure today. SHA-2 is faster in most software and more widely supported, so it is a fine default. SHA-3 gives you a different internal design and immunity to length extension, which some architects prefer for new work. The wrong move is neither of these: it is continuing to reach for MD5 or SHA-1.

How do salts and hashing work together for passwords? When a user sets a password, the server generates a unique random salt, combines it with the password, runs the combination through a slow password-hashing function, and stores the salt alongside the resulting digest. At login it repeats the process with the stored salt and compares. The salt is not secret, its job is to make every stored hash unique so precomputed tables fail and identical passwords never collide.

Hashing gives you a compact, deterministic, irreversible fingerprint of data. Pick a modern function, respect the one place plain hashes do not belong, and it becomes a quiet, reliable foundation under signatures, integrity checks, and identity across your systems.

Sources & further reading

Sharetwitterlinkedin

Related guides