Class: PassForge::Passphrase

Inherits:
Object
  • Object
show all
Defined in:
lib/passforge/passphrase.rb

Overview

Passphrase generator for creating memorable, XKCD-style passwords Uses EFF’s long wordlist for maximum security and memorability

Class Method Summary collapse

Class Method Details

.crack_time(words:) ⇒ String

Estimate crack time for a passphrase

Parameters:

  • words (Integer)

    Number of words

Returns:

  • (String)

    Human-readable crack time estimate



61
62
63
64
65
66
67
68
69
# File 'lib/passforge/passphrase.rb', line 61

def self.crack_time(words:)
  ent = entropy(words: words)
  guesses_per_second = 1_000_000_000 # 1 billion guesses/sec
  
  total_guesses = 2**ent
  seconds = total_guesses / guesses_per_second
  
  format_time(seconds)
end

.entropy(words:) ⇒ Float

Calculate entropy of a passphrase

Parameters:

  • words (Integer)

    Number of words

Returns:

  • (Float)

    Entropy in bits



50
51
52
53
54
55
56
# File 'lib/passforge/passphrase.rb', line 50

def self.entropy(words:)
  # EFF wordlist has 7,776 words (2^12.9)
  # Entropy = log2(possibilities^words)
  # For our wordlist: log2(1000^words) ≈ 9.97 * words
  wordlist_size = Wordlist::WORDS.length
  Math.log2(wordlist_size) * words
end

.format_time(seconds) ⇒ Object

Format seconds into human-readable time



73
74
75
76
77
78
79
80
81
82
# File 'lib/passforge/passphrase.rb', line 73

def self.format_time(seconds)
  return "instant" if seconds < 1
  return "#{seconds.to_i} seconds" if seconds < 60
  return "#{(seconds / 60).to_i} minutes" if seconds < 3600
  return "#{(seconds / 3600).to_i} hours" if seconds < 86_400
  return "#{(seconds / 86_400).to_i} days" if seconds < 31_536_000
  return "#{(seconds / 31_536_000).to_i} years" if seconds < 31_536_000_000
  
  "#{(seconds / 31_536_000_000).to_i} millennia"
end

.generate(words: 4, separator: "-", capitalize: true, numbers: false) ⇒ String

Generate a passphrase

Examples:

Generate a basic passphrase

PassForge::Passphrase.generate
# => "Correct-Horse-Battery-Staple"

Generate with custom separator

PassForge::Passphrase.generate(words: 5, separator: " ")
# => "Correct Horse Battery Staple Clipper"

Generate with numbers

PassForge::Passphrase.generate(words: 4, numbers: true)
# => "Correct-Horse-Battery-Staple-42"

Parameters:

  • words (Integer) (defaults to: 4)

    Number of words in the passphrase (default: 4)

  • separator (String) (defaults to: "-")

    Character(s) to separate words (default: “-”)

  • capitalize (Boolean) (defaults to: true)

    Capitalize first letter of each word (default: true)

  • numbers (Boolean) (defaults to: false)

    Add a random number at the end (default: false)

Returns:

  • (String)

    Generated passphrase

Raises:

  • (ArgumentError)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/passforge/passphrase.rb', line 29

def self.generate(words: 4, separator: "-", capitalize: true, numbers: false)
  raise ArgumentError, "Words must be at least 2" if words < 2
  raise ArgumentError, "Words must be at most 10" if words > 10

  selected_words = Wordlist.random_words(words)
  
  # Capitalize if requested
  selected_words = selected_words.map(&:capitalize) if capitalize
  
  # Join with separator
  passphrase = selected_words.join(separator)
  
  # Add random number if requested
  passphrase += "#{separator}#{SecureRandom.random_number(100)}" if numbers
  
  passphrase
end