passw 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/passw +75 -5
  3. data/lib/passw.rb +96 -44
  4. metadata +22 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a51d723f38d972ec59f524cd443744a970ba525db372f04fa037d9f2c009577d
4
- data.tar.gz: 425ad269230eda5e40e3819a21641713cf5df0a09309153ffb82d54558d42479
3
+ metadata.gz: 861aa536ac1b2f8f9f6962d996b1f5a9e8cef01a81d3a74b81c35bbe1c1efab2
4
+ data.tar.gz: 73b49a46d9c1d4945cace67566be4966416d9b96baec77fbf847aad0132e1210
5
5
  SHA512:
6
- metadata.gz: e479081a2678a9c77566bcf6dc4e3ffb41cb91405398ea8aa0b03e009ca23c04e67bca69a01f685b4229d7ef561f1406a1e1e4e8094e4181a97f5db8fdd1c458
7
- data.tar.gz: 48dbe7a4ea40336ef665c376f61e4923bb80fd3ba0a410dee13b3f83f9fb5f749a68cf98dcf557d367d54973c81bdc8c4794374ca14d95d09b606509295753b1
6
+ metadata.gz: 9212c895b7a18ebc4bf1c4058733060c385b7b589004c0ae6905f4c4906f842ddd763be4ceae279872c6838e05bb604f89d7ff4199643b3b3861c0c62a6cd3e9
7
+ data.tar.gz: d46c2ea84faba50fc396f9ffb293d38f311140fe0cfd9a2bdbe95ed4564e0685114273c6ea275bbf9ea38a359cf78a9b6d3f10374840a6d4ebc338cbfab423b6
data/bin/passw CHANGED
@@ -1,10 +1,80 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'optparse'
3
4
  require 'rubygems'
4
- require File.expand_path('../lib/passw', File.dirname(__FILE__))
5
+ require_relative '../lib/passw'
5
6
 
6
- if !ARGV.empty?
7
- puts Passw.generate(ARGV[0])
8
- else
9
- puts "Passw Usage: passw <length>"
7
+ # Default options
8
+ options = {
9
+ lowercase: true,
10
+ uppercase: true,
11
+ symbols: true,
12
+ numbers: true,
13
+ duplicates: true,
14
+ enforce_types: true,
15
+ avoid_sequences: false,
16
+ exclude: [],
17
+ min_length: 8
18
+ }
19
+
20
+ # Define and parse options
21
+ OptionParser.new do |opts|
22
+ opts.banner = "Usage: passw <length> [options]"
23
+
24
+ opts.on("-l", "--lowercase", "Include lowercase letters (default: true)") do
25
+ options[:lowercase] = true
26
+ end
27
+
28
+ opts.on("-u", "--uppercase", "Include uppercase letters (default: true)") do
29
+ options[:uppercase] = true
30
+ end
31
+
32
+ opts.on("-s", "--symbols", "Include symbols (default: true)") do
33
+ options[:symbols] = true
34
+ end
35
+
36
+ opts.on("-n", "--numbers", "Include numbers (default: true)") do
37
+ options[:numbers] = true
38
+ end
39
+
40
+ opts.on("-d", "--no-duplicates", "Disallow duplicate characters") do
41
+ options[:duplicates] = false
42
+ end
43
+
44
+ opts.on("-e", "--enforce-types", "Ensure at least one of each selected type (default: true)") do
45
+ options[:enforce_types] = true
46
+ end
47
+
48
+ opts.on("-a", "--avoid-sequences", "Avoid sequential characters") do
49
+ options[:avoid_sequences] = true
50
+ end
51
+
52
+ opts.on("-x", "--exclude CHARS", "Exclude specific characters (comma-separated, e.g., 'O,0,I,l')") do |chars|
53
+ options[:exclude] = chars.split(',')
54
+ end
55
+
56
+ opts.on("-m", "--min-length LENGTH", Integer, "Set minimum password length (default: 8)") do |min_length|
57
+ options[:min_length] = min_length
58
+ end
59
+
60
+ opts.on("-h", "--help", "Prints this help") do
61
+ puts opts
62
+ exit
63
+ end
64
+ end.parse!
65
+
66
+ # Ensure length is provided and valid
67
+ if ARGV.empty?
68
+ puts "Error: You must specify a password length."
69
+ puts "Usage: passw <length> [options]"
70
+ exit
10
71
  end
72
+
73
+ length = ARGV[0].to_i
74
+ if length <= 0
75
+ puts "Error: Please enter a valid positive integer for length."
76
+ exit
77
+ end
78
+
79
+ # Generate and print the password
80
+ puts Passw.generate(length, options)
data/lib/passw.rb CHANGED
@@ -1,68 +1,120 @@
1
1
  module Passw
2
- # Generate a password with the sepecified options
2
+ # Generate a password with specified options
3
3
  # Params:
4
4
  # +length+:: the length of the password
5
5
  # +options+:: a hash defining the attributes for the password
6
6
  def self.generate(length, options = {})
7
7
  defaults = {
8
- lowercase: true, # Allow lower case characters
9
- uppercase: true, # Allow uppercase characters
10
- symbols: true, # Allow symbols
11
- numbers: true, # Allow numbers
12
- duplicates: true # Allow characters to be duplicated (less secure if true)
8
+ lowercase: true, # Allow lowercase characters
9
+ uppercase: true, # Allow uppercase characters
10
+ symbols: true, # Allow symbols
11
+ numbers: true, # Allow numbers
12
+ duplicates: true, # Allow duplicates
13
+ enforce_types: true, # Ensure at least one of each selected character type
14
+ avoid_sequences: true, # Avoid sequential/repeating characters
15
+ exclude: [], # Characters to exclude from password
16
+ min_length: 8 # Minimum password length
13
17
  }
14
-
15
- defaults.merge!(options)
16
-
17
- buffer = []
18
-
19
- buffer += lowercase if defaults[:lowercase]
20
- buffer += uppercase if defaults[:uppercase]
21
- buffer += symbols if defaults[:symbols]
22
- buffer += numbers if defaults[:numbers]
23
-
24
- base = []
25
-
26
- buffer_length = buffer.length
27
-
28
- (0...length.to_i).each do |i|
29
- if defaults[:duplicates]
30
- base << buffer[srand % buffer_length]
18
+
19
+ # Merge user options with defaults
20
+ settings = defaults.merge(options)
21
+
22
+ # Enforce minimum length
23
+ length = [length.to_i, settings[:min_length]].max
24
+
25
+ # Build character set based on options
26
+ character_set = build_character_set(settings)
27
+ return '' if character_set.empty?
28
+
29
+ # Filter out excluded characters
30
+ character_set -= settings[:exclude]
31
+
32
+ # Generate the password with necessary character types enforced
33
+ password = generate_password(character_set, length, settings)
34
+
35
+ # Calculate and display password entropy
36
+ entropy = calculate_entropy(character_set.size, length)
37
+
38
+ password.shuffle.join
39
+ end
40
+
41
+ private
42
+
43
+ # Build the character set based on the specified options
44
+ def self.build_character_set(settings)
45
+ character_set = []
46
+ character_set += lowercase if settings[:lowercase]
47
+ character_set += uppercase if settings[:uppercase]
48
+ character_set += symbols if settings[:symbols]
49
+ character_set += numbers if settings[:numbers]
50
+ character_set
51
+ end
52
+
53
+ # Generate the password based on options
54
+ def self.generate_password(character_set, length, settings)
55
+ password = []
56
+
57
+ # Ensure at least one character from each type if enforce_types is enabled
58
+ if settings[:enforce_types]
59
+ password << lowercase.sample if settings[:lowercase]
60
+ password << uppercase.sample if settings[:uppercase]
61
+ password << symbols.sample if settings[:symbols]
62
+ password << numbers.sample if settings[:numbers]
63
+ end
64
+
65
+ # Fill the rest of the password
66
+ while password.length < length
67
+ candidate = character_set.sample
68
+
69
+ if settings[:duplicates]
70
+ password << candidate
31
71
  else
32
- loop do
33
- candidate = buffer[srand % buffer_length]
34
-
35
- if !base.include? candidate
36
- base << candidate
37
- break
38
- end
39
-
40
- # Ensure that this loop does not run forever if duplicates are disallowed
41
- # In this case, we're limited to the collective size of buffered characters
42
-
43
- break if base.length == buffer_length - 1
72
+ # Avoid duplicates if duplicates option is false
73
+ next if password.include?(candidate)
74
+ password << candidate
75
+ end
76
+
77
+ # Avoid sequences/repeating characters if avoid_sequences is true
78
+ if settings[:avoid_sequences] && password.size > 1
79
+ next_char = password[-1]
80
+ prev_char = password[-2]
81
+ if next_char.ord == prev_char.ord + 1 || next_char.ord == prev_char.ord - 1
82
+ password.pop
44
83
  end
45
84
  end
46
85
  end
47
-
48
- base.shuffle.join
86
+ password
49
87
  end
50
88
 
51
- private
89
+ # Entropy calculation
90
+ def self.calculate_entropy(charset_size, length)
91
+ (Math.log2(charset_size) * length).round(2)
92
+ end
93
+
94
+ # Assess password strength based on entropy value
95
+ def self.password_strength(entropy)
96
+ case entropy
97
+ when 0..27 then "Very Weak"
98
+ when 28..35 then "Weak"
99
+ when 36..59 then "Reasonable"
100
+ when 60..127 then "Strong"
101
+ else "Very Strong"
102
+ end
103
+ end
52
104
 
53
105
  def self.symbols
54
106
  %w[! " ' # $ % & ( ) * + , - . / : ; < = > ? ` ~ { | } @ ^]
55
107
  end
56
108
 
57
109
  def self.lowercase
58
- %w[a b c d e f g h i j k l m n o p q r s t u v w x y z]
59
- end
110
+ ('a'..'z').to_a
111
+ end
60
112
 
61
113
  def self.uppercase
62
- %w[A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
63
- end
114
+ ('A'..'Z').to_a
115
+ end
64
116
 
65
117
  def self.numbers
66
- %w[0 1 2 3 4 5 6 7 8 9]
118
+ ('0'..'9').to_a
67
119
  end
68
- end
120
+ end
metadata CHANGED
@@ -1,51 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passw
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Nieuwoudt
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-06 00:00:00.000000000 Z
11
+ date: 2024-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 5.7.0
20
17
  - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: '5.7'
19
+ version: '5.25'
23
20
  type: :development
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 5.7.0
30
24
  - - "~>"
31
25
  - !ruby/object:Gem::Version
32
- version: '5.7'
26
+ version: '5.25'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: rake
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - ">="
31
+ - - "~>"
38
32
  - !ruby/object:Gem::Version
39
- version: '13.0'
33
+ version: 13.2.1
40
34
  type: :development
41
35
  prerelease: false
42
36
  version_requirements: !ruby/object:Gem::Requirement
43
37
  requirements:
44
- - - ">="
38
+ - - "~>"
45
39
  - !ruby/object:Gem::Version
46
- version: '13.0'
47
- description: Passw is a simple, customizable password generator for Ruby that allows
48
- you to generate secure passwords easily
40
+ version: 13.2.1
41
+ description: Passw is a Ruby library for generating secure passwords, supporting length,
42
+ character types, exclusions, and entropy-based strength assessment.
49
43
  email: sean@isean.co.za
50
44
  executables:
51
45
  - passw
@@ -54,11 +48,13 @@ extra_rdoc_files: []
54
48
  files:
55
49
  - bin/passw
56
50
  - lib/passw.rb
57
- homepage: https://github.com/SeanNieuwoudt/passw
51
+ homepage: https://github.com/sn/passw
58
52
  licenses:
59
- - GPL-3.0
60
- metadata: {}
61
- post_install_message:
53
+ - GPL-3.0-or-later
54
+ metadata:
55
+ changelog_uri: https://github.com/sn/passw/blob/main/CHANGELOG.md
56
+ documentation_uri: https://github.com/sn/passw#readme
57
+ post_install_message:
62
58
  rdoc_options: []
63
59
  require_paths:
64
60
  - lib
@@ -66,15 +62,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
66
62
  requirements:
67
63
  - - ">="
68
64
  - !ruby/object:Gem::Version
69
- version: '0'
65
+ version: '2.7'
70
66
  required_rubygems_version: !ruby/object:Gem::Requirement
71
67
  requirements:
72
68
  - - ">="
73
69
  - !ruby/object:Gem::Version
74
70
  version: '0'
75
71
  requirements: []
76
- rubygems_version: 3.0.1
77
- signing_key:
72
+ rubygems_version: 3.5.22
73
+ signing_key:
78
74
  specification_version: 4
79
- summary: Passw is a simple, customizable password generator for Ruby
75
+ summary: Customizable Ruby library for secure password generation with character and
76
+ strength options.
80
77
  test_files: []