ExkPasswd.Entropy (ExkPasswd v0.3.0)

View Source

Password entropy calculation and strength analysis.

This module provides entropy metrics to assess password strength from two perspectives:

  • Blind estimate: A heuristic brute-force search-space estimate based on the character classes present and the password length. It is not a claim about the probability distribution of an observed password.

  • Seen entropy: A conservative min-entropy estimate when the attacker knows the dictionary and configuration. Deterministic case, substitution, Pinyin, and Romaji collisions are included.

Security Model

Password strength comes from entropy (number of possible combinations) and cryptographically secure randomness, not from keeping the generation method secret.

Project Ratings

ExkPasswd uses these project-defined bands for its convenience rating:

  • < 40 bits: Weak
  • 40-52 bits: Fair
  • 52-78 bits: Good
  • 78+ bits: Excellent

These names are presentation labels, not standards-based suitability claims.

Examples

iex> config = ExkPasswd.Config.new!(num_words: 3)
...> password = ExkPasswd.generate(config)
...> result = ExkPasswd.Entropy.calculate(password, config)
...> result.blind > 40
true
iex> result.seen > 50
true

iex> ExkPasswd.Entropy.calculate_seen(ExkPasswd.Config.new!(num_words: 6))
...> # Returns entropy in bits (float)

Summary

Functions

Calculate blind and seen entropy metrics for a password and settings.

Calculate a blind brute-force search-space estimate for a password string.

Calculate seen entropy from settings.

Calculate seen entropy with detailed breakdown of entropy sources.

Determine strength status based on entropy values.

Calculate effective entropy from blind and seen values.

Estimate time to crack password based on entropy.

Types

entropy_result()

@type entropy_result() :: %{
  blind: float(),
  seen: float(),
  status: :excellent | :good | :fair | :weak,
  blind_crack_time: String.t(),
  seen_crack_time: String.t(),
  details: map()
}

Functions

calculate(password, settings)

@spec calculate(String.t(), ExkPasswd.Config.t()) :: entropy_result()

Calculate blind and seen entropy metrics for a password and settings.

Returns detailed entropy analysis including both blind and seen entropy, strength status, crack time estimates, and breakdown of entropy sources.

Parameters

  • password - The generated password string
  • config - The Config struct used to generate the password

Returns

A map containing:

  • :blind - Blind entropy in bits (float)
  • :seen - Seen entropy in bits (float)
  • :status - Overall strength (:excellent, :good, :fair, :weak)
  • :blind_crack_time - Human-readable crack time estimate for blind attack
  • :seen_crack_time - Human-readable crack time estimate for seen attack
  • :details - Breakdown of entropy components

Examples

iex> config = ExkPasswd.Config.new!(num_words: 4)
...> password = "12-HAPPY-forest-DANCE-bird-56"
...> result = ExkPasswd.Entropy.calculate(password, config)
...> is_float(result.blind) and is_float(result.seen)
true

calculate_blind(password)

@spec calculate_blind(String.t()) :: float()

Calculate a blind brute-force search-space estimate for a password string.

Analyzes the actual password to determine alphabet size (character types used) and calculates entropy based on brute-force attack assumptions.

Formula: L × log₂(A)

  • A = alphabet size (number of unique character types)
  • L = password length

Parameters

  • password - The password string to analyze

Returns

Entropy in bits (float)

Examples

iex> ExkPasswd.Entropy.calculate_blind("aB3!")
...> # ~26.3 bits for 4-char with mixed types

iex> blind = ExkPasswd.Entropy.calculate_blind("correcthorsebatterystaple")
...> blind > 100
true

calculate_seen(settings)

@spec calculate_seen(ExkPasswd.Config.t()) :: float()

Calculate seen entropy from settings.

Calculates conservative min-entropy assuming the attacker knows the dictionary and configuration. Random custom transforms whose output distribution cannot be verified are credited with no entropy.

Parameters

  • config - The Config struct

Returns

Entropy in bits (float)

Examples

iex> config = ExkPasswd.Config.new!(num_words: 3)
...> seen = ExkPasswd.Entropy.calculate_seen(config)
...> seen > 40
true

calculate_seen_detailed(settings)

@spec calculate_seen_detailed(ExkPasswd.Config.t()) :: map()

Calculate seen entropy with detailed breakdown of entropy sources.

Returns a map showing how each component contributes to total entropy.

Parameters

  • config - The Config struct

Returns

Map with entropy breakdown and total

Examples

iex> config = ExkPasswd.Config.new!(num_words: 3)
...> result = ExkPasswd.Entropy.calculate_seen_detailed(config)
...> is_float(result.total)
true

determine_status(blind, seen)

@spec determine_status(float(), float()) :: :excellent | :good | :fair | :weak

Determine strength status based on entropy values.

Parameters

  • blind - Blind entropy in bits
  • seen - Seen entropy in bits

Returns

Status atom: :excellent, :good, :fair, or :weak

Examples

iex> ExkPasswd.Entropy.determine_status(80, 80)
:excellent

iex> ExkPasswd.Entropy.determine_status(60, 55)
:good

effective_entropy(blind, seen)

@spec effective_entropy(float(), float()) :: float()

Calculate effective entropy from blind and seen values.

Uses the lower of the two as the limiting factor for security assessment.

Parameters

  • blind - Blind entropy in bits
  • seen - Seen entropy in bits

Returns

Effective entropy in bits (float)

Examples

iex> ExkPasswd.Entropy.effective_entropy(80.0, 65.0)
65.0

iex> ExkPasswd.Entropy.effective_entropy(50.0, 70.0)
50.0

estimate_crack_time(entropy_bits)

@spec estimate_crack_time(float()) :: String.t()

Estimate time to crack password based on entropy.

Uses a comparison rate of one billion guesses per second and an average search of half the space. This is not a prediction for a particular verifier or hash.

Parameters

  • entropy_bits - Entropy in bits

Returns

Human-readable time estimate string

Examples

iex> time = ExkPasswd.Entropy.estimate_crack_time(40)
...> String.contains?(time, "minute") or String.contains?(time, "second")
true

iex> time = ExkPasswd.Entropy.estimate_crack_time(80)
...> String.contains?(time, "year") or String.contains?(time, "centur")
true