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.
- checksums.yaml +4 -4
- data/bin/passw +75 -5
- data/lib/passw.rb +96 -44
- metadata +22 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 861aa536ac1b2f8f9f6962d996b1f5a9e8cef01a81d3a74b81c35bbe1c1efab2
|
4
|
+
data.tar.gz: 73b49a46d9c1d4945cace67566be4966416d9b96baec77fbf847aad0132e1210
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
5
|
+
require_relative '../lib/passw'
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
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:
|
9
|
-
uppercase:
|
10
|
-
symbols:
|
11
|
-
numbers:
|
12
|
-
duplicates:
|
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
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
-
|
59
|
-
end
|
110
|
+
('a'..'z').to_a
|
111
|
+
end
|
60
112
|
|
61
113
|
def self.uppercase
|
62
|
-
|
63
|
-
end
|
114
|
+
('A'..'Z').to_a
|
115
|
+
end
|
64
116
|
|
65
117
|
def self.numbers
|
66
|
-
|
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.
|
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:
|
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.
|
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.
|
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:
|
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:
|
47
|
-
description: Passw is a
|
48
|
-
|
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/
|
51
|
+
homepage: https://github.com/sn/passw
|
58
52
|
licenses:
|
59
|
-
- GPL-3.0
|
60
|
-
metadata:
|
61
|
-
|
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: '
|
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.
|
77
|
-
signing_key:
|
72
|
+
rubygems_version: 3.5.22
|
73
|
+
signing_key:
|
78
74
|
specification_version: 4
|
79
|
-
summary:
|
75
|
+
summary: Customizable Ruby library for secure password generation with character and
|
76
|
+
strength options.
|
80
77
|
test_files: []
|