Password Strength Checkers: From Character Sets to Entropy Calculation#

Ever wondered how those “Password Strength: Strong” indicators work? Why do some sites require uppercase, lowercase, numbers, and special characters? Let’s break down the math behind password strength detection.

The Core: Information Entropy#

Password strength is fundamentally about information entropy - measuring the uncertainty in a password. Higher entropy means harder to guess.

The formula:

Entropy = log₂(N^L) = L × log₂(N)

Where:

  • N = character set size (number of possible characters)
  • L = password length

For example, an 8-character lowercase password:

  • N = 26 (a-z)
  • L = 8
  • Entropy = 8 × log₂(26) ≈ 37.6 bits

An 8-character mixed password (uppercase + lowercase + numbers + symbols):

  • N = 26 + 26 + 10 + 32 = 94
  • L = 8
  • Entropy = 8 × log₂(94) ≈ 52.4 bits

That 15-bit difference means the harder password takes 2^15 = 32,768 times longer to crack!

Crack Time Estimation#

With entropy, we can estimate brute-force time:

const charset =
  (hasLowercase ? 26 : 0) +
  (hasUppercase ? 26 : 0) +
  (hasNumbers ? 10 : 0) +
  (hasSpecial ? 32 : 0)

const combinations = Math.pow(charset || 1, password.length)
const guessesPerSecond = 10_000_000_000  // Modern GPU: ~10 billion/sec
const seconds = combinations / guessesPerSecond

Convert seconds to human-readable time:

function formatCrackTime(seconds: number): string {
  if (seconds < 1) return 'Instant'
  if (seconds < 60) return `${Math.round(seconds)} seconds`
  if (seconds < 3600) return `${Math.round(seconds / 60)} minutes`
  if (seconds < 86400) return `${Math.round(seconds / 3600)} hours`
  if (seconds < 31536000) return `${Math.round(seconds / 86400)} days`
  return `${Math.round(seconds / 31536000)} years`
}

A 12-character mixed password takes about 17.8 billion years to crack - Earth won’t even exist by then.

Scoring Algorithm#

Raw entropy isn’t intuitive, so we design a 6-point scoring system:

function analyzePassword(password: string): StrengthResult {
  let score = 0
  
  const checks = {
    length: password.length >= 8,        // At least 8 chars
    uppercase: /[A-Z]/.test(password),   // Has uppercase
    lowercase: /[a-z]/.test(password),   // Has lowercase
    numbers: /\d/.test(password),        // Has numbers
    special: /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password),
    minLength: password.length >= 12,    // Extra point for 12+ chars
  }
  
  if (checks.length) score += 1
  if (checks.uppercase) score += 1
  if (checks.lowercase) score += 1
  if (checks.numbers) score += 1
  if (checks.special) score += 1
  if (checks.minLength) score += 1
  
  // ... penalty logic follows
}

Common Password Blacklist#

Some passwords pass the rules but are too common. They need penalties:

const COMMON_PASSWORDS = [
  '123456', 'password', '12345678', 'qwerty', '123456789',
  'letmein', '1234567', 'football', 'iloveyou', 'admin',
  'welcome', 'monkey', 'login', 'abc123', '111111',
]

if (COMMON_PASSWORDS.includes(password.toLowerCase())) {
  score = Math.max(0, score - 3)  // Deduct 3 points
}

123456 and password top the leaked password lists every year. They’re cracked instantly via rainbow tables.

Repeated Character Detection#

aaaaaa has decent entropy on paper but is actually weak. Detect consecutive repeats:

if (/(.)\1{2,}/.test(password)) {
  score = Math.max(0, score - 1)
}

The regex (.)\1{2,} means: any character appearing 3+ times consecutively. \1 is a backreference to the first capture group.

Strong Password Generator#

Detection is half the battle. Here’s a generation trick: guarantee each character type:

function generatePassword(): string {
  const charset = {
    uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    lowercase: 'abcdefghijklmnopqrstuvwxyz',
    numbers: '0123456789',
    special: '!@#$%^&*()_+-=[]{}|;:,.<>?',
  }
  
  let password = ''
  // Guarantee at least one of each type
  password += charset.uppercase[Math.floor(Math.random() * 26)]
  password += charset.lowercase[Math.floor(Math.random() * 26)]
  password += charset.numbers[Math.floor(Math.random() * 10)]
  password += charset.special[Math.floor(Math.random() * 32)]
  
  // Fill remaining positions randomly
  const allChars = Object.values(charset).join('')
  for (let i = password.length; i < 16; i++) {
    password += allChars[Math.floor(Math.random() * allChars.length)]
  }
  
  // Shuffle the result
  return password.split('').sort(() => Math.random() - 0.5).join('')
}

The final shuffle is crucial - otherwise the first 4 characters would always follow the “uppercase + lowercase + number + symbol” pattern.

Security Considerations#

1. Don’t Use Math.random()#

Math.random() is pseudo-random and predictable. Production code should use crypto.getRandomValues():

const array = new Uint32Array(1)
crypto.getRandomValues(array)
const randomIndex = array[0] % charsetLength

2. Frontend Detection Limitations#

Client-side checks only stop honest users. Real security requires:

  • Server-side salted hashing (bcrypt/scrypt/Argon2)
  • Rate limiting against brute force
  • Multi-factor authentication (MFA)
  • Breach detection (Have I Been Pwned API)

3. Never Transmit Passwords in URLs or Logs#

Even for strength checking, passwords should only exist in memory. Never send them to a server.

Try It Yourself#

Built on these principles: Password Strength Checker

Features:

  • Real-time 6-point scoring
  • Crack time estimation
  • Improvement suggestions
  • One-click strong password generation
  • Common password blacklist detection

Next time you set a password, check its entropy first - your data security might depend on those extra characters.


Related: Password Generator | Hash Generator