1Introduction to Hashing Algorithms
▶
Hashing algorithms sit at the heart of modern computing security, data integrity, and authentication systems. At their most fundamental level, a hashing algorithm is a mathematical function that takes some input — whether a single letter, a lengthy document, or an entire software package — and transforms it into a compact, fixed-length string of characters. That output string goes by several names: a hash value, a digest, or a fingerprint. Understanding what makes this transformation special, and why it is so widely relied upon, requires exploring the properties that define a well-designed hash function and the ways those properties translate into real-world security guarantees.
To appreciate what a hash function does, consider a simple analogy. Imagine you have a machine into which you can feed any document — a single Post-it note or the complete works of Shakespeare — and the machine always prints out a receipt exactly 64 characters long. The receipt looks like random gibberish, but it is completely determined by what you fed in. Feed in exactly the same document again, and you get exactly the same receipt. Feed in a document with even one comma changed, and the receipt looks entirely different. That machine is, conceptually, a hash function.
A more concrete illustration uses the SHA-256 algorithm. Hashing the word hello produces:
Input: hello
Output: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Now change just one character — lowercase the H in a capitalized version:
Input: Hello
Output: 185f8db32921bd46d35f6f7157b16a674f0cef9a16245f57b01b5cf4bc40c7b4
The two outputs share no obvious relationship even though the inputs differ by only one bit of capitalization. This dramatic divergence is not an accident; it is a deliberate and essential design goal.
What Is a Hash Function?
A hash function is a deterministic algorithm that maps data of arbitrary size to a fixed-size value. Deterministic is the key word here: given the same input, a hash function must always return the same output, without exception. This predictability is what makes hash functions useful. If the same password hashed differently each time you typed it, a login system could never verify your credentials.
The output — the digest — is described as a fingerprint of the original input because, like a human fingerprint, it is intended to be unique to that specific input. Just as no two people are supposed to share the same fingerprint pattern, no two different inputs should ideally produce the same hash. In practice, collisions (two different inputs producing the same output) are mathematically inevitable because the input space is infinite while the output space is finite, but a well-designed algorithm makes finding such collisions computationally infeasible.
Fixed-Length Output and the Transformation Process
One of the most immediately useful properties of a hash function is its fixed-length output. Regardless of whether you hash a two-character string or a two-gigabyte video file, the output digest is always the same number of bits. The specific length depends on the algorithm chosen:
| Algorithm | Output Length (bits) | Output Length (hex characters) |
|---|---|---|
| MD5 | 128 | 32 |
| SHA-1 | 160 | 40 |
| SHA-256 | 256 | 64 |
| SHA-512 | 512 | 128 |
| bcrypt | 184 (encoded output) | 60 (standard encoded string) |
This fixed-length property makes hashes extremely practical for storage and comparison. A database storing user passwords does not need to allocate variable amounts of space depending on how long each user's password is; it simply stores a 64-character string for every account when using SHA-256. Comparing two hashes to check equality is always an operation on strings of known, identical length, which is both computationally efficient and straightforward to implement.
The transformation process itself is one-way. The algorithm applies a series of mathematical operations — mixing, permuting, compressing — to the input in ways that are easy to perform in the forward direction but practically impossible to reverse. This is sometimes described as a trapdoor function: easy to fall through, impossible to climb back out of. There is no inverse function, no decryption key, and no mathematical shortcut that allows you to recover the original input from the digest alone.
Core Properties of Cryptographic Hash Functions
Not every hash function qualifies as a cryptographic hash function. Non-cryptographic hash functions (used in hash tables and checksums) prioritize speed over security. Cryptographic hash functions must satisfy three rigorous properties:
- Pre-image resistance: Given a hash value h, it must be computationally infeasible to find any input m such that hash(m) = h. This means an attacker who sees a stored hash digest cannot work backwards to find the original input. For a 256-bit hash, brute-forcing all possible inputs would require on the order of 2256 attempts — a number so astronomically large that no computer, or collection of computers, could complete such a search within any practical timeframe.
- Collision resistance: It must be computationally infeasible to find two different inputs m1 and m2 such that hash(m1) = hash(m2). Collisions are mathematically guaranteed to exist (pigeonhole principle), but finding them intentionally must require so much computing effort that it is practically impossible. When researchers can find collisions efficiently — as happened with MD5 and SHA-1 — the algorithm is considered broken for security purposes.
- The avalanche effect: A change of even a single bit in the input should cause approximately half of the output bits to flip, in an unpredictable way. This prevents an attacker from making incremental guesses by slightly modifying inputs and observing small, predictable changes in the output. The avalanche effect is what caused the dramatic difference between the hello and Hello examples shown earlier.
A fourth property, second pre-image resistance, is closely related: given a specific input m1, it must be infeasible to find a different input m2 that produces the same hash. This is subtly different from general collision resistance because the attacker is constrained to match a specific, known input rather than finding any pair of colliding inputs.
Hashing vs. Encryption: A Key Distinction
A common source of confusion — even among developers — is treating hashing and encryption as interchangeable. They are fundamentally different tools serving different purposes, and conflating them leads to serious security mistakes.
Encryption is a two-way process. Data is transformed using an algorithm and a key into an unreadable ciphertext, and anyone with the correct key can reverse the process to recover the original plaintext. Encryption is designed for confidentiality: you want to protect data in transit or at rest, but you (or an authorized party) will need to read it again later. Examples include encrypting a file on disk or establishing a secure HTTPS connection.
Hashing is a one-way process. Once data is hashed, the original cannot be recovered from the digest — not even by the system that created the hash. This is precisely what you want for password storage. When a user creates a password, the system hashes it and stores the digest. When the user logs in later, the system hashes the typed password and compares the two digests. The original password is never stored anywhere. Even if an attacker steals the database, they have only a list of hashes — not passwords.
| Property | Hashing | Encryption |
|---|---|---|
| Reversible? | No | Yes (with correct key) |
| Uses a key? | No (standard hashing) | Yes |
| Output length | Fixed | Variable (typically similar to input size) |
| Primary use | Integrity, authentication | Confidentiality |
| Can recover original data? | No | Yes |
A critical security mistake is storing passwords using reversible encryption instead of hashing. If the encryption key is ever compromised — which it can be, because it must be stored somewhere accessible to the application — every password in the database becomes recoverable. With proper hashing, there is no key to steal and no reversal path to follow.
Common Hashing Algorithms and Their Use Cases
MD5 (Message Digest 5) was designed by Ronald Rivest in 1991 and produces a 128-bit digest. For much of the 1990s and early 2000s it was the default choice for checksums and password storage. However, MD5 is now considered cryptographically broken. Researchers demonstrated practical collision attacks — meaning two different inputs can be engineered to produce the same MD5 hash — and MD5 is vulnerable to fast brute-force attacks due to its speed and short output. It remains in use for non-security purposes like verifying accidental file corruption, but it must never be used for passwords or digital signatures.
SHA-1 (Secure Hash Algorithm 1), producing a 160-bit digest, was the successor to MD5 and was widely adopted in SSL certificates, version control systems, and digital signatures. SHA-1 was deprecated for most security uses after 2011 and officially broken by the SHAttered attack in 2017, which produced a real-world SHA-1 collision. Major browsers and certificate authorities no longer accept SHA-1 certificates.
SHA-256, part of the SHA-2 family developed by the NSA and standardized by NIST, produces a 256-bit digest and is currently the dominant general-purpose cryptographic hash. It is used in TLS/SSL certificates (securing HTTPS connections), Bitcoin and many other blockchain systems, code-signing certificates, and file integrity verification. SHA-256 has no known practical attacks and its 256-bit output makes brute-force pre-image attacks impossible with any foreseeable technology.
bcrypt and Argon2 occupy a special category: they are purpose-built for password hashing rather than general data hashing. The distinguishing design goal is intentional slowness. A general hash like SHA-256 can process hundreds of millions of inputs per second on commodity hardware — which is great for hashing large files quickly but catastrophic for password storage, because an attacker with stolen hashes can test enormous numbers of candidate passwords per second. bcrypt and Argon2 incorporate a configurable work factor (or cost parameter) that forces each hash computation to take a measurable amount of time (typically 100–500 milliseconds). This slows legitimate logins negligibly — a user waits a fraction of a second — but reduces an attacker's cracking speed from millions of attempts per second to perhaps tens or hundreds per second.
Argon2 is the winner of the Password Hashing Competition (2015) and offers additional resistance to GPU- and ASIC-based attacks by allowing configuration of memory usage. It is considered the current best practice for new systems.
The Role of Hashing in Data Integrity
Beyond passwords, one of hashing's most important applications is data integrity verification. The core idea is simple: if you hash a piece of data before transmitting or storing it, and later hash it again and compare the two digests, you can determine with extremely high confidence whether the data has been modified.
Consider a practical scenario. A software publisher releases a new version of their application as a downloadable file. They publish the SHA-256 hash of that file on their website. A user downloads the file (possibly from a mirror server they do not fully trust), then runs SHA-256 on the downloaded file themselves. If the computed hash matches the published hash exactly, the file is intact and unaltered. If even a single byte was changed — whether by a man-in-the-middle attacker injecting malware or by a network transmission error — the computed hash will be completely different, and the user knows not to trust the file.
# Example: Verifying a downloaded file on Linux
sha256sum downloaded_software.tar.gz
# Output: a3b1c2d4e5f6... downloaded_software.tar.gz
# Compare with the publisher's listed hash:
# a3b1c2d4e5f6... ← match confirms integrity
This same principle underpins digital signatures: rather than signing an entire large document (which would be slow), a cryptographic signature is applied to the document's hash. Verifying the signature means hashing the received document and checking that the signature matches that hash value, confirming both authenticity and integrity in a single efficient step.
Version control systems like Git rely entirely on hashing for their internal data model. Every file, directory snapshot, and commit is identified by its SHA-1 (historically) or SHA-256 (in newer versions) hash. This means any accidental or malicious change to any historical commit is immediately detectable, because the hash of the modified object will no longer match the stored reference.
The integrity-checking application of hashing is a direct consequence of the avalanche effect: because a one-bit change in input causes roughly half of the output bits to flip unpredictably, there is no such thing as a "small" or "undetected" alteration. Either the data is exactly identical (same hash) or it is different in any way (completely different hash). There is no middle ground, and this binary certainty is what makes hashing such a powerful tool for trust and verification.