easy-password 0.1
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 +7 -0
- data/README.md +52 -0
- data/easy-password.gemspec +28 -0
- data/lib/easy-password.rb +316 -0
- data/lib/easy-password/version.rb +5 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 22d9fd1676c83cd88d002fbc80e9f99727e524c704027a2d4d37078c17c5234d
|
4
|
+
data.tar.gz: 325356f44444034a31212ee79b0e2e92bb7392314351df8e4b78985baa09acb9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 67ffa6ec83d80b16bbcbddb2f649fe183b97d2753e018c35deb3a2fd27f6652af2cb191fa26282fde1c1020a97cdf5a6f995c7d1d70323ecbb9d7c8ead00b21c
|
7
|
+
data.tar.gz: 5763ce1f321402431fea4169d1bbb351adc6e33223eb1d2744970fd94aa73762b8dcd0e0b78114a939958847c80d5f8895ea775efb85d0c1f6d613102bbf8031
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
easy-password
|
2
|
+
=============
|
3
|
+
Password generator, checker, hasher
|
4
|
+
|
5
|
+
|
6
|
+
Examples
|
7
|
+
========
|
8
|
+
|
9
|
+
Adding a simple password generator and using it by default:
|
10
|
+
|
11
|
+
~~~ruby
|
12
|
+
# Generate 10 random alphanumeric characters
|
13
|
+
EasyPassword.generator :random10 do
|
14
|
+
SecureRandom.alphanumeric(10)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Define the default generator
|
18
|
+
EasyPassword.default_generator = :random10
|
19
|
+
~~~
|
20
|
+
|
21
|
+
|
22
|
+
Adding a checker
|
23
|
+
|
24
|
+
~~~ruby
|
25
|
+
# Implementing classic at least 1 lowercase, 1 upercase, 1 digit
|
26
|
+
EasyPassword.checker :aA1 do |password, all|
|
27
|
+
list = { /\d/ => :digit_needed,
|
28
|
+
/[A-Z]/ => :upercase_needed,
|
29
|
+
/[a-z]/ => :lowercase_needed,
|
30
|
+
}.lazy.map {|regex, failure| failure if password !~ regex }
|
31
|
+
.reject(&:nil?)
|
32
|
+
all ? list.to_a : list.first
|
33
|
+
end
|
34
|
+
|
35
|
+
# Looking for known bad passwords in a database (using Sequel)
|
36
|
+
Password.checker :hack_dictionary do |password, all|
|
37
|
+
! DB[:bad_passwords].first(:password => password).nil?
|
38
|
+
end
|
39
|
+
~~~
|
40
|
+
|
41
|
+
Creating password
|
42
|
+
|
43
|
+
~~~ruby
|
44
|
+
password = EasyPassword.new
|
45
|
+
password = EasyPassword.new('foobar')
|
46
|
+
~~~
|
47
|
+
|
48
|
+
Checking for weakness
|
49
|
+
|
50
|
+
~~~ruby
|
51
|
+
password.weakness
|
52
|
+
~~~
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'lib/easy-password/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'easy-password'
|
7
|
+
s.version = EasyPassword::VERSION
|
8
|
+
s.summary = "Password generator, checker, hasher"
|
9
|
+
s.description = <<~EOF
|
10
|
+
Ease password creation by allowing:
|
11
|
+
* password generation
|
12
|
+
* password weakness checking
|
13
|
+
* hashing password to sha256, md5, sha, ntlm, lmhash
|
14
|
+
EOF
|
15
|
+
|
16
|
+
s.homepage = 'https://gitlab.com/sdalu/easy-password'
|
17
|
+
s.license = 'MIT'
|
18
|
+
|
19
|
+
s.authors = [ "Stéphane D'Alu" ]
|
20
|
+
s.email = [ 'stephane.dalu@insa-lyon.fr' ]
|
21
|
+
|
22
|
+
s.files = %w[ README.md easy-password.gemspec ] +
|
23
|
+
Dir['lib/**/*.rb']
|
24
|
+
|
25
|
+
s.add_development_dependency 'yard', '~>0'
|
26
|
+
s.add_development_dependency 'redcarpet', '~>3'
|
27
|
+
s.add_development_dependency 'rake', '~>13'
|
28
|
+
end
|
@@ -0,0 +1,316 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Adding a simple password generator and using it by default:
|
5
|
+
#
|
6
|
+
# # Generate 10 random alphanumeric characters
|
7
|
+
# EasyPassword.generator :random10 do
|
8
|
+
# SecureRandom.alphanumeric(10)
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# # Define the default generator
|
12
|
+
# EasyPassword.default_generator = :random10
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# Adding a checker
|
16
|
+
#
|
17
|
+
# # Implementing classic at least 1 lowercase, 1 upercase, 1 digit
|
18
|
+
# EasyPassword.checker :aA1 do |password, all|
|
19
|
+
# list = { /\d/ => :digit_needed,
|
20
|
+
# /[A-Z]/ => :upercase_needed,
|
21
|
+
# /[a-z]/ => :lowercase_needed,
|
22
|
+
# }.lazy.map {|regex, failure| failure if password !~ regex }
|
23
|
+
# .reject(&:nil?)
|
24
|
+
# all ? list.to_a : list.first
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # Looking for known bad passwords in a database (using Sequel)
|
28
|
+
# Password.checker :hack_dictionary do |password, all|
|
29
|
+
# ! DB[:bad_passwords].first(:password => password).nil?
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Creating password
|
33
|
+
#
|
34
|
+
# password = EasyPassword.new
|
35
|
+
# password = EasyPassword.new('foobar')
|
36
|
+
#
|
37
|
+
# Checking for weakness
|
38
|
+
#
|
39
|
+
# password.weakness
|
40
|
+
#
|
41
|
+
|
42
|
+
class EasyPassword
|
43
|
+
Digest = OpenSSL::Digest
|
44
|
+
|
45
|
+
@hide = true
|
46
|
+
@checkers = {}
|
47
|
+
@generators = {}
|
48
|
+
@default_generator = nil
|
49
|
+
@default_checkers = nil
|
50
|
+
|
51
|
+
|
52
|
+
# Is password value hidden when calling #to_s
|
53
|
+
def self.hide
|
54
|
+
@hide
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# Control if password value is hidden when calling #to_s
|
59
|
+
def self.hide=(hide)
|
60
|
+
@hide = hide
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Define the default generator to use
|
65
|
+
#
|
66
|
+
# @param [Symbol] type Generator nickname
|
67
|
+
#
|
68
|
+
def self.default_generator=(type)
|
69
|
+
@default_generator = type
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Default generator
|
74
|
+
def self.default_generator
|
75
|
+
@default_generator
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# Define the list of default checkers to use
|
80
|
+
#
|
81
|
+
# @param [Array<Symbol>,nil] checkers list of checkers nickname, if nil
|
82
|
+
# all available checkers will be used
|
83
|
+
#
|
84
|
+
def self.default_checkers=(checkers)
|
85
|
+
@default_checkers = checkers
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# List of default checkers to use
|
90
|
+
def self.default_checkers
|
91
|
+
@default_checkers
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
# DSL to add a new password generator
|
96
|
+
#
|
97
|
+
# @yieldparam [void]
|
98
|
+
# @yieldreturn [String] plain text password
|
99
|
+
#
|
100
|
+
def self.generator(name, &block)
|
101
|
+
@generators[name] = block
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# DSL to ass a new password checker
|
106
|
+
#
|
107
|
+
# @yieldparam [String] password plain text password
|
108
|
+
# @yieldparam [Boolean] all should all weakness be listed
|
109
|
+
# @yieldreturn [false, nil, []] if no weakness have been discovered
|
110
|
+
# @yieldreturn [true, Symbol, Array<Symbol>] name of weakness
|
111
|
+
#
|
112
|
+
def self.checker(name, &block)
|
113
|
+
@checkers[name] ||= block
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Check for weakness
|
118
|
+
#
|
119
|
+
# @param [String, EasyPassword] password
|
120
|
+
# @param [Symbol] checkers
|
121
|
+
# @param [Boolean] all
|
122
|
+
#
|
123
|
+
# @raise [KeyError] if a requested checker is not defined
|
124
|
+
#
|
125
|
+
# @return [Hash{Symbol=>Array<Symbol>}]
|
126
|
+
#
|
127
|
+
def self.weakness(password, *checkers, all: true)
|
128
|
+
return nil if @checkers.empty?
|
129
|
+
password = password.raw if password.kind_of?(EasyPassword)
|
130
|
+
|
131
|
+
checkers = self.default_checkers if checkers.empty?
|
132
|
+
list = if checkers.nil? || checkers.empty?
|
133
|
+
then @checkers.lazy
|
134
|
+
else checkers.lazy.map {|n| [n, @checkers.fetch(n)] }
|
135
|
+
end
|
136
|
+
list = list.map {|name, checker|
|
137
|
+
case r = checker.call(password, all: all)
|
138
|
+
when Array then [ name, r ] unless r.empty?
|
139
|
+
when Symbol then [ name, [ r ] ]
|
140
|
+
when true then [ name, [ name ] ]
|
141
|
+
when nil, false
|
142
|
+
else raise ArgumentError, 'unsupported checker return value'
|
143
|
+
end
|
144
|
+
}.reject(&:nil?)
|
145
|
+
|
146
|
+
list = if all
|
147
|
+
then Hash[list.to_a]
|
148
|
+
else Hash[*list.first].transform_values {|v| v[0,1] }
|
149
|
+
end
|
150
|
+
list unless list.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Generate a plain text password string.
|
155
|
+
#
|
156
|
+
# @param [Symbol] type Generator nickname
|
157
|
+
# @return [String]
|
158
|
+
#
|
159
|
+
def self.generate(type = self.default_generator)
|
160
|
+
if type.nil?
|
161
|
+
raise ArgumentError, 'invalid generator type'
|
162
|
+
end
|
163
|
+
|
164
|
+
@generators[type]&.call() ||
|
165
|
+
raise("requested generator '#{type}' doesn't exist")
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# Create a MD5-hashed password
|
170
|
+
#
|
171
|
+
# @param [String] password plain text password
|
172
|
+
#
|
173
|
+
# @return [String] hashed password
|
174
|
+
#
|
175
|
+
def self.md5(password)
|
176
|
+
"{MD5}" + [Digest::MD5.digest(password) ].pack('m0')
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
# Create a SHA-hashed password
|
181
|
+
#
|
182
|
+
# @param [String] password plain text password
|
183
|
+
#
|
184
|
+
# @return [String] hashed password
|
185
|
+
#
|
186
|
+
def self.sha(password)
|
187
|
+
"{SHA}" + [Digest::SHA1.digest(password) ].pack('m0')
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# Create a SHA256-hashed password
|
192
|
+
#
|
193
|
+
# @param [String] password plain text password
|
194
|
+
#
|
195
|
+
# @return [String] hashed password
|
196
|
+
#
|
197
|
+
def self.sha256(password)
|
198
|
+
"{sha256}" + [Digest::SHA256.digest(password)].pack('m0')
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
# Create an NTML-hashed password
|
203
|
+
#
|
204
|
+
# @param [String] password plain text password
|
205
|
+
#
|
206
|
+
# @return [String] hashed password
|
207
|
+
#
|
208
|
+
def self.ntlm(password)
|
209
|
+
Digest::MD4.hexdigest(password.encode("utf-16le"))
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
# Create a LMHASH-hashed password
|
214
|
+
#
|
215
|
+
# @param [String] password plain text password
|
216
|
+
#
|
217
|
+
# @return [String] hashed password
|
218
|
+
#
|
219
|
+
def self.lmhash(password)
|
220
|
+
passwd = password[0..13].upcase
|
221
|
+
passwd = passwd + "\000" * (14 - passwd.length)
|
222
|
+
des = OpenSSL::Cipher::Cipher.new('des-ecb')
|
223
|
+
des.encrypt
|
224
|
+
[passwd[0..6], passwd[7..13]].collect { |key56|
|
225
|
+
keybin = key56.unpack('B*')[0].scan(/.{7}/).collect {|k|
|
226
|
+
k + (k.count('1') % 2 == 0 ? '1' : '0') }
|
227
|
+
des.key = keybin.pack('B8' * 8)
|
228
|
+
des.update('KGS!@#$%')
|
229
|
+
}.join.unpack('C*').map { |b| '%02x' % b }.join
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Create a new EasyPassword
|
234
|
+
def initialize(password = EasyPassword::generate)
|
235
|
+
@passwd = password.clone.freeze
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
# Get the plain text password
|
240
|
+
#
|
241
|
+
# @return [String] plain text password
|
242
|
+
def raw
|
243
|
+
@passwd
|
244
|
+
end
|
245
|
+
|
246
|
+
|
247
|
+
# Get the SHA256-hashed password
|
248
|
+
#
|
249
|
+
# @return [String] hashed password
|
250
|
+
#
|
251
|
+
def sha
|
252
|
+
self.class.sha(@passwd)
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
# Get the SHA256-hashed password
|
257
|
+
#
|
258
|
+
# @return [String] hashed password
|
259
|
+
#
|
260
|
+
def sha256
|
261
|
+
self.class.sha256(@passwd)
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
# Get the MD5-hashed password
|
266
|
+
#
|
267
|
+
# @return [String] hashed password
|
268
|
+
#
|
269
|
+
def md5
|
270
|
+
self.class.md5(@passwd)
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
# Get the NTLM-hashed password
|
275
|
+
#
|
276
|
+
# @return [String] hashed password
|
277
|
+
#
|
278
|
+
def ntlm
|
279
|
+
self.class.ntlm(@passwd)
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
# Get the LMHASH-hashed password
|
284
|
+
#
|
285
|
+
# @return [String] hashed password
|
286
|
+
#
|
287
|
+
def lmhash
|
288
|
+
self.class.lmhash(@passwd)
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
# Display password.
|
293
|
+
# The behavior is controlled by Password.hide, so either
|
294
|
+
# the plain text password will be displayed or ********
|
295
|
+
#
|
296
|
+
# @return [String]
|
297
|
+
#
|
298
|
+
def to_s
|
299
|
+
self.class.hide != true ? "********" : self.raw
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
# Check for weakness
|
304
|
+
#
|
305
|
+
# @param [Symbol] checkers
|
306
|
+
# @param [Boolean] all
|
307
|
+
#
|
308
|
+
# @raise [KeyError] if a requested checker is not defined
|
309
|
+
#
|
310
|
+
# @return [Hash{Symbol=>Array<Symbol>}]
|
311
|
+
#
|
312
|
+
def weakness(*checkers, all: true)
|
313
|
+
self.class.weakness(@passwd, *checkers, all: all)
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy-password
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stéphane D'Alu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redcarpet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13'
|
55
|
+
description: |
|
56
|
+
Ease password creation by allowing:
|
57
|
+
* password generation
|
58
|
+
* password weakness checking
|
59
|
+
* hashing password to sha256, md5, sha, ntlm, lmhash
|
60
|
+
email:
|
61
|
+
- stephane.dalu@insa-lyon.fr
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- README.md
|
67
|
+
- easy-password.gemspec
|
68
|
+
- lib/easy-password.rb
|
69
|
+
- lib/easy-password/version.rb
|
70
|
+
homepage: https://gitlab.com/sdalu/easy-password
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubygems_version: 3.0.8
|
90
|
+
signing_key:
|
91
|
+
specification_version: 4
|
92
|
+
summary: Password generator, checker, hasher
|
93
|
+
test_files: []
|