wikk_password 0.1.2 → 0.1.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 +5 -5
- data/History.txt +36 -0
- data/Manifest.txt +5 -0
- data/README.md +4 -4
- data/Rakefile +16 -17
- data/lib/unix-crypt/lib/base.rb +63 -0
- data/lib/unix-crypt/lib/des.rb +19 -0
- data/lib/unix-crypt/lib/md5.rb +52 -0
- data/lib/unix-crypt/lib/sha.rb +98 -0
- data/lib/unix-crypt/unix_crypt.rb +45 -0
- data/lib/wikk_password.rb +128 -119
- metadata +22 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: da2a0031e8433f4f21bcd5e95d2cba7af04cfc877f19f0b22c7ab3db5b92d170
|
|
4
|
+
data.tar.gz: cf9519efb1c8dd06c978a934cc020238df5a0e51ba7286646abe24e3728ffbf7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b00e403e9a2a84904cecee98832134767d05c5f713e64c861a5bde300955e92d437acd279e7318c8edac4afe4a5748f0fda9d8272371a8bdb93dd1d3024306b
|
|
7
|
+
data.tar.gz: dad6e0d5c5dc5c9caa3d12aa2b27f69d1eaa8b2c43fd831c5b8971b5fbed2f9632294c95870c62ce6cc4f2b2e2c500f2a12205abad3a26b68735645c651d6515
|
data/History.txt
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
robertburrowes Sun Jun 5 18:39:33 2022 +1200
|
|
2
|
+
#{PROJECT} release 0.1.3
|
|
3
|
+
robertburrowes Sun Jun 5 18:35:04 2022 +1200
|
|
4
|
+
typo
|
|
5
|
+
robertburrowes Sun Jun 5 18:33:40 2022 +1200
|
|
6
|
+
unix-crypt gem temp fix
|
|
7
|
+
robertburrowes Sun Jun 5 18:29:56 2022 +1200
|
|
8
|
+
Test against the library, not the installed gem.
|
|
9
|
+
robertburrowes Sun Jun 5 18:29:33 2022 +1200
|
|
10
|
+
typo
|
|
11
|
+
robertburrowes Sun Jun 5 18:09:38 2022 +1200
|
|
12
|
+
Included Roger Nesbitt's unix-crypt directly, as his gem has gone. Will fix later
|
|
13
|
+
robertburrowes Sun Oct 25 21:47:30 2020 +1300
|
|
14
|
+
Tidy up yard comments
|
|
15
|
+
robertburrowes Sun Oct 25 21:07:20 2020 +1300
|
|
16
|
+
reformated for Hoe
|
|
17
|
+
robertburrowes Sun Oct 25 21:07:01 2020 +1300
|
|
18
|
+
Update dependencies to stop warnings
|
|
19
|
+
robertburrowes Sun Oct 25 21:06:37 2020 +1300
|
|
20
|
+
Included .gitignore in repo
|
|
21
|
+
robertburrowes Sun Oct 25 21:06:17 2020 +1300
|
|
22
|
+
Moved dev scripts to sbin
|
|
23
|
+
robertburrowes Mon Apr 13 23:09:22 2020 +1200
|
|
24
|
+
Qualify directory for tests
|
|
25
|
+
robertburrowes Mon Apr 13 23:08:11 2020 +1200
|
|
26
|
+
bump version for test change
|
|
27
|
+
robertburrowes Mon Apr 13 23:07:55 2020 +1200
|
|
28
|
+
remove unneeded require.
|
|
29
|
+
robertburrowes Sat Jun 25 10:11:46 2016 +1200
|
|
30
|
+
Upped version and autogen git tag for gem release.
|
|
31
|
+
robertburrowes Sat Jun 25 10:11:19 2016 +1200
|
|
32
|
+
added explicit DES,MD5,SHA256 and SHA512 tests
|
|
33
|
+
robertburrowes Sat Jun 25 10:10:35 2016 +1200
|
|
34
|
+
Dropped from archive as they are generated by testing
|
|
35
|
+
robertburrowes Sat Jun 25 10:07:22 2016 +1200
|
|
36
|
+
DES constant to String, added self.valid_sha256_response?
|
|
1
37
|
robertburrowes Wed Jun 22 22:53:58 2016 +1200
|
|
2
38
|
Wrong spelling of unix-crypt
|
|
3
39
|
robertburrowes Wed Jun 22 22:50:55 2016 +1200
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# wikk_password
|
|
2
2
|
|
|
3
|
-
*
|
|
4
|
-
* Source https://github.com/wikarekare/wikk_password
|
|
5
|
-
* Gem https://rubygems.org/gems/wikk_password
|
|
3
|
+
* Docs :: https://wikarekare.github.io/wikk_password/
|
|
4
|
+
* Source :: https://github.com/wikarekare/wikk_password
|
|
5
|
+
* Gem :: https://rubygems.org/gems/wikk_password
|
|
6
6
|
|
|
7
7
|
## DESCRIPTION:
|
|
8
8
|
|
|
@@ -38,7 +38,7 @@ rob:$aes256$cxpzz9BMCOvyqfyngashHA==$Z9qOyqgMa4V7ffnI0NOjIhPv+ObAfhC0vyNPXoR5bbw
|
|
|
38
38
|
## REQUIREMENTS:
|
|
39
39
|
|
|
40
40
|
###Gem requires
|
|
41
|
-
* require 'unix_crypt'
|
|
41
|
+
* require 'unix_crypt' (No longer available. Temp fix has been to include this gem's code)
|
|
42
42
|
* require 'wikk_aes_256'
|
|
43
43
|
|
|
44
44
|
## INSTALL:
|
data/Rakefile
CHANGED
|
@@ -4,27 +4,26 @@ require 'rubygems'
|
|
|
4
4
|
require 'hoe'
|
|
5
5
|
Hoe.plugin :yard
|
|
6
6
|
|
|
7
|
-
Hoe.spec 'wikk_password' do
|
|
8
|
-
self.readme_file =
|
|
9
|
-
self.developer(
|
|
7
|
+
Hoe.spec 'wikk_password' do
|
|
8
|
+
self.readme_file = 'README.md'
|
|
9
|
+
self.developer( 'Rob Burrowes', 'r.burrowes@auckland.ac.nz')
|
|
10
10
|
remote_rdoc_dir = '' # Release to root
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
self.yard_title = 'wikk_password'
|
|
13
|
-
self.yard_options = ['--markup', 'markdown', '--protected']
|
|
14
|
-
|
|
15
|
-
self.dependency "unix-crypt", [">= 1.3.0"]
|
|
16
|
-
self.dependency "wikk_aes_256", [">= 0.1.4"]
|
|
17
|
-
end
|
|
13
|
+
self.yard_options = [ '--markup', 'markdown', '--protected' ]
|
|
18
14
|
|
|
15
|
+
# self.dependency "unix-crypt", ['~> 1.3', '>= 1.3.0']
|
|
16
|
+
self.dependency 'wikk_aes_256', [ '~> 0.1', '>= 0.1.4' ]
|
|
17
|
+
end
|
|
19
18
|
|
|
20
|
-
#Validate manfest.txt
|
|
21
|
-
#rake check_manifest
|
|
19
|
+
# Validate manfest.txt
|
|
20
|
+
# rake check_manifest
|
|
22
21
|
|
|
23
|
-
#Local checking. Creates pkg/
|
|
24
|
-
#rake gem
|
|
22
|
+
# Local checking. Creates pkg/
|
|
23
|
+
# rake gem
|
|
25
24
|
|
|
26
|
-
#create doc/
|
|
27
|
-
#rake docs
|
|
25
|
+
# create doc/
|
|
26
|
+
# rake docs
|
|
28
27
|
|
|
29
|
-
#Copy up to rubygem.org
|
|
30
|
-
#rake release VERSION=1.0.1
|
|
28
|
+
# Copy up to rubygem.org
|
|
29
|
+
# rake release VERSION=1.0.1
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module UnixCrypt
|
|
2
|
+
class Base
|
|
3
|
+
def self.build(password, salt = nil, rounds = nil)
|
|
4
|
+
salt ||= generate_salt
|
|
5
|
+
if salt.length > max_salt_length
|
|
6
|
+
raise UnixCrypt::SaltTooLongError, "Salts longer than #{max_salt_length} characters are not permitted"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
construct_password(password, salt, rounds)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.hash(password, salt, rounds = nil)
|
|
13
|
+
bit_specified_base64encode internal_hash(prepare_password(password), salt, rounds)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.generate_salt
|
|
17
|
+
# Generates a random salt using the same character set as the base64 encoding
|
|
18
|
+
# used by the hash encoder.
|
|
19
|
+
SecureRandom.base64((default_salt_length * 6 / 8.0).ceil).tr('+', '.')[0...default_salt_length]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.construct_password(password, salt, rounds)
|
|
23
|
+
"$#{identifier}$#{rounds_marker rounds}#{salt}$#{hash(password, salt, rounds)}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.bit_specified_base64encode(input)
|
|
27
|
+
b64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
28
|
+
input = input.bytes.to_a
|
|
29
|
+
output = ''
|
|
30
|
+
byte_indexes.each do |i3, i2, i1|
|
|
31
|
+
b1 = i1 && input[i1] || 0
|
|
32
|
+
b2 = i2 && input[i2] || 0
|
|
33
|
+
b3 = i3 && input[i3] || 0
|
|
34
|
+
output <<
|
|
35
|
+
b64[b1 & 0b00111111] <<
|
|
36
|
+
b64[((b1 & 0b11000000) >> 6) |
|
|
37
|
+
((b2 & 0b00001111) << 2)] <<
|
|
38
|
+
b64[((b2 & 0b11110000) >> 4) |
|
|
39
|
+
((b3 & 0b00000011) << 4)] <<
|
|
40
|
+
b64[(b3 & 0b11111100) >> 2]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
remainder = 3 - (length % 3)
|
|
44
|
+
remainder = 0 if remainder == 3
|
|
45
|
+
output[0..(-1 - remainder)]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.prepare_password(password)
|
|
49
|
+
# For Ruby 1.9+, convert the password to UTF-8, then treat that new string
|
|
50
|
+
# as binary for the digest methods.
|
|
51
|
+
if password.respond_to?(:encode)
|
|
52
|
+
password = password.encode('UTF-8')
|
|
53
|
+
password.force_encoding('ASCII-8BIT')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
password
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.rounds_marker(_rounds)
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module UnixCrypt
|
|
2
|
+
class DES < UnixCrypt::Base
|
|
3
|
+
def self.hash(*_args)
|
|
4
|
+
raise 'Unimplemented for DES'
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.construct_password(password, salt, _rounds)
|
|
8
|
+
password.crypt(salt)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.default_salt_length
|
|
12
|
+
2
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.max_salt_length
|
|
16
|
+
2
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module UnixCrypt
|
|
2
|
+
class MD5 < UnixCrypt::Base
|
|
3
|
+
def self.digest
|
|
4
|
+
Digest::MD5
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.length
|
|
8
|
+
16
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.default_salt_length
|
|
12
|
+
8
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.max_salt_length
|
|
16
|
+
8
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.identifier
|
|
20
|
+
1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.byte_indexes
|
|
24
|
+
[[ 0, 6, 12 ], [ 1, 7, 13 ], [ 2, 8, 14 ], [ 3, 9, 15 ], [ 4, 10, 5 ], [ nil, nil, 11 ]]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.internal_hash(password, salt, _ignored = nil)
|
|
28
|
+
salt = salt[0..7]
|
|
29
|
+
|
|
30
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
|
31
|
+
a_string = "#{password}$1$#{salt}#{b * (password.length / length)}#{b[0...password.length % length]}"
|
|
32
|
+
|
|
33
|
+
password_length = password.length
|
|
34
|
+
while password_length > 0
|
|
35
|
+
a_string += password_length & 1 == 0 ? password[0].chr : "\x0"
|
|
36
|
+
password_length >>= 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
input = digest.digest(a_string)
|
|
40
|
+
|
|
41
|
+
1000.times do |index|
|
|
42
|
+
c_string = (index & 1 == 0 ? input : password)
|
|
43
|
+
c_string += salt unless index % 3 == 0
|
|
44
|
+
c_string += password unless index % 7 == 0
|
|
45
|
+
c_string += (index & 1 == 0 ? password : input)
|
|
46
|
+
input = digest.digest(c_string)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
input
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module UnixCrypt
|
|
2
|
+
class SHABase < Base
|
|
3
|
+
def self.default_salt_length
|
|
4
|
+
16
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.max_salt_length
|
|
8
|
+
16
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.default_rounds
|
|
12
|
+
5000
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.internal_hash(password, salt, rounds = nil)
|
|
16
|
+
rounds = apply_rounds_bounds(rounds || default_rounds)
|
|
17
|
+
salt = salt[0..15]
|
|
18
|
+
|
|
19
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
|
20
|
+
|
|
21
|
+
a_string = password + salt + b * (password.length / length) + b[0...password.length % length]
|
|
22
|
+
|
|
23
|
+
password_length = password.length
|
|
24
|
+
while password_length > 0
|
|
25
|
+
a_string += password_length & 1 == 0 ? password : b
|
|
26
|
+
password_length >>= 1
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
input = digest.digest(a_string)
|
|
30
|
+
|
|
31
|
+
dp = digest.digest(password * password.length)
|
|
32
|
+
p = dp * (password.length / length) + dp[0...password.length % length]
|
|
33
|
+
|
|
34
|
+
ds = digest.digest(salt * (16 + input.bytes.first))
|
|
35
|
+
s = ds * (salt.length / length) + ds[0...salt.length % length]
|
|
36
|
+
|
|
37
|
+
rounds.times do |index|
|
|
38
|
+
c_string = (index & 1 == 0 ? input : p)
|
|
39
|
+
c_string += s unless index % 3 == 0
|
|
40
|
+
c_string += p unless index % 7 == 0
|
|
41
|
+
c_string += (index & 1 == 0 ? p : input)
|
|
42
|
+
input = digest.digest(c_string)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
input
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.apply_rounds_bounds(rounds)
|
|
49
|
+
rounds = 1000 if rounds < 1000
|
|
50
|
+
rounds = 999_999_999 if rounds > 999_999_999
|
|
51
|
+
rounds
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.rounds_marker(rounds)
|
|
55
|
+
if rounds && rounds != default_rounds
|
|
56
|
+
"rounds=#{apply_rounds_bounds(rounds)}$"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
class SHA256 < SHABase
|
|
62
|
+
def self.digest
|
|
63
|
+
Digest::SHA256
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.length
|
|
67
|
+
32
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.identifier
|
|
71
|
+
5
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.byte_indexes
|
|
75
|
+
[[ 0, 10, 20 ], [ 21, 1, 11 ], [ 12, 22, 2 ], [ 3, 13, 23 ], [ 24, 4, 14 ], [ 15, 25, 5 ], [ 6, 16, 26 ], [ 27, 7, 17 ], [ 18, 28, 8 ], [ 9, 19, 29 ], [ nil, 31, 30 ]]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class SHA512 < SHABase
|
|
80
|
+
def self.digest
|
|
81
|
+
Digest::SHA512
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.length
|
|
85
|
+
64
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.identifier
|
|
89
|
+
6
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.byte_indexes
|
|
93
|
+
[[ 0, 21, 42 ], [ 22, 43, 1 ], [ 44, 2, 23 ], [ 3, 24, 45 ], [ 25, 46, 4 ], [ 47, 5, 26 ], [ 6, 27, 48 ], [ 28, 49, 7 ], [ 50, 8, 29 ], [ 9, 30, 51 ], [ 31, 52, 10 ],
|
|
94
|
+
[ 53, 11, 32 ], [ 12, 33, 54 ], [ 34, 55, 13 ], [ 56, 14, 35 ], [ 15, 36, 57 ], [ 37, 58, 16 ], [ 59, 17, 38 ], [ 18, 39, 60 ], [ 40, 61, 19 ], [ 62, 20, 41 ], [ nil, nil, 63 ]
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2013, Roger Nesbitt
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
7
|
+
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
8
|
+
# documentation and/or other materials provided with the distribution.
|
|
9
|
+
# Neither the name of the unix-crypt nor the names of its contributors may be used to endorse or promote products derived from this software
|
|
10
|
+
# without specific prior written permission.
|
|
11
|
+
#
|
|
12
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
13
|
+
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
14
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
15
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
16
|
+
|
|
17
|
+
require 'digest'
|
|
18
|
+
require 'securerandom'
|
|
19
|
+
|
|
20
|
+
module UnixCrypt
|
|
21
|
+
Error = Class.new(StandardError)
|
|
22
|
+
SaltTooLongError = Class.new(Error)
|
|
23
|
+
|
|
24
|
+
def self.valid?(password, string)
|
|
25
|
+
# Handle the original DES-based crypt(3)
|
|
26
|
+
return password.crypt(string) == string if string.length == 13
|
|
27
|
+
|
|
28
|
+
# All other types of password follow a standard format
|
|
29
|
+
return false unless (m = string.match(/\A\$([156])\$(?:rounds=(\d+)\$)?(.+)\$(.+)/))
|
|
30
|
+
|
|
31
|
+
hash = IDENTIFIER_MAPPINGS[m[1]].hash(password, m[3], m[2] && m[2].to_i)
|
|
32
|
+
hash == m[4]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
require_relative 'lib/base'
|
|
37
|
+
require_relative 'lib/des'
|
|
38
|
+
require_relative 'lib/md5'
|
|
39
|
+
require_relative 'lib/sha'
|
|
40
|
+
|
|
41
|
+
UnixCrypt::IDENTIFIER_MAPPINGS = {
|
|
42
|
+
'1' => UnixCrypt::MD5,
|
|
43
|
+
'5' => UnixCrypt::SHA256,
|
|
44
|
+
'6' => UnixCrypt::SHA512
|
|
45
|
+
}
|
data/lib/wikk_password.rb
CHANGED
|
@@ -1,170 +1,184 @@
|
|
|
1
|
-
|
|
1
|
+
# Keep in our own namespace
|
|
2
|
+
module WIKK
|
|
2
3
|
require 'wikk_aes_256'
|
|
3
4
|
require 'digest/sha2'
|
|
4
|
-
require 'unix_crypt'
|
|
5
5
|
require 'base64'
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
require_relative 'unix-crypt/unix_crypt.rb' # Temp fix, as the original gem has gone away
|
|
7
|
+
|
|
8
|
+
# READS/WRITES our private password file entries.
|
|
9
|
+
#
|
|
8
10
|
# @attr_reader [String] user the decrypted text
|
|
9
11
|
# @attr_reader [String] password the encrypted password, in form $type$initial_vector$encrypted_text
|
|
10
12
|
class Password
|
|
11
|
-
VERSION = '0.1.
|
|
13
|
+
VERSION = '0.1.3'
|
|
12
14
|
|
|
13
15
|
attr_reader :user, :password
|
|
14
|
-
|
|
15
|
-
#New. Fetches a user entry from the password file, or creates a new user (call via Passwd::add_user)
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
|
|
17
|
+
# New. Fetches a user entry from the password file, or creates a new user (call via Passwd::add_user)
|
|
18
|
+
#
|
|
19
|
+
# @param user [String] User name to fetch from password file, or to create, if new_user == true
|
|
20
|
+
# @param config [WIKK:Configuration] or hash or class with attr_readers :passwordFile, :encryption, :key
|
|
21
|
+
# @param new_user [Boolean] If true, then the user shouldn't be in password file.
|
|
22
|
+
# @return [WIKK::Password]
|
|
23
|
+
# @raise [IndexError] if the user entry exists.
|
|
24
|
+
def initialize(user, config, new_user = false)
|
|
25
|
+
if config.instance_of?(Hash)
|
|
26
|
+
sym = config.transform_keys(&:to_sym)
|
|
24
27
|
@config = Struct.new(*(k = sym.keys)).new(*sym.values_at(*k))
|
|
25
28
|
else
|
|
26
|
-
|
|
29
|
+
@config = config
|
|
27
30
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
raise IndexError, "User \"#{user}\" not found" if getpwnam(user) == false && !new_user
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Sets the user password, but does not save this. You must call save().
|
|
35
|
+
#
|
|
36
|
+
# @param password [String] the clear text password to encypt
|
|
37
|
+
# @return [String] the password file password entry.
|
|
38
|
+
def set_password(password) # rubocop: disable Naming/AccessorMethodName
|
|
35
39
|
@password = encrypt(password, @config.encryption)
|
|
36
40
|
end
|
|
37
|
-
|
|
38
|
-
#Compare an SHA256 hashed password + challenge with this users password
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
41
|
+
|
|
42
|
+
# Compare an SHA256 hashed password + challenge with this users password
|
|
43
|
+
#
|
|
44
|
+
# @param challenge [String] a random string, sent to the remote client, added to the password, and SHA256 hashed
|
|
45
|
+
# @param response [String] the remote clients hex_SHA256(password + challenge)
|
|
46
|
+
# @return [Boolean] True if the users password matches the one that created the response.
|
|
47
|
+
# @note The password entry must be decryptable, not a UNIX style hash.
|
|
48
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
|
44
49
|
def valid_sha256_response?(challenge, response)
|
|
45
|
-
return response == Digest::SHA256.digest(decrypt + challenge).
|
|
50
|
+
return response == Digest::SHA256.digest(decrypt + challenge).unpack1('H*')
|
|
46
51
|
end
|
|
47
|
-
|
|
48
|
-
#Compare an SHA256 hashed password + challenge with this users password
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
52
|
+
|
|
53
|
+
# Compare an SHA256 hashed password + challenge with this users password
|
|
54
|
+
#
|
|
55
|
+
# @param user [String] User name to fetch from password file, or to create, if new_user == true
|
|
56
|
+
# @param config [WIKK:Configuration] or hash or class with attr_readers :passwordFile, :encryption, :key
|
|
57
|
+
# @param challenge [String] a random string, sent to the remote client, added to the password, and SHA256 hashed
|
|
58
|
+
# @param response [String] the remote clients hex_SHA256(password + challenge)
|
|
59
|
+
# @return [Boolean] True if the users password matches the one that created the response.
|
|
60
|
+
# @note The password entry must be decryptable, not a UNIX style hash.
|
|
61
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
|
56
62
|
def self.valid_sha256_response?(user, config, challenge, response)
|
|
57
63
|
self.new(user, config).valid_sha256_response?(challenge, response)
|
|
58
64
|
end
|
|
59
|
-
|
|
60
|
-
#Compares the password with the user's password by encrypting the password passed in
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
65
|
+
|
|
66
|
+
# Compares the password with the user's password by encrypting the password passed in
|
|
67
|
+
#
|
|
68
|
+
# @param password [String] The clear text password
|
|
69
|
+
# @return [Boolean] True if the passwords match
|
|
70
|
+
# @raise [ArgumentError] if the encryption method is unknown.
|
|
64
71
|
def valid?(ct_password)
|
|
65
|
-
ignore,encryption,iv,
|
|
66
|
-
encryption = 'DES' if ignore != '' #No $'s in DES password, so ignore has text.
|
|
72
|
+
ignore, encryption, iv, _password = @password.split('$')
|
|
73
|
+
encryption = 'DES' if ignore != '' # No $'s in DES password, so ignore has text.
|
|
67
74
|
case encryption
|
|
68
|
-
when 'ct'
|
|
69
|
-
when 'aes256'
|
|
70
|
-
when 'DES'
|
|
71
|
-
when 'MD5','1','SHA256','5','SHA512','6'
|
|
75
|
+
when 'ct' then return ct_password == @password
|
|
76
|
+
when 'aes256' then return encrypt(ct_password, encryption, iv) == @password
|
|
77
|
+
when 'DES' then return UnixCrypt.valid?(ct_password, @password)
|
|
78
|
+
when 'MD5', '1', 'SHA256', '5', 'SHA512', '6' then return UnixCrypt.valid?(ct_password, @password) # rubocop: disable Lint/DuplicateBranch
|
|
72
79
|
else raise ArgumentError, "Unsupported encryption algorithm $#{encryption}"
|
|
73
80
|
end
|
|
74
81
|
end
|
|
75
|
-
|
|
76
|
-
#Adds a user to the password file
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
|
|
82
|
+
|
|
83
|
+
# Adds a user to the password file
|
|
84
|
+
#
|
|
85
|
+
# @param user [String] New user name. Raises an error if the user exists
|
|
86
|
+
# @param password [String] Clear text password. Raises an error if this is nil or ''
|
|
87
|
+
# @note Modifies the password file.
|
|
88
|
+
# @raise [IndexError] if the user entry exists.
|
|
89
|
+
# @raise [ArgumentError] if the password is nil or empty.
|
|
90
|
+
def self.add_user(user, password, config)
|
|
83
91
|
user_record = self.new(user, config, true)
|
|
84
|
-
raise IndexError, "User \"#{user}\" is already present"
|
|
85
|
-
raise ArgumentError, "Password can't be empty" if password
|
|
92
|
+
raise IndexError, "User \"#{user}\" is already present" if user_record.password != nil
|
|
93
|
+
raise ArgumentError, "Password can't be empty" if password.nil? || password == ''
|
|
94
|
+
|
|
86
95
|
user_record.set_password(password)
|
|
87
96
|
user_record.save
|
|
88
97
|
end
|
|
89
|
-
|
|
90
|
-
#Saves changes or a new user entry into the password file
|
|
98
|
+
|
|
99
|
+
# Saves changes or a new user entry into the password file
|
|
100
|
+
#
|
|
91
101
|
def save
|
|
92
102
|
loadfile
|
|
93
103
|
@pwent[@user] = @password
|
|
94
104
|
writefile
|
|
95
105
|
end
|
|
96
|
-
|
|
97
|
-
#Outputs password file entry as a string
|
|
98
|
-
#
|
|
106
|
+
|
|
107
|
+
# Outputs password file entry as a string
|
|
108
|
+
#
|
|
109
|
+
# @return [String] password file entry.
|
|
99
110
|
def to_s
|
|
100
111
|
"#{@user}:#{@password}"
|
|
101
112
|
end
|
|
102
113
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
# Fetch a password file entry by user's name
|
|
115
|
+
#
|
|
116
|
+
# @param user [String] user name
|
|
117
|
+
# @return [Boolean] True if user entry exists
|
|
118
|
+
private def getpwnam(user)
|
|
119
|
+
loadfile
|
|
120
|
+
@user = user
|
|
121
|
+
@password = @pwent[@user]
|
|
122
|
+
return @password != nil # i.e. Found a user entry
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Read the password file into the @pwent hash
|
|
126
|
+
#
|
|
127
|
+
private def loadfile
|
|
128
|
+
@pwent = {}
|
|
129
|
+
File.open(@config.passwordFile, 'r') do |fd|
|
|
130
|
+
fd.each do |line|
|
|
131
|
+
tokens = line.chomp.split(/:/)
|
|
132
|
+
@pwent[tokens[0]] = tokens[1] if tokens[0] != ''
|
|
133
|
+
end
|
|
123
134
|
end
|
|
124
135
|
end
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
|
|
137
|
+
# Overwrite the password file from the @pwent hash
|
|
138
|
+
#
|
|
139
|
+
private def writefile
|
|
140
|
+
File.open(@config.passwordFile, 'w+') do |fd|
|
|
141
|
+
@pwent.each do |k, v|
|
|
142
|
+
fd.puts "#{k}:#{v}"
|
|
143
|
+
end
|
|
132
144
|
end
|
|
133
145
|
end
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
#
|
|
139
|
-
|
|
146
|
+
|
|
147
|
+
# Encrypts a clear text password
|
|
148
|
+
#
|
|
149
|
+
# @param password [String] The clear text password
|
|
150
|
+
# @param challenge [String] Norm
|
|
151
|
+
# @raise [ArgumentError] if the encryption algorithm isn't known.
|
|
152
|
+
private def encrypt(password, algorithm = 'aes256', pwd_iv = nil)
|
|
140
153
|
case algorithm
|
|
141
|
-
when
|
|
142
|
-
password,
|
|
154
|
+
when 'aes256'
|
|
155
|
+
password, _key, iv = WIKK::AES_256.cipher_to_s(password, @config.key, pwd_iv)
|
|
143
156
|
return "$aes256$#{iv}$#{password}"
|
|
144
|
-
when
|
|
157
|
+
when 'DES'
|
|
145
158
|
return UnixCrypt::DES.build(password)
|
|
146
|
-
when
|
|
159
|
+
when 'MD5', '1' # Unix password digest, which is multiple hashes
|
|
147
160
|
return UnixCrypt::MD5.build(password)
|
|
148
|
-
when
|
|
161
|
+
when 'SHA256', '5' # Unix password digest, which is multiple hashes
|
|
149
162
|
return UnixCrypt::SHA256.build(password)
|
|
150
|
-
when
|
|
163
|
+
when 'SHA512', '6' # Unix password digest, which is multiple hashes
|
|
151
164
|
return UnixCrypt::SHA512.build(password)
|
|
152
|
-
when 'ct' #ct == clear text
|
|
165
|
+
when 'ct' # ct == clear text
|
|
153
166
|
return "$ct$$#{password}"
|
|
154
167
|
else
|
|
155
168
|
raise ArgumentError, "Unsupported Encryption algorithm #{@config.encryption}"
|
|
156
169
|
end
|
|
157
170
|
end
|
|
158
|
-
|
|
159
|
-
#Decrypts @password, if this is possible
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
|
|
172
|
+
# Decrypts @password, if this is possible
|
|
173
|
+
#
|
|
174
|
+
# @return [String] the clear text password
|
|
175
|
+
# @raise [ArgumentError] if the encryption type can't be decrypted.
|
|
176
|
+
private def decrypt
|
|
177
|
+
_ignore, encryption, iv, password = @password.split('$')
|
|
164
178
|
case encryption
|
|
165
|
-
when 'ct'
|
|
179
|
+
when 'ct' then password
|
|
166
180
|
when 'aes256'
|
|
167
|
-
ct_password,
|
|
181
|
+
ct_password, _ct_key, _ct_iv = WIKK::AES_256.decrypt(password, true, @config.key, iv)
|
|
168
182
|
return ct_password
|
|
169
183
|
else
|
|
170
184
|
raise ArgumentError, "Unsupported decryption algorithm #{@config.encryption}"
|
|
@@ -172,8 +186,3 @@ module WIKK
|
|
|
172
186
|
end
|
|
173
187
|
end
|
|
174
188
|
end
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
metadata
CHANGED
|
@@ -1,33 +1,22 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wikk_password
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rob Burrowes
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-06-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: unix-crypt
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.3.0
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 1.3.0
|
|
27
13
|
- !ruby/object:Gem::Dependency
|
|
28
14
|
name: wikk_aes_256
|
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
|
30
16
|
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
31
20
|
- - ">="
|
|
32
21
|
- !ruby/object:Gem::Version
|
|
33
22
|
version: 0.1.4
|
|
@@ -35,6 +24,9 @@ dependencies:
|
|
|
35
24
|
prerelease: false
|
|
36
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
26
|
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '0.1'
|
|
38
30
|
- - ">="
|
|
39
31
|
- !ruby/object:Gem::Version
|
|
40
32
|
version: 0.1.4
|
|
@@ -44,28 +36,28 @@ dependencies:
|
|
|
44
36
|
requirements:
|
|
45
37
|
- - ">="
|
|
46
38
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: 0.1.
|
|
39
|
+
version: 0.1.3
|
|
48
40
|
type: :development
|
|
49
41
|
prerelease: false
|
|
50
42
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
43
|
requirements:
|
|
52
44
|
- - ">="
|
|
53
45
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: 0.1.
|
|
46
|
+
version: 0.1.3
|
|
55
47
|
- !ruby/object:Gem::Dependency
|
|
56
48
|
name: hoe
|
|
57
49
|
requirement: !ruby/object:Gem::Requirement
|
|
58
50
|
requirements:
|
|
59
51
|
- - "~>"
|
|
60
52
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '3.
|
|
53
|
+
version: '3.23'
|
|
62
54
|
type: :development
|
|
63
55
|
prerelease: false
|
|
64
56
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
57
|
requirements:
|
|
66
58
|
- - "~>"
|
|
67
59
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '3.
|
|
60
|
+
version: '3.23'
|
|
69
61
|
description: |-
|
|
70
62
|
Reads/writes password file entries of format user:password and provides tests for password validity.
|
|
71
63
|
Works with standard unix password types, clear text for testing, and decryptable AES_256_CBC.
|
|
@@ -82,12 +74,17 @@ files:
|
|
|
82
74
|
- Manifest.txt
|
|
83
75
|
- README.md
|
|
84
76
|
- Rakefile
|
|
77
|
+
- lib/unix-crypt/lib/base.rb
|
|
78
|
+
- lib/unix-crypt/lib/des.rb
|
|
79
|
+
- lib/unix-crypt/lib/md5.rb
|
|
80
|
+
- lib/unix-crypt/lib/sha.rb
|
|
81
|
+
- lib/unix-crypt/unix_crypt.rb
|
|
85
82
|
- lib/wikk_password.rb
|
|
86
|
-
homepage:
|
|
83
|
+
homepage: https://wikarekare.github.io/wikk_password/
|
|
87
84
|
licenses:
|
|
88
85
|
- MIT
|
|
89
86
|
metadata: {}
|
|
90
|
-
post_install_message:
|
|
87
|
+
post_install_message:
|
|
91
88
|
rdoc_options:
|
|
92
89
|
- "--markup"
|
|
93
90
|
- markdown
|
|
@@ -108,9 +105,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
108
105
|
- !ruby/object:Gem::Version
|
|
109
106
|
version: '0'
|
|
110
107
|
requirements: []
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
signing_key:
|
|
108
|
+
rubygems_version: 3.2.22
|
|
109
|
+
signing_key:
|
|
114
110
|
specification_version: 4
|
|
115
111
|
summary: Reads/writes password file entries of format user:password and provides tests
|
|
116
112
|
for password validity
|