Skip to content
UtilHQ
developer

How to Generate Hashes (MD5, SHA-256, SHA-512)

Learn how hash functions work and how to generate MD5, SHA-256, and SHA-512 hashes in code. Includes security best practices for file integrity and password storage.

By UtilHQ Team
Ad Space

A hash function takes any input (text, files, anything) and produces a fixed-length string of characters called a digest. The same input always produces the same output, but even a tiny change in the input produces a completely different hash.

This property makes hashes perfect for verifying file integrity, detecting duplicates, and (when done correctly) storing passwords.

The catch: not all hash algorithms are created equal. MD5 and SHA-1 are broken for security purposes. If you’re building anything security-sensitive, you need SHA-256 or stronger.

How to Generate Hashes in Code

Here’s how to generate common hash types in popular languages.

JavaScript (Node.js)

Node’s built-in crypto module handles all major hash algorithms.

const crypto = require('crypto');

// MD5 (NOT secure - use for checksums only)
const md5 = crypto.createHash('md5').update('hello').digest('hex');
console.log(md5); // "5d41402abc4b2a76b9719d911017c592"

// SHA-256 (secure)
const sha256 = crypto.createHash('sha256').update('hello').digest('hex');
console.log(sha256); // "2cf24dba5fb0a30e..."

// SHA-512 (even stronger)
const sha512 = crypto.createHash('sha512').update('hello').digest('hex');
console.log(sha512); // "9b71d224bd62f378..."

JavaScript (Browser)

Modern browsers have built-in support for SHA-based hashing.

async function sha256(text) {
  const buffer = new TextEncoder().encode(text);
  const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

const hash = await sha256('hello');
console.log(hash); // "2cf24dba5fb0a30e..."

Note: MD5 isn’t available in browser crypto. This is intentional - browsers don’t want to encourage insecure hashing.

Python

import hashlib

text = "hello"

# MD5
md5 = hashlib.md5(text.encode()).hexdigest()
print(md5)  # "5d41402abc4b2a76b9719d911017c592"

# SHA-256
sha256 = hashlib.sha256(text.encode()).hexdigest()
print(sha256)  # "2cf24dba5fb0a30e..."

# SHA-512
sha512 = hashlib.sha512(text.encode()).hexdigest()
print(sha512)  # "9b71d224bd62f378..."

Bash (Command Line)

Linux and macOS have built-in hash utilities.

# MD5
echo -n "hello" | md5sum
# 5d41402abc4b2a76b9719d911017c592

# SHA-256
echo -n "hello" | sha256sum
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

# Hash a file
sha256sum file.zip

On macOS, use md5, shasum -a 256, or shasum -a 512 instead.

Hash Algorithm Comparison

Not all hashing algorithms serve the same purpose. Here’s what to use when.

AlgorithmOutput LengthSpeedSecurity StatusBest Use Case
MD5128 bits (32 hex)Very fastBROKENFile checksums (non-security)
SHA-1160 bits (40 hex)FastBROKENLegacy systems only
SHA-256256 bits (64 hex)FastSecureFile integrity, digital signatures
SHA-512512 bits (128 hex)FastSecureHigh-security applications
bcryptVariableSlow (intentional)SecurePassword hashing only
Argon2VariableSlow (intentional)SecureModern password hashing

MD5 and SHA-1 are cryptographically broken. Attackers can create collisions (different inputs that produce the same hash). Don’t use them for security.

SHA-256 and SHA-512 are secure for file integrity, digital signatures, and blockchain applications.

Never use fast hashes for passwords. Use bcrypt, scrypt, or Argon2 instead - they’re intentionally slow to resist brute-force attacks.

Anatomy of a Hash

A hash is a fixed-length hexadecimal string. Let’s break down a SHA-256 hash:

2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Key Properties:

  1. Deterministic: The same input always produces the same hash.
  2. Fixed Length: SHA-256 always produces 64 hex characters (256 bits), regardless of input size.
  3. One-Way: You can’t reverse a hash to get the original input.
  4. Avalanche Effect: Changing one character in the input completely changes the output.

Example - Tiny Input Change:

Input: "hello"
SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

Input: "Hello" (capital H)
SHA-256: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969

Completely different hashes, even though we only changed one letter’s capitalization.

Use Cases for Hash Functions

File Integrity Verification

When you download software, the publisher often provides a hash checksum. You hash the downloaded file and compare it to the published hash to verify the file wasn’t corrupted or tampered with.

# Download file
curl -O https://example.com/software.zip

# Verify against published hash
sha256sum software.zip
# Compare output to the hash on the download page

If the hashes match, the file is authentic. If they differ, the file was modified (accidentally or maliciously).

Detecting Duplicate Files

Cloud storage services use hashes to deduplicate files. If two users upload the same file, the service stores it once and tracks two references to it.

const files = ['file1.txt', 'file2.txt', 'file3.txt'];
const hashes = new Map();

files.forEach(file => {
  const content = fs.readFileSync(file);
  const hash = crypto.createHash('sha256').update(content).digest('hex');

  if (hashes.has(hash)) {
    console.log(`${file} is a duplicate of ${hashes.get(hash)}`);
  } else {
    hashes.set(hash, file);
  }
});

Content Addressing

Git uses SHA-1 hashes to uniquely identify every commit, tree, and blob. The hash of the content becomes its address.

Example: Git commit hash a3f7d9c is the SHA-1 hash of the commit’s metadata and changes.

Digital Signatures

Signing large files is slow. Instead, sign the hash of the file (which is fast), then verify the signature against the hash.

1. Hash the file → SHA-256 digest
2. Sign the digest with private key → Signature
3. Recipient hashes the file → SHA-256 digest
4. Verify signature against digest → Confirms authenticity

Password Storage (With Critical Caveats)

Storing passwords in plain text is suicide. Storing SHA-256 hashes of passwords is almost as bad.

Wrong Way:

// NEVER DO THIS
const passwordHash = crypto.createHash('sha256')
  .update(password)
  .digest('hex');
// Vulnerable to rainbow tables and brute force

Right Way:

const bcrypt = require('bcrypt');

// Hash with bcrypt (slow, salted, secure)
const hash = await bcrypt.hash(password, 10);
// Result includes salt and cost factor

Why? Fast hashes like SHA-256 can be brute-forced at billions of attempts per second on GPUs. Bcrypt/Argon2 are designed to be slow (taking ~100ms per attempt), making brute-force attacks impractical.

Security Considerations

MD5 and SHA-1 Are Broken for Security

In 2017, Google demonstrated a practical SHA-1 collision attack. Two different PDF files produced the same SHA-1 hash. This breaks digital signatures and certificate validation.

Verdict: Use SHA-256 or stronger for anything security-related.

Safe uses for MD5:

  • Non-critical checksums (verifying file transfer integrity within trusted networks)
  • Cache keys (where collisions cause bugs, not security breaches)
  • Database row deduplication

Rainbow Tables and Salting

A rainbow table is a precomputed database of hashes for common passwords. If you store unsalted password hashes, attackers can reverse them instantly.

Example:

SHA-256("password123") = ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f

This hash appears in every rainbow table. An attacker with your database can crack millions of passwords in seconds.

Solution: Add a random salt before hashing.

const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.createHash('sha256')
  .update(salt + password)
  .digest('hex');
// Store both salt and hash

Better yet, use bcrypt/Argon2 which handle salting automatically.

Hash Length and Collision Probability

Longer hashes reduce collision probability. A collision is when two different inputs produce the same hash.

Birthday Paradox Math:

  • MD5 (128 bits): 50% collision chance after 2^64 hashes (~18 quintillion)
  • SHA-256 (256 bits): 50% collision chance after 2^128 hashes (functionally impossible)

For perspective, 2^128 is more than the number of atoms in the observable universe.

Common Mistakes to Avoid

Using Hashes for Encryption

Hashes are one-way functions. You can’t decrypt a hash to recover the original data.

Wrong:

// This makes no sense
const encrypted = crypto.createHash('sha256').update(secret).digest('hex');
const decrypted = ??? // Impossible

Right:

// Use encryption for reversible operations
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const encrypted = cipher.update(secret, 'utf8', 'hex') + cipher.final('hex');
// Can be decrypted with the key

Not Comparing Hashes Safely

Comparing hashes with == or === can leak timing information, allowing attackers to determine if they’re getting close to the correct hash.

Vulnerable:

if (userHash === storedHash) {
  // Timing attack: comparison stops at first different character
}

Secure:

const crypto = require('crypto');
if (crypto.timingSafeEqual(Buffer.from(userHash), Buffer.from(storedHash))) {
  // Constant-time comparison
}

Hashing Passwords with Fast Algorithms

SHA-256 can compute billions of hashes per second on a GPU. If your password database leaks, attackers will brute-force every password in hours.

Benchmark (NVIDIA RTX 4090):

  • SHA-256: 15 billion hashes/second
  • bcrypt (cost 10): ~60,000 hashes/second

That’s a 250,000x difference. Use bcrypt/Argon2 for passwords.

Forgetting to Encode Input Properly

Text encoding matters. “hello” encoded as UTF-8 vs. UTF-16 produces different hashes.

// Consistent encoding
const hash = crypto.createHash('sha256')
  .update(text, 'utf8')  // Explicit encoding
  .digest('hex');

Pro Tips

Hashing Large Files Efficiently

Don’t load entire files into memory. Stream them instead.

const crypto = require('crypto');
const fs = require('fs');

function hashFile(filename) {
  return new Promise((resolve, reject) => {
    const hash = crypto.createHash('sha256');
    const stream = fs.createReadStream(filename);

    stream.on('data', chunk => hash.update(chunk));
    stream.on('end', () => resolve(hash.digest('hex')));
    stream.on('error', reject);
  });
}

const fileHash = await hashFile('large-file.zip');

This works for gigabyte-sized files without memory issues.

Verifying Downloaded Files

Always verify checksums for downloaded software.

# macOS
curl -O https://example.com/software.dmg
shasum -a 256 software.dmg

# Linux
wget https://example.com/software.tar.gz
sha256sum software.tar.gz

# Compare output to published checksum

If they don’t match, delete the file. It’s either corrupted or compromised.

Content-Addressable Storage

Use hashes as filenames to ensure uniqueness and easy deduplication.

async function saveFile(content) {
  const hash = crypto.createHash('sha256')
    .update(content)
    .digest('hex');

  const filename = `uploads/${hash.slice(0, 2)}/${hash.slice(2, 4)}/${hash}.jpg`;
  // Path: uploads/2c/f2/2cf24dba5fb0a30e...jpg

  await fs.promises.writeFile(filename, content);
  return hash;
}

This pattern prevents filename collisions and makes duplicate detection trivial.

HMAC for Message Authentication

When you need to verify both integrity and authenticity, use HMAC (Hash-based Message Authentication Code).

const hmac = crypto.createHmac('sha256', secretKey)
  .update(message)
  .digest('hex');

HMAC uses a secret key, so only parties with the key can generate valid hashes. This prevents tampering.

Use case: API request signatures. The client signs the request with a shared secret, and the server verifies the signature.

Real-World Examples

Blockchain Proof-of-Work

Bitcoin mining is essentially finding a hash that starts with a certain number of zeros.

function mine(data, difficulty) {
  let nonce = 0;
  const target = '0'.repeat(difficulty);

  while (true) {
    const hash = crypto.createHash('sha256')
      .update(data + nonce)
      .digest('hex');

    if (hash.startsWith(target)) {
      return { nonce, hash };
    }
    nonce++;
  }
}

// Find hash starting with "000"
const result = mine('Block data', 3);
console.log(result);
// { nonce: 12345, hash: "000a3f7d9c..." }

This is computationally expensive by design - it secures the blockchain.

Git Commit Hashing

Every Git commit has a SHA-1 hash based on its content.

# See commit hash
git log --oneline
# a3f7d9c Add feature

# Show what's hashed
git cat-file commit a3f7d9c
# tree <hash>
# parent <hash>
# author ...
# committer ...
#
# Add feature

The hash is deterministic - if the commit content changes, the hash changes.

CDN Cache Busting

Append content hashes to filenames for cache invalidation.

// Before: style.css (cached forever - bad)
// After: style.2cf24dba.css (new hash = new file)

const hash = crypto.createHash('md5')
  .update(fileContent)
  .digest('hex')
  .slice(0, 8);

const filename = `style.${hash}.css`;

When you update the CSS, the hash changes, browsers fetch the new file. Old versions remain cached.

Frequently Asked Questions

Can two different inputs produce the same hash?

Theoretically yes (infinite inputs, finite outputs), but practically no for modern algorithms. With SHA-256, you’d need to compute 2^128 hashes to have a 50% collision chance, which requires more energy than the sun produces.

Can you reverse a hash to get the original input?

No. Hash functions are one-way by design. You can’t mathematically reverse them. However, attackers can use rainbow tables (precomputed hashes) or brute force to find inputs that match a hash.

Why is MD5 still used if it is broken?

MD5 is broken for security (collision attacks), but it’s still fine for non-security uses like checksums, ETags, and cache keys. It’s faster than SHA-256 and “good enough” when security isn’t a concern.

How do attackers crack password hashes?

Attackers use three primary methods: rainbow tables (precomputed hashes for common passwords), brute force (trying every combination, which is fast with GPUs), and dictionary attacks (trying common passwords first). Slow hashing with bcrypt or Argon2 makes these attacks impractical.

What is the difference between hashing and encryption?

Hashing is one-way (irreversible), encryption is two-way (reversible with a key). Use hashing for integrity verification and password storage. Use encryption for confidentiality.

Should I use SHA-256 or SHA-512?

SHA-256 is standard and plenty secure for most uses. SHA-512 is overkill unless you’re in a high-security environment (government, finance). SHA-512 is faster on 64-bit processors but produces larger outputs.

What is a salt and why do I need it?

A salt is random data added to the input before hashing. It prevents rainbow table attacks by making every hash unique, even for identical passwords. Modern password hashing libraries (bcrypt/Argon2) handle salting automatically.

Can I use hashes as database primary keys?

Yes, but it’s usually a bad idea. Hashes are long (32-64 characters), making indexes larger and slower than integer keys. Use hashes for content addressing, not primary keys.

Use the Online Generator

Need to quickly hash text or verify a file checksum?

Use our Free Hash Generator. It supports MD5, SHA-1, SHA-256, and SHA-512 with instant results. Perfect for verifying downloads, generating checksums, or learning how different algorithms compare. No installation required - runs entirely in your browser.

Share this article

Have suggestions for this article?