Cryptographic hash functions are fundamental building blocks in modern security systems. They transform arbitrary-sized data into fixed-size values, enabling data integrity verification, password storage, digital signatures, and much more. In this comprehensive guide, we'll explore how hash functions work and how to use them correctly.
What is a Cryptographic Hash Function?
A cryptographic hash function is a mathematical algorithm that:
- 1Deterministic: Same input always produces same output
- 2Fixed Output: Produces fixed-size output regardless of input size
- 3Pre-image Resistant: Computationally infeasible to reverse
- 4Collision Resistant: Hard to find two inputs with same output
- 5Avalanche Effect: Small input changes cause massive output changes
import { createHash } from 'crypto';
function hash(data: string): string {
return createHash('sha256')
.update(data)
.digest('hex');
}
// Demonstrating properties
const data1 = 'Hello, World!';
const data2 = 'Hello, World?'; // One character different
console.log('Hash 1:', hash(data1));
console.log('Hash 2:', hash(data2));
console.log('Same length:', hash(data1).length === hash(data2).length);
console.log('Completely different:', hash(data1) !== hash(data2));Popular Hash Functions
SHA-256 (Secure Hash Algorithm 256-bit)
SHA-256 is part of the SHA-2 family and is the most widely used hash function.
import { createHash } from 'crypto';
function sha256(data: string): string {
return createHash('sha256')
.update(data, 'utf8')
.digest('hex');
}
// Always produces 64-character hex string (256 bits)
console.log(sha256('password'));
// Output: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8SHA-3 (Keccak)
SHA-3 is the latest member of the Secure Hash Algorithm family.
import { createHash } from 'crypto';
function sha3(data: string): string {
return createHash('sha3-256')
.update(data)
.digest('hex');
}BLAKE2
BLAKE2 is faster than MD5, SHA-1, and SHA-256, while providing similar security.
import { createHash } from 'crypto';
function blake2b(data: string): string {
return createHash('blake2b512')
.update(data)
.digest('hex');
}Data Integrity Verification
Hash functions are commonly used to verify data integrity.
import { createReadStream } from 'fs';
import { createHash } from 'crypto';
import { pipeline } from 'stream/promises';
async function calculateFileHash(filePath: string): Promise<string> {
const hash = createHash('sha256');
const stream = createReadStream(filePath);
await pipeline(stream, hash);
return hash.digest('hex');
}
// Verify downloaded file
async function verifyFile(filePath: string, expectedHash: string): Promise<boolean> {
const actualHash = await calculateFileHash(filePath);
console.log('Expected:', expectedHash);
console.log('Actual:', actualHash);
return actualHash.toLowerCase() === expectedHash.toLowerCase();
}
// Usage
await verifyFile('download.zip', 'a1b2c3d4e5f6...');Checksum Verification for Large Files
class FileIntegrityChecker {
async generateChecksum(filePath: string): Promise<{
sha256: string;
md5: string;
size: number;
}> {
const [sha256, md5, stats] = await Promise.all([
this.hashFile(filePath, 'sha256'),
this.hashFile(filePath, 'md5'),
fs.promises.stat(filePath),
]);
return {
sha256,
md5,
size: stats.size,
};
}
private async hashFile(filePath: string, algorithm: string): Promise<string> {
const hash = createHash(algorithm);
const stream = createReadStream(filePath);
await pipeline(stream, hash);
return hash.digest('hex');
}
async verifyChecksum(
filePath: string,
expected: { sha256: string; md5: string; size: number }
): Promise<boolean> {
const actual = await this.generateChecksum(filePath);
return (
actual.sha256.toLowerCase() === expected.sha256.toLowerCase() &&
actual.md5.toLowerCase() === expected.md5.toLowerCase() &&
actual.size === expected.size
);
}
}Password Hashing
IMPORTANT: Never store passwords in plaintext. Never use simple hash functions like SHA-256 for passwords. Always use specialized password hashing functions.
Why Not Simple Hashing?
// ❌ BAD: Simple hashing
import { createHash } from 'crypto';
function hashPasswordBad(password: string): string {
return createHash('sha256').update(password).digest('hex');
}
// Vulnerable to:
// 1. Rainbow tables (precomputed hash lookups)
// 2. Dictionary attacks
// 3. Same passwords have same hashesUsing bcrypt
bcrypt is designed specifically for password hashing.
import bcrypt from 'bcrypt';
class PasswordHasher {
// Hash password with salt
async hashPassword(password: string): Promise<string> {
const saltRounds = 12; // Higher = more secure but slower
return await bcrypt.hash(password, saltRounds);
}
// Verify password
async verifyPassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
}
// Usage
const hasher = new PasswordHasher();
// Registration
const password = 'userPassword123';
const hash = await hasher.hashPassword(password);
await database.saveUser({ username: 'user', passwordHash: hash });
// Login
const user = await database.findByUsername('user');
const isValid = await hasher.verifyPassword(password, user.passwordHash);Using Argon2 (Recommended)
Argon2 is the winner of the Password Hashing Competition and is considered the best choice for new applications.
import argon2 from 'argon2';
class Argon2Hasher {
async hashPassword(password: string): Promise<string> {
return await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64 MB
timeCost: 3, // Number of iterations
parallelism: 4,
});
}
async verifyPassword(password: string, hash: string): Promise<boolean> {
try {
return await argon2.verify(hash, password);
} catch (error) {
return false;
}
}
}Using PBKDF2
PBKDF2 is widely supported and recommended by NIST.
import { pbkdf2, randomBytes } from 'crypto';
class PBKDF2Hasher {
async hashPassword(password: string): Promise<{
hash: string;
salt: string;
iterations: number;
}> {
const salt = randomBytes(32);
const iterations = 100000;
const hash = await new Promise<Buffer>((resolve, reject) => {
pbkdf2(password, salt, iterations, 32, 'sha256', (err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
});
});
return {
hash: hash.toString('hex'),
salt: salt.toString('hex'),
iterations,
};
}
async verifyPassword(
password: string,
stored: { hash: string; salt: string; iterations: number }
): Promise<boolean> {
const hash = await new Promise<Buffer>((resolve, reject) => {
pbkdf2(
password,
Buffer.from(stored.salt, 'hex'),
stored.iterations,
32,
'sha256',
(err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
}
);
});
return hash.toString('hex') === stored.hash;
}
}Hash Tables and Caching
Hash functions are essential for efficient data storage and retrieval.
class HashTable<K, V> {
private buckets: Array<Array<{ key: K; value: V }>> = [];
private size: number;
constructor(size: number = 100) {
this.size = size;
this.buckets = new Array(size).fill(null).map(() => []);
}
private hash(key: K): number {
const stringKey = String(key);
let hash = 0;
for (let i = 0; i < stringKey.length; i++) {
const char = stringKey.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash) % this.size;
}
set(key: K, value: V): void {
const index = this.hash(key);
const bucket = this.buckets[index];
// Update if key exists
const existing = bucket.find((item) => item.key === key);
if (existing) {
existing.value = value;
return;
}
// Add new entry
bucket.push({ key, value });
}
get(key: K): V | undefined {
const index = this.hash(key);
const bucket = this.buckets[index];
const item = bucket.find((item) => item.key === key);
return item?.value;
}
delete(key: K): boolean {
const index = this.hash(key);
const bucket = this.buckets[index];
const itemIndex = bucket.findIndex((item) => item.key === key);
if (itemIndex === -1) return false;
bucket.splice(itemIndex, 1);
return true;
}
}Content Addressing (Git-like Storage)
Hash functions enable content-addressed storage where the content's hash becomes its identifier.
import { createHash } from 'crypto';
import { mkdir, writeFile, readFile } from 'fs/promises';
import { join } from 'path';
class ContentAddressedStorage {
constructor(private basePath: string) {}
async store(content: string): Promise<string> {
const hash = this.calculateHash(content);
const path = this.getPathFromHash(hash);
await mkdir(path.dir, { recursive: true });
await writeFile(path.full, content);
return hash;
}
async retrieve(hash: string): Promise<string | null> {
const path = this.getPathFromHash(hash);
try {
return await readFile(path.full, 'utf8');
} catch {
return null;
}
}
private calculateHash(content: string): string {
return createHash('sha256').update(content).digest('hex');
}
private getPathFromHash(hash: string): { dir: string; full: string } {
const dir = join(this.basePath, hash.substring(0, 2));
const full = join(dir, hash.substring(2));
return { dir, full };
}
}
// Usage
const storage = new ContentAddressedStorage('./objects');
// Store content
const hash1 = await storage.store('Hello, World!');
const hash2 = await storage.store('Hello, World!'); // Same content, same hash
// Retrieve content
const content = await storage.retrieve(hash1);Merkle Trees
Merkle trees enable efficient verification of large data structures.
import { createHash } from 'crypto';
interface MerkleNode {
hash: string;
left?: MerkleNode;
right?: MerkleNode;
}
class MerkleTree {
private root: MerkleNode;
constructor(data: string[]) {
this.root = this.buildTree(data);
}
private buildTree(data: string[]): MerkleNode {
const leaves: MerkleNode[] = data.map((item) => ({
hash: this.hash(item),
}));
return this.buildLevel(leaves);
}
private buildLevel(nodes: MerkleNode[]): MerkleNode {
if (nodes.length === 1) {
return nodes[0];
}
const nextLevel: MerkleNode[] = [];
for (let i = 0; i < nodes.length; i += 2) {
const left = nodes[i];
const right = i + 1 < nodes.length ? nodes[i + 1] : left;
const combinedHash = this.hash(left.hash + right.hash);
nextLevel.push({
hash: combinedHash,
left,
right,
});
}
return this.buildLevel(nextLevel);
}
private hash(data: string): string {
return createHash('sha256').update(data).digest('hex');
}
getRootHash(): string {
return this.root.hash;
}
verify(data: string[], rootHash: string): boolean {
const tree = new MerkleTree(data);
return tree.getRootHash() === rootHash;
}
}Bloom Filters
Bloom filters are space-efficient probabilistic data structures based on hashing.
import { createHash } from 'crypto';
class BloomFilter {
private bitArray: boolean[];
private size: number;
private hashCount: number;
constructor(size: number = 1000, hashCount: number = 3) {
this.size = size;
this.hashCount = hashCount;
this.bitArray = new Array(size).fill(false);
}
private hash(item: string, seed: number): number {
const hash = createHash('sha256')
.update(item + seed)
.digest('hex');
let value = 0;
for (let i = 0; i < hash.length; i++) {
value = (value * 31 + hash.charCodeAt(i)) % this.size;
}
return Math.abs(value);
}
add(item: string): void {
for (let i = 0; i < this.hashCount; i++) {
const index = this.hash(item, i);
this.bitArray[index] = true;
}
}
has(item: string): boolean {
for (let i = 0; i < this.hashCount; i++) {
const index = this.hash(item, i);
if (!this.bitArray[index]) {
return false;
}
}
return true;
}
}
// Usage
const filter = new BloomFilter();
filter.add('user1@example.com');
filter.add('user2@example.com');
console.log(filter.has('user1@example.com')); // true
console.log(filter.has('user3@example.com')); // false (probably)HMAC (Hash-based Message Authentication Code)
HMAC provides both data integrity and authentication.
import { createHmac } from 'crypto';
class HMACAuth {
constructor(private secretKey: string) {}
sign(message: string): string {
return createHmac('sha256', this.secretKey)
.update(message)
.digest('hex');
}
verify(message: string, signature: string): boolean {
const expectedSignature = this.sign(message);
return signature === expectedSignature;
}
}
// Usage
const auth = new HMACAuth(process.env.API_SECRET!);
// Generate signature for request
const message = JSON.stringify({ user: 123, action: 'delete' });
const signature = auth.sign(message);
// Verify incoming request
const isValid = auth.verify(message, signature);HMAC for API Authentication
interface APIRequest {
method: string;
path: string;
body?: any;
timestamp: number;
}
class APIClient {
constructor(
private apiKey: string,
private apiSecret: string
) {}
signRequest(request: APIRequest): string {
const payload = JSON.stringify({
method: request.method,
path: request.path,
body: request.body,
timestamp: request.timestamp,
});
return createHmac('sha256', this.apiSecret)
.update(payload)
.digest('hex');
}
async makeRequest(method: string, path: string, body?: any) {
const timestamp = Date.now();
const request: APIRequest = { method, path, body, timestamp };
const signature = this.signRequest(request);
return fetch(`https://api.example.com${path}`, {
method,
headers: {
'X-API-Key': this.apiKey,
'X-Signature': signature,
'X-Timestamp': timestamp.toString(),
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
}
}Consistent Hashing
Used in distributed systems for data partitioning.
import { createHash } from 'crypto';
interface Server {
id: string;
address: string;
}
class ConsistentHash {
private ring: Map<number, Server> = new Map();
private virtualNodes: number = 150;
addServer(server: Server): void {
for (let i = 0; i < this.virtualNodes; i++) {
const virtualNodeKey = `${server.id}#${i}`;
const hash = this.hash(virtualNodeKey);
this.ring.set(hash, server);
}
}
removeServer(server: Server): void {
for (let i = 0; i < this.virtualNodes; i++) {
const virtualNodeKey = `${server.id}#${i}`;
const hash = this.hash(virtualNodeKey);
this.ring.delete(hash);
}
}
getServer(key: string): Server | undefined {
if (this.ring.size === 0) return undefined;
const hash = this.hash(key);
const sortedHashes = Array.from(this.ring.keys()).sort((a, b) => a - b);
// Find first server with hash >= key hash
for (const ringHash of sortedHashes) {
if (ringHash >= hash) {
return this.ring.get(ringHash);
}
}
// Wrap around to first server
return this.ring.get(sortedHashes[0]);
}
private hash(key: string): number {
const hash = createHash('md5').update(key).digest('hex');
return parseInt(hash.substring(0, 8), 16);
}
}Common Pitfalls
1. Using Weak Hash Functions
// ❌ BAD: MD5 and SHA-1 are broken
import { createHash } from 'crypto';
const weak = createHash('md5').update(data).digest('hex');
// ✅ GOOD: Use SHA-256 or better
const strong = createHash('sha256').update(data).digest('hex');2. Hashing Passwords Without Salt
// ❌ BAD: Same passwords have same hashes
const hash = createHash('sha256').update(password).digest('hex');
// ✅ GOOD: Use bcrypt or argon2 (includes salt)
const hash = await bcrypt.hash(password, 10);3. Hash Concatenated Strings Without Separator
// ❌ BAD: 'user1' + 'pass' vs 'user' + '1pass' can collide
const hash = createHash('sha256').update(user + password).digest('hex');
// ✅ GOOD: Use separator or HMAC
const hash = createHmac('sha256', key).update(user + password).digest('hex');Performance Considerations
import { performance } from 'perf_hooks';
function benchmarkHash(algorithm: string, data: string, iterations: number = 10000) {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
createHash(algorithm).update(data).digest();
}
const end = performance.now();
const duration = end - start;
console.log(`${algorithm}: ${duration.toFixed(2)}ms for ${iterations} iterations`);
console.log(`Average: ${(duration / iterations).toFixed(4)}ms per hash`);
}
// Benchmark
const data = 'Hello, World!';
benchmarkHash('md5', data);
benchmarkHash('sha1', data);
benchmarkHash('sha256', data);
benchmarkHash('sha512', data);Conclusion
Hash functions are fundamental to modern cryptography and security. From verifying data integrity to storing passwords securely, they enable countless security features. By understanding the different types of hash functions and their appropriate use cases, you can build more secure and efficient systems.
Remember: Always use specialized password hashing functions (bcrypt, Argon2) for passwords, SHA-256 or better for general hashing, and HMAC when authentication is required. Never roll your own cryptographic algorithms—always use well-established, peer-reviewed implementations.