passw 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/passw +76 -6
  3. data/lib/passw.rb +96 -44
  4. metadata +19 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3141c86801808c3cc779ddfc545e1d1421447894e5e689b1dd9ac2ef9d9554c8
4
- data.tar.gz: ee4c7f89f378859666dd9c088eadef2c37db8aba9fe448d45819969fe88718ec
3
+ metadata.gz: 861aa536ac1b2f8f9f6962d996b1f5a9e8cef01a81d3a74b81c35bbe1c1efab2
4
+ data.tar.gz: 73b49a46d9c1d4945cace67566be4966416d9b96baec77fbf847aad0132e1210
5
5
  SHA512:
6
- metadata.gz: dac19c051d61d46c09ca31f86da80849edac4b8236eb048de36b1d151cd4352fb096fbd686b3e8016b3751676b2938d79f686b032c93e2967c46c0d2b78d72e9
7
- data.tar.gz: 6316b7adf6dc0d6e31c40323bdbe81a829788a12c5e560bca5fdb3c45f54f05151f75fd7f0bfc35a107c7ef5f34fc3089770be1c2660ba0e6ca04f73bf346985
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>"
10
- end
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
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passw
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
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: 2020-07-11 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
@@ -16,36 +16,30 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.7'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 5.7.0
19
+ version: '5.25'
23
20
  type: :development
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '5.7'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 5.7.0
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
@@ -56,9 +50,11 @@ files:
56
50
  - lib/passw.rb
57
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.1.4
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: []