[Ruby] Command-line Password Generator

Posted by Justin C. on December 19, 2019 · 10 mins read Category: Programming Tags: Powershell

A Ruby script for generating sequential passwords to pipe into other tools. Supports command-line parameters to customize the password length range, character set, and password seed. Generated passwords are output to stdout.

> generator.rb -h
Password Generator
Usage: generator.rb [options]
 -v, –verbose Output more information
 -l, –lowercase Include lowercase letters in seeds
 -u, –uppercase Include uppercase letters in seeds
 -n, –numbers Include numbers in seeds
 -s, –special Include special characters in seeds
 -a, –all Include all characters in seeds
 -m, –min NUMBER Set minimum seed length(>0)
 -M, –max NUMBER Set maximum seed length(>minimum)
 -w, –wifi Set min and max to acceptable wifi seed length
 -S, –seed SEED Set starting seed string
 -h, –help Display this screen

While running, press spacebar to output generator stats to stderr.

Console Output

Source

#!/usr/bin/env ruby
require 'optparse'
require 'io/wait'

$interrupted = false

trap("INT") { $interrupted = true}
options = {}
optparse = OptionParser.new do |opts|
  opts.banner = "Password Generator\nUsage: generator.rb [options]"

  #Defines the command line options
  options[:verbose] = false
  opts.on('-v','--verbose', 'Output more information') do
    options[:verbose] = true
  end
  options[:lowercase] = false
  opts.on('-l', '--lowercase', 'Include lowercase letters in seeds') do
    options[:lowercase] = true
  end
  options[:uppercase] = false
  opts.on('-u', '--uppercase', 'Include uppercase letters in seeds') do
    options[:uppercase] = true
  end
  options[:numbers] = false
  opts.on('-n', '--numbers', 'Include numbers in seeds') do
    options[:numbers] = true
  end
  options[:special] = false
  opts.on('-s', '--special', 'Include special characters in seeds') do
    options[:special] = true
  end
  options[:all] = false
  opts.on('-a', '--all', 'Include all characters in seeds') do
    options[:all] = true
    options[:lowercase] = true
    options[:uppercase] = true
    options[:numbers] = true
    options[:special] = true
  end
  options[:min] = 1
  opts.on('-m NUMBER', '--min NUMBER', 'Set minimum seed length(>0)') do |i|
    if i.to_i > 0
      options[:min] = i.to_i
    else
      puts "Invalid Mimimum, must be greater than 0."
      exit
    end
  end
  options[:max] = 10
  opts.on('-M NUMBER', '--max NUMBER', 'Set maximum seed length(>minimum)') do |i|
    if i.to_i >= options[:min]
	options[:max] = i.to_i
    else
      puts "Invalid Maximum, must be greater than or equal to the minimum."
      exit
    end
  end
  options[:wifi] = false
  opts.on('-w', '--wifi', 'Set min and max to acceptable wifi seed length') do
    options[:min] = 8
    options[:max] = 63
  end

  options[:seed] = ""
  opts.on('-S SEED', '--seed SEED', 'Set starting seed string') do |i|
    options[:seed] = i
    puts "Start seed: " + options[:seed]
  end

  opts.on('-h', '--help', 'Display this screen' ) do
    puts opts
    exit
  end


end
optparse.parse!

#lowercase = "abcdefghijklmnopqrstuvwxyz"
lowercase = "etaoinsrhdlucmfywgpbvkxqjz"
#uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
uppercase = "ETAOINSRHDLUCMFYWGPBVKXQJZ"
numbers = "1234567890"
special = "\-\=\`\~\!\@\#$\%\^\&\*\(\)\_\+\,\.\/\<\>\?\;\'\:\"\[\]\{\}\\"
total = 0
$seeder = ""
$seeder = lowercase if options[:lowercase] == true
$seeder += uppercase if options[:uppercase]
$seeder += numbers if options[:numbers]
$seeder += special if options[:special]
$start_seed =  (options[:seed].length >0 ? options[:seed] : "")
0.upto($start_seed.length-1) {|i|
  if $seeder.index($start_seed[i]).nil?
    puts "Seed character (" + $start_seed[i].chr + ")  not in character set, please specify additional character sets."
    exit
  end
                             }

$seeder_length = $seeder.length
#puts $seeder
if $seeder_length == 0
  puts "Must specify characters to use. See --help for available options."
  exit
end
#puts $seeder
$start_time = Time.new
$max_length = options[:max]
$current_length = options[:min]
$current_slot = 0
$candidates = []
$password = ""
$total = 0

def initialize_candidates
  0.upto($max_length-1) {|i| $candidates[i] = 0}
  if $start_seed.length > 0
    0.upto($start_seed.length-1) {|i| $candidates[i] = $seeder.index($start_seed[i])}
  end
end

def increment_letter
	$candidates[$current_slot] += 1

	if $candidates[$current_slot] >= $seeder_length && $current_slot > 0
		$candidates[$current_slot] = 0
		$current_slot -= 1
		increment_letter
	end

end

def input?
  begin
    c = $stdin.getc
    return true if c == "q"
    show_stats if c == " "
    false
    rescue Errno::EINTR
      false
    rescue Errno::EAGAIN
      false
    rescue EOFError
      true
  end
end

def show_stats
    $stderr.puts "Generator Stats - words: " + $total.to_s + " current: " + $password + " w/s: " + ($total/(Time.now - $start_time)).round.to_s + " run time: " + (Time.new - $start_time).to_i.to_s + " second(s)"#Time.new(Time.now - $start_time).strftime("%T")

end

def exiting
  show_stats
  $stderr.puts "Exiting...                                                                   "
  exit
end

#system("stty raw -echo")
initialize_candidates
#Curses.noecho

while $current_length <= $max_length
  $password = ""
  $current_slot = $current_length-1
  0.upto($current_length-1) {|i| $password = $password + $seeder[$candidates[i]].chr}
  $stdout.puts $password
  $total += 1
  increment_letter
  if $candidates[0] >= $seeder_length
    $current_length += 1
    initialize_candidates
  end
  exiting if input? if $stdin.ready?#input?
  exiting if $interrupted == true
end