unix-crypt 1.1.1 → 1.3.0
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/README.rdoc +17 -4
- data/lib/unix_crypt.rb +15 -163
- data/lib/unix_crypt/base.rb +61 -0
- data/lib/unix_crypt/command_line.rb +9 -3
- data/lib/unix_crypt/des.rb +13 -0
- data/lib/unix_crypt/md5.rb +37 -0
- data/lib/unix_crypt/sha.rb +75 -0
- data/test/test_unix_crypt.rb +35 -4
- data/unix-crypt.gemspec +4 -1
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4f112426245004b2afc9ce190162d81729fcabd
|
4
|
+
data.tar.gz: a47fe1d84ca16f4597b63a3622f96b57405e25f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e355b3654486a6102dae8273c00cb58925e9731e268f4486fae24944bf5c63c51e18e79015cae9cc4a22b6c97182d730f5799eef155baac1c5ab879e8cf17f38
|
7
|
+
data.tar.gz: 9051d55228a699b3104ec8ea71637af6b05973a984ffd7f36d654d851673ce66259f0d72f6289c56c2dc78aadc226933dc1c2f1eac7a9592bb961d2f17651b87
|
data/README.rdoc
CHANGED
@@ -12,7 +12,11 @@ It handles:
|
|
12
12
|
* SHA256 passwords (starting with $5$)
|
13
13
|
* SHA512 passwords (starting with $6$)
|
14
14
|
|
15
|
-
This library is compatible with Ruby 1.8.7 and above. Tested on Ruby 2.0.
|
15
|
+
This library is compatible with Ruby 1.8.7 and above. Tested on Ruby 2.0.0p353.
|
16
|
+
|
17
|
+
== Installation
|
18
|
+
|
19
|
+
gem install unix-crypt
|
16
20
|
|
17
21
|
== Using the command line tool
|
18
22
|
|
@@ -20,9 +24,9 @@ An executable named +mkunixcrypt+ allows you to generate passwords from the comm
|
|
20
24
|
|
21
25
|
Usage: mkunixcrypt [options]
|
22
26
|
Encrypts password using the unix-crypt gem
|
23
|
-
|
27
|
+
|
24
28
|
Options:
|
25
|
-
-h, --hash [HASH] Set hash algorithm [SHA512 (default), SHA256, MD5]
|
29
|
+
-h, --hash [HASH] Set hash algorithm [SHA512 (default), SHA256, MD5, DES]
|
26
30
|
-p, --password [PASSWORD] Provide password on command line (insecure!)
|
27
31
|
-s, --salt [SALT] Provide hash salt
|
28
32
|
-r, --rounds [ROUNDS] Set number of hashing rounds (SHA256/SHA512 only)
|
@@ -31,8 +35,10 @@ An executable named +mkunixcrypt+ allows you to generate passwords from the comm
|
|
31
35
|
|
32
36
|
== Using the library
|
33
37
|
|
34
|
-
You can either validate a password matches its hash:
|
38
|
+
You can either validate a password of any type matches its hash:
|
35
39
|
|
40
|
+
>> require 'unix_crypt'
|
41
|
+
=> true
|
36
42
|
>> UnixCrypt.valid?("Hello world!", "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5")
|
37
43
|
=> true
|
38
44
|
|
@@ -46,6 +52,13 @@ If a salt is not specified, one will be generated using random data:
|
|
46
52
|
>> UnixCrypt::SHA256.build("Hello world!")
|
47
53
|
=> "$5$v.fjb6lucDCZKjcf$90gzpr9HYo0eAeaN8rubElJdUUOcVYjTnGePBRvCgt1"
|
48
54
|
|
55
|
+
There are four classes you can use, depending on which hashing algorithm you'd like:
|
56
|
+
|
57
|
+
UnixCrypt::DES
|
58
|
+
UnixCrypt::MD5
|
59
|
+
UnixCrypt::SHA256
|
60
|
+
UnixCrypt::SHA512
|
61
|
+
|
49
62
|
== License
|
50
63
|
|
51
64
|
Licensed under the BSD license. See LICENSE file for details.
|
data/lib/unix_crypt.rb
CHANGED
@@ -2,178 +2,30 @@ require 'digest'
|
|
2
2
|
require 'securerandom'
|
3
3
|
|
4
4
|
module UnixCrypt
|
5
|
-
VERSION = "1.
|
5
|
+
VERSION = "1.3.0"
|
6
|
+
|
7
|
+
Error = Class.new(StandardError)
|
8
|
+
SaltTooLongError = Class.new(Error)
|
6
9
|
|
7
10
|
def self.valid?(password, string)
|
8
11
|
# Handle the original DES-based crypt(3)
|
9
12
|
return password.crypt(string) == string if string.length == 13
|
10
13
|
|
14
|
+
# All other types of password follow a standard format
|
11
15
|
return false unless m = string.match(/\A\$([156])\$(?:rounds=(\d+)\$)?(.+)\$(.+)/)
|
12
16
|
|
13
17
|
hash = IDENTIFIER_MAPPINGS[m[1]].hash(password, m[3], m[2] && m[2].to_i)
|
14
18
|
hash == m[4]
|
15
19
|
end
|
20
|
+
end
|
16
21
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"$#{identifier}$#{rounds_marker rounds}#{salt}$#{hash(password, salt, rounds)}"
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.hash(password, salt, rounds = nil)
|
25
|
-
bit_specified_base64encode internal_hash(prepare_password(password), salt, rounds)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.generate_salt
|
29
|
-
# Generates a random salt using the same character set as the base64 encoding
|
30
|
-
# used by the hash encoder.
|
31
|
-
SecureRandom.base64(default_salt_length).gsub("=", "").tr("+", ".")
|
32
|
-
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
def self.bit_specified_base64encode(input)
|
36
|
-
b64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
37
|
-
input = input.bytes.to_a
|
38
|
-
output = ""
|
39
|
-
byte_indexes.each do |i3, i2, i1|
|
40
|
-
b1, b2, b3 = i1 && input[i1] || 0, i2 && input[i2] || 0, i3 && input[i3] || 0
|
41
|
-
output <<
|
42
|
-
b64[ b1 & 0b00111111] <<
|
43
|
-
b64[((b1 & 0b11000000) >> 6) |
|
44
|
-
((b2 & 0b00001111) << 2)] <<
|
45
|
-
b64[((b2 & 0b11110000) >> 4) |
|
46
|
-
((b3 & 0b00000011) << 4)] <<
|
47
|
-
b64[ (b3 & 0b11111100) >> 2]
|
48
|
-
end
|
49
|
-
|
50
|
-
remainder = 3 - (length % 3)
|
51
|
-
remainder = 0 if remainder == 3
|
52
|
-
output[0..-1-remainder]
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.prepare_password(password)
|
56
|
-
# For Ruby 1.9+, convert the password to UTF-8, then treat that new string
|
57
|
-
# as binary for the digest methods.
|
58
|
-
if password.respond_to?(:encode)
|
59
|
-
password = password.encode("UTF-8")
|
60
|
-
password.force_encoding("ASCII-8BIT")
|
61
|
-
end
|
62
|
-
|
63
|
-
password
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.rounds_marker(rounds)
|
67
|
-
nil
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
class MD5 < Base
|
72
|
-
def self.digest; Digest::MD5; end
|
73
|
-
def self.length; 16; end
|
74
|
-
def self.default_salt_length; 6; end
|
75
|
-
def self.identifier; 1; end
|
76
|
-
|
77
|
-
def self.byte_indexes
|
78
|
-
[[0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5], [nil, nil, 11]]
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.internal_hash(password, salt, ignored = nil)
|
82
|
-
salt = salt[0..7]
|
83
|
-
|
84
|
-
b = digest.digest("#{password}#{salt}#{password}")
|
85
|
-
a_string = "#{password}$1$#{salt}#{b * (password.length/length)}#{b[0...password.length % length]}"
|
86
|
-
|
87
|
-
password_length = password.length
|
88
|
-
while password_length > 0
|
89
|
-
a_string += (password_length & 1 != 0) ? "\x0" : password[0].chr
|
90
|
-
password_length >>= 1
|
91
|
-
end
|
92
|
-
|
93
|
-
input = digest.digest(a_string)
|
94
|
-
|
95
|
-
1000.times do |index|
|
96
|
-
c_string = ((index & 1 != 0) ? password : input)
|
97
|
-
c_string += salt unless index % 3 == 0
|
98
|
-
c_string += password unless index % 7 == 0
|
99
|
-
c_string += ((index & 1 != 0) ? input : password)
|
100
|
-
input = digest.digest(c_string)
|
101
|
-
end
|
102
|
-
|
103
|
-
input
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class SHABase < Base
|
108
|
-
protected
|
109
|
-
def self.default_salt_length; 12; end
|
110
|
-
def self.default_rounds; 5000; end
|
111
|
-
|
112
|
-
def self.internal_hash(password, salt, rounds = nil)
|
113
|
-
rounds = apply_rounds_bounds(rounds || default_rounds)
|
114
|
-
salt = salt[0..15]
|
115
|
-
|
116
|
-
b = digest.digest("#{password}#{salt}#{password}")
|
117
|
-
|
118
|
-
a_string = password + salt + b * (password.length/length) + b[0...password.length % length]
|
119
|
-
|
120
|
-
password_length = password.length
|
121
|
-
while password_length > 0
|
122
|
-
a_string += (password_length & 1 != 0) ? b : password
|
123
|
-
password_length >>= 1
|
124
|
-
end
|
125
|
-
|
126
|
-
input = digest.digest(a_string)
|
127
|
-
|
128
|
-
dp = digest.digest(password * password.length)
|
129
|
-
p = dp * (password.length/length) + dp[0...password.length % length]
|
130
|
-
|
131
|
-
ds = digest.digest(salt * (16 + input.bytes.first))
|
132
|
-
s = ds * (salt.length/length) + ds[0...salt.length % length]
|
133
|
-
|
134
|
-
rounds.times do |index|
|
135
|
-
c_string = ((index & 1 != 0) ? p : input)
|
136
|
-
c_string += s unless index % 3 == 0
|
137
|
-
c_string += p unless index % 7 == 0
|
138
|
-
c_string += ((index & 1 != 0) ? input : p)
|
139
|
-
input = digest.digest(c_string)
|
140
|
-
end
|
141
|
-
|
142
|
-
input
|
143
|
-
end
|
144
|
-
|
145
|
-
def self.apply_rounds_bounds(rounds)
|
146
|
-
rounds = 1000 if rounds < 1000
|
147
|
-
rounds = 999_999_999 if rounds > 999_999_999
|
148
|
-
rounds
|
149
|
-
end
|
150
|
-
|
151
|
-
def self.rounds_marker(rounds)
|
152
|
-
if rounds && rounds != default_rounds
|
153
|
-
"rounds=#{apply_rounds_bounds(rounds)}$"
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
class SHA256 < SHABase
|
159
|
-
def self.digest; Digest::SHA256; end
|
160
|
-
def self.length; 32; end
|
161
|
-
def self.identifier; 5; end
|
162
|
-
|
163
|
-
def self.byte_indexes
|
164
|
-
[[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]]
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
class SHA512 < SHABase
|
169
|
-
def self.digest; Digest::SHA512; end
|
170
|
-
def self.length; 64; end
|
171
|
-
def self.identifier; 6; end
|
172
|
-
def self.byte_indexes
|
173
|
-
[[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],
|
174
|
-
[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]]
|
175
|
-
end
|
176
|
-
end
|
22
|
+
require 'unix_crypt/base'
|
23
|
+
require 'unix_crypt/des'
|
24
|
+
require 'unix_crypt/md5'
|
25
|
+
require 'unix_crypt/sha'
|
177
26
|
|
178
|
-
|
179
|
-
|
27
|
+
UnixCrypt::IDENTIFIER_MAPPINGS = {
|
28
|
+
'1' => UnixCrypt::MD5,
|
29
|
+
'5' => UnixCrypt::SHA256,
|
30
|
+
'6' => UnixCrypt::SHA512
|
31
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class UnixCrypt::Base
|
2
|
+
def self.build(password, salt = nil, rounds = nil)
|
3
|
+
salt ||= generate_salt
|
4
|
+
if salt.length > max_salt_length
|
5
|
+
raise UnixCrypt::SaltTooLongError, "Salts longer than #{max_salt_length} characters are not permitted"
|
6
|
+
end
|
7
|
+
|
8
|
+
construct_password(password, salt, rounds)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.hash(password, salt, rounds = nil)
|
12
|
+
bit_specified_base64encode internal_hash(prepare_password(password), salt, rounds)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.generate_salt
|
16
|
+
# Generates a random salt using the same character set as the base64 encoding
|
17
|
+
# used by the hash encoder.
|
18
|
+
SecureRandom.base64((default_salt_length * 6 / 8.0).ceil).tr("+", ".")[0...default_salt_length]
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
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, b2, b3 = i1 && input[i1] || 0, i2 && input[i2] || 0, i3 && input[i3] || 0
|
32
|
+
output <<
|
33
|
+
b64[ b1 & 0b00111111] <<
|
34
|
+
b64[((b1 & 0b11000000) >> 6) |
|
35
|
+
((b2 & 0b00001111) << 2)] <<
|
36
|
+
b64[((b2 & 0b11110000) >> 4) |
|
37
|
+
((b3 & 0b00000011) << 4)] <<
|
38
|
+
b64[ (b3 & 0b11111100) >> 2]
|
39
|
+
end
|
40
|
+
|
41
|
+
remainder = 3 - (length % 3)
|
42
|
+
remainder = 0 if remainder == 3
|
43
|
+
output[0..-1-remainder]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.prepare_password(password)
|
47
|
+
# For Ruby 1.9+, convert the password to UTF-8, then treat that new string
|
48
|
+
# as binary for the digest methods.
|
49
|
+
if password.respond_to?(:encode)
|
50
|
+
password = password.encode("UTF-8")
|
51
|
+
password.force_encoding("ASCII-8BIT")
|
52
|
+
end
|
53
|
+
|
54
|
+
password
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.rounds_marker(rounds)
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -22,7 +22,12 @@ class UnixCrypt::CommandLine
|
|
22
22
|
password_warning
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
begin
|
26
|
+
puts @options.hasher.build(@options.password, @options.salt, @options.rounds)
|
27
|
+
rescue UnixCrypt::Error => e
|
28
|
+
$stderr.puts "password generation failed: #{e.message}"
|
29
|
+
end
|
30
|
+
|
26
31
|
clear_string(@options.password)
|
27
32
|
end
|
28
33
|
|
@@ -31,7 +36,8 @@ class UnixCrypt::CommandLine
|
|
31
36
|
HASHERS = {
|
32
37
|
:SHA512 => UnixCrypt::SHA512,
|
33
38
|
:SHA256 => UnixCrypt::SHA256,
|
34
|
-
:MD5 => UnixCrypt::MD5
|
39
|
+
:MD5 => UnixCrypt::MD5,
|
40
|
+
:DES => UnixCrypt::DES
|
35
41
|
}
|
36
42
|
|
37
43
|
def self.parse(args)
|
@@ -47,7 +53,7 @@ class UnixCrypt::CommandLine
|
|
47
53
|
opts.separator ""
|
48
54
|
opts.separator "Options:"
|
49
55
|
|
50
|
-
opts.on("-h", "--hash [HASH]", String, "Set hash algorithm [SHA512 (default), SHA256, MD5]") do |hasher|
|
56
|
+
opts.on("-h", "--hash [HASH]", String, "Set hash algorithm [SHA512 (default), SHA256, MD5, DES]") do |hasher|
|
51
57
|
options.hashmethod = hasher.to_s.upcase.to_sym
|
52
58
|
options.hasher = HASHERS[options.hashmethod]
|
53
59
|
raise Abort, "Invalid hash algorithm for -h/--hash" if options.hasher.nil?
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class UnixCrypt::DES < UnixCrypt::Base
|
2
|
+
def self.hash(*args)
|
3
|
+
raise "Unimplemented for DES"
|
4
|
+
end
|
5
|
+
|
6
|
+
protected
|
7
|
+
def self.construct_password(password, salt, rounds)
|
8
|
+
password.crypt(salt)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.default_salt_length; 2; end
|
12
|
+
def self.max_salt_length; 2; end
|
13
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class UnixCrypt::MD5 < UnixCrypt::Base
|
2
|
+
protected
|
3
|
+
def self.digest; Digest::MD5; end
|
4
|
+
def self.length; 16; end
|
5
|
+
def self.default_salt_length; 8; end
|
6
|
+
def self.max_salt_length; 8; end
|
7
|
+
def self.identifier; 1; end
|
8
|
+
|
9
|
+
def self.byte_indexes
|
10
|
+
[[0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5], [nil, nil, 11]]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.internal_hash(password, salt, ignored = nil)
|
14
|
+
salt = salt[0..7]
|
15
|
+
|
16
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
17
|
+
a_string = "#{password}$1$#{salt}#{b * (password.length/length)}#{b[0...password.length % length]}"
|
18
|
+
|
19
|
+
password_length = password.length
|
20
|
+
while password_length > 0
|
21
|
+
a_string += (password_length & 1 != 0) ? "\x0" : password[0].chr
|
22
|
+
password_length >>= 1
|
23
|
+
end
|
24
|
+
|
25
|
+
input = digest.digest(a_string)
|
26
|
+
|
27
|
+
1000.times do |index|
|
28
|
+
c_string = ((index & 1 != 0) ? password : input)
|
29
|
+
c_string += salt unless index % 3 == 0
|
30
|
+
c_string += password unless index % 7 == 0
|
31
|
+
c_string += ((index & 1 != 0) ? input : password)
|
32
|
+
input = digest.digest(c_string)
|
33
|
+
end
|
34
|
+
|
35
|
+
input
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module UnixCrypt
|
2
|
+
class SHABase < Base
|
3
|
+
protected
|
4
|
+
def self.default_salt_length; 16; end
|
5
|
+
def self.max_salt_length; 16; end
|
6
|
+
def self.default_rounds; 5000; end
|
7
|
+
|
8
|
+
def self.internal_hash(password, salt, rounds = nil)
|
9
|
+
rounds = apply_rounds_bounds(rounds || default_rounds)
|
10
|
+
salt = salt[0..15]
|
11
|
+
|
12
|
+
b = digest.digest("#{password}#{salt}#{password}")
|
13
|
+
|
14
|
+
a_string = password + salt + b * (password.length/length) + b[0...password.length % length]
|
15
|
+
|
16
|
+
password_length = password.length
|
17
|
+
while password_length > 0
|
18
|
+
a_string += (password_length & 1 != 0) ? b : password
|
19
|
+
password_length >>= 1
|
20
|
+
end
|
21
|
+
|
22
|
+
input = digest.digest(a_string)
|
23
|
+
|
24
|
+
dp = digest.digest(password * password.length)
|
25
|
+
p = dp * (password.length/length) + dp[0...password.length % length]
|
26
|
+
|
27
|
+
ds = digest.digest(salt * (16 + input.bytes.first))
|
28
|
+
s = ds * (salt.length/length) + ds[0...salt.length % length]
|
29
|
+
|
30
|
+
rounds.times do |index|
|
31
|
+
c_string = ((index & 1 != 0) ? p : input)
|
32
|
+
c_string += s unless index % 3 == 0
|
33
|
+
c_string += p unless index % 7 == 0
|
34
|
+
c_string += ((index & 1 != 0) ? input : p)
|
35
|
+
input = digest.digest(c_string)
|
36
|
+
end
|
37
|
+
|
38
|
+
input
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.apply_rounds_bounds(rounds)
|
42
|
+
rounds = 1000 if rounds < 1000
|
43
|
+
rounds = 999_999_999 if rounds > 999_999_999
|
44
|
+
rounds
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.rounds_marker(rounds)
|
48
|
+
if rounds && rounds != default_rounds
|
49
|
+
"rounds=#{apply_rounds_bounds(rounds)}$"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class SHA256 < SHABase
|
55
|
+
protected
|
56
|
+
def self.digest; Digest::SHA256; end
|
57
|
+
def self.length; 32; end
|
58
|
+
def self.identifier; 5; end
|
59
|
+
|
60
|
+
def self.byte_indexes
|
61
|
+
[[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]]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class SHA512 < SHABase
|
66
|
+
protected
|
67
|
+
def self.digest; Digest::SHA512; end
|
68
|
+
def self.length; 64; end
|
69
|
+
def self.identifier; 6; end
|
70
|
+
def self.byte_indexes
|
71
|
+
[[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],
|
72
|
+
[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]]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/test/test_unix_crypt.rb
CHANGED
@@ -50,25 +50,50 @@ class UnixCryptTest < Test::Unit::TestCase
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
53
|
+
def test_validity_of_des_password_generation
|
54
|
+
hash = UnixCrypt::DES.build("test")
|
55
|
+
assert UnixCrypt.valid?("test", hash)
|
56
|
+
|
57
|
+
hash = UnixCrypt::DES.build("test", 'xx')
|
58
|
+
assert UnixCrypt.valid?("test", hash)
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_validity_of_md5_password_generation
|
54
62
|
hash = UnixCrypt::MD5.build("test")
|
55
63
|
assert UnixCrypt.valid?("test", hash)
|
64
|
+
|
65
|
+
hash = UnixCrypt::MD5.build("test", "abcdefgh")
|
66
|
+
assert UnixCrypt.valid?("test", hash)
|
56
67
|
end
|
57
68
|
|
58
|
-
def
|
69
|
+
def test_validity_of_sha256_password_generation
|
59
70
|
hash = UnixCrypt::SHA256.build("test")
|
60
71
|
assert UnixCrypt.valid?("test", hash)
|
72
|
+
|
73
|
+
hash = UnixCrypt::SHA256.build("test", "1234567890123456")
|
74
|
+
assert UnixCrypt.valid?("test", hash)
|
61
75
|
end
|
62
76
|
|
63
|
-
def
|
77
|
+
def test_validity_of_sha512_password_generation
|
64
78
|
hash = UnixCrypt::SHA512.build("test")
|
65
79
|
assert UnixCrypt.valid?("test", hash)
|
80
|
+
|
81
|
+
hash = UnixCrypt::SHA512.build("test", "1234567890123456")
|
82
|
+
assert UnixCrypt.valid?("test", hash)
|
66
83
|
end
|
67
84
|
|
68
|
-
def
|
85
|
+
def test_structure_of_generated_passwords_and_salts
|
86
|
+
assert_match %r{\A[a-zA-Z0-9./]{13}\z}, UnixCrypt::DES.build("test password")
|
87
|
+
assert_match %r{\Azz[a-zA-Z0-9./]{11}\z}, UnixCrypt::DES.build("test password", 'zz')
|
88
|
+
|
69
89
|
assert_match %r{\A\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{22}\z}, UnixCrypt::MD5.build("test password")
|
90
|
+
assert_match %r{\A\$1\$abcdefgh\$[a-zA-Z0-9./]{22}\z}, UnixCrypt::MD5.build("test password", "abcdefgh")
|
91
|
+
|
70
92
|
assert_match %r{\A\$5\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{43}\z}, UnixCrypt::SHA256.build("test password")
|
93
|
+
assert_match %r{\A\$5\$0123456789abcdef\$[a-zA-Z0-9./]{43}\z}, UnixCrypt::SHA256.build("test password", "0123456789abcdef")
|
94
|
+
|
71
95
|
assert_match %r{\A\$6\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password")
|
96
|
+
assert_match %r{\A\$6\$0123456789abcdef\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password", "0123456789abcdef")
|
72
97
|
end
|
73
98
|
|
74
99
|
def test_password_generation_with_rounds
|
@@ -88,4 +113,10 @@ class UnixCryptTest < Test::Unit::TestCase
|
|
88
113
|
assert_match %r{\A\$6\$rounds=1000\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, hash
|
89
114
|
assert UnixCrypt.valid?("test password", hash)
|
90
115
|
end
|
116
|
+
|
117
|
+
def test_salt_is_not_longer_than_max_length
|
118
|
+
assert_raise(UnixCrypt::SaltTooLongError) { UnixCrypt::DES.build("test", "123") }
|
119
|
+
assert_raise(UnixCrypt::SaltTooLongError) { UnixCrypt::MD5.build("test", "123456789") }
|
120
|
+
assert_raise(UnixCrypt::SaltTooLongError) { UnixCrypt::SHA256.build("test", "12345678901234567") }
|
121
|
+
end
|
91
122
|
end
|
data/unix-crypt.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unix-crypt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Nesbitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Performs the UNIX crypt(3) algorithm using DES (standard 13 character
|
14
14
|
passwords), MD5 (starting with $1$), SHA256 (starting with $5$) and SHA512 (starting
|
@@ -25,7 +25,11 @@ files:
|
|
25
25
|
- Rakefile
|
26
26
|
- bin/mkunixcrypt
|
27
27
|
- lib/unix_crypt.rb
|
28
|
+
- lib/unix_crypt/base.rb
|
28
29
|
- lib/unix_crypt/command_line.rb
|
30
|
+
- lib/unix_crypt/des.rb
|
31
|
+
- lib/unix_crypt/md5.rb
|
32
|
+
- lib/unix_crypt/sha.rb
|
29
33
|
- test/test_unix_crypt.rb
|
30
34
|
- test/unix_crypt/test_command_line.rb
|
31
35
|
- unix-crypt.gemspec
|
@@ -49,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
53
|
version: '0'
|
50
54
|
requirements: []
|
51
55
|
rubyforge_project:
|
52
|
-
rubygems_version: 2.
|
56
|
+
rubygems_version: 2.1.11
|
53
57
|
signing_key:
|
54
58
|
specification_version: 4
|
55
59
|
summary: Performs the UNIX crypt(3) algorithm using DES, MD5, SHA256 or SHA512
|