unix-crypt 1.0.2 → 1.1.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.
- data/lib/unix_crypt.rb +32 -4
- data/test/unix_crypt_test.rb +8 -0
- metadata +21 -41
data/lib/unix_crypt.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'digest'
|
2
|
+
require 'securerandom'
|
2
3
|
|
3
4
|
module UnixCrypt
|
4
5
|
def self.valid?(password, string)
|
@@ -6,17 +7,26 @@ module UnixCrypt
|
|
6
7
|
return password.crypt(string) == string if string.length == 13
|
7
8
|
|
8
9
|
return false unless m = string.match(/\A\$([156])\$(?:rounds=(\d+)\$)?(.+)\$(.+)/)
|
10
|
+
|
9
11
|
hash = IDENTIFIER_MAPPINGS[m[1]].hash(password, m[3], m[2] && m[2].to_i)
|
10
12
|
hash == m[4]
|
11
13
|
end
|
12
14
|
|
13
15
|
class Base
|
14
|
-
def self.build(password, salt, rounds = nil)
|
16
|
+
def self.build(password, salt = nil, rounds = nil)
|
17
|
+
salt ||= generate_salt
|
18
|
+
|
15
19
|
"$#{identifier}$#{salt}$#{hash(password, salt, rounds)}"
|
16
20
|
end
|
17
21
|
|
22
|
+
def self.generate_salt
|
23
|
+
# Generates a random salt using the same character set as the base64 encoding
|
24
|
+
# used by the hash encoder.
|
25
|
+
SecureRandom.base64(default_salt_length).gsub("=", "").tr("+", ".")
|
26
|
+
end
|
27
|
+
|
18
28
|
protected
|
19
|
-
def self.
|
29
|
+
def self.bit_specified_base64encode(input)
|
20
30
|
b64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
21
31
|
input = input.bytes.to_a
|
22
32
|
output = ""
|
@@ -35,11 +45,23 @@ module UnixCrypt
|
|
35
45
|
remainder = 0 if remainder == 3
|
36
46
|
output[0..-1-remainder]
|
37
47
|
end
|
48
|
+
|
49
|
+
def self.prepare_password(password)
|
50
|
+
# For Ruby 1.9+, convert the password to UTF-8, then treat that new string
|
51
|
+
# as binary for the digest methods.
|
52
|
+
if password.respond_to?(:encode)
|
53
|
+
password = password.encode("UTF-8")
|
54
|
+
password.force_encoding("ASCII-8BIT")
|
55
|
+
end
|
56
|
+
|
57
|
+
password
|
58
|
+
end
|
38
59
|
end
|
39
60
|
|
40
61
|
class MD5 < Base
|
41
62
|
def self.digest; Digest::MD5; end
|
42
63
|
def self.length; 16; end
|
64
|
+
def self.default_salt_length; 6; end
|
43
65
|
def self.identifier; 1; end
|
44
66
|
|
45
67
|
def self.byte_indexes
|
@@ -49,6 +71,8 @@ module UnixCrypt
|
|
49
71
|
def self.hash(password, salt, ignored = nil)
|
50
72
|
salt = salt[0..7]
|
51
73
|
|
74
|
+
password = prepare_password(password)
|
75
|
+
|
52
76
|
b = digest.digest("#{password}#{salt}#{password}")
|
53
77
|
a_string = "#{password}$1$#{salt}#{b * (password.length/length)}#{b[0...password.length % length]}"
|
54
78
|
|
@@ -68,11 +92,13 @@ module UnixCrypt
|
|
68
92
|
input = digest.digest(c_string)
|
69
93
|
end
|
70
94
|
|
71
|
-
|
95
|
+
bit_specified_base64encode(input)
|
72
96
|
end
|
73
97
|
end
|
74
98
|
|
75
99
|
class SHABase < Base
|
100
|
+
def self.default_salt_length; 12; end
|
101
|
+
|
76
102
|
def self.hash(password, salt, rounds = nil)
|
77
103
|
rounds ||= 5000
|
78
104
|
rounds = 1000 if rounds < 1000
|
@@ -80,6 +106,8 @@ module UnixCrypt
|
|
80
106
|
|
81
107
|
salt = salt[0..15]
|
82
108
|
|
109
|
+
password = prepare_password(password)
|
110
|
+
|
83
111
|
b = digest.digest("#{password}#{salt}#{password}")
|
84
112
|
|
85
113
|
a_string = password + salt + b * (password.length/length) + b[0...password.length % length]
|
@@ -106,7 +134,7 @@ module UnixCrypt
|
|
106
134
|
input = digest.digest(c_string)
|
107
135
|
end
|
108
136
|
|
109
|
-
|
137
|
+
bit_specified_base64encode(input)
|
110
138
|
end
|
111
139
|
end
|
112
140
|
|
data/test/unix_crypt_test.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: utf-8
|
1
2
|
#
|
2
3
|
# MD5 test cases constructed by Mark Johnston, taken from
|
3
4
|
# http://code.activestate.com/recipes/325204-passwd-file-compatible-1-md5-crypt/
|
@@ -23,6 +24,7 @@ class UnixCryptTest < Test::Unit::TestCase
|
|
23
24
|
[nil, '____sixteen_____', '$1$dL3xbVZI$kkgqhCanLdxODGq14g/tW1'],
|
24
25
|
[nil, '____seventeen____', '$1$NaH5na7J$j7y8Iss0hcRbu3kzoJs5V.'],
|
25
26
|
[nil, '__________thirty-three___________', '$1$HO7Q6vzJ$yGwp2wbL5D7eOVzOmxpsy.'],
|
27
|
+
[nil, 'Pässword', '$1$NaH5na7J$MvnEHcxaKZzgBk8QdjdAQ0'],
|
26
28
|
|
27
29
|
# SHA256
|
28
30
|
["$5$saltstring", "Hello world!", "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5"],
|
@@ -47,4 +49,10 @@ class UnixCryptTest < Test::Unit::TestCase
|
|
47
49
|
assert UnixCrypt.valid?(password, expected), "Password '#{password}' (index #{index}) failed"
|
48
50
|
end
|
49
51
|
end
|
52
|
+
|
53
|
+
def test_salt_generation
|
54
|
+
assert_match %r{\A\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{22}\z}, UnixCrypt::MD5.build("test password")
|
55
|
+
assert_match %r{\A\$5\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{43}\z}, UnixCrypt::SHA256.build("test password")
|
56
|
+
assert_match %r{\A\$6\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password")
|
57
|
+
end
|
50
58
|
end
|
metadata
CHANGED
@@ -1,68 +1,48 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: unix-crypt
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
- 2
|
10
|
-
version: 1.0.2
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Roger Nesbitt
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2012-03-22 00:00:00 +13:00
|
19
|
-
default_executable:
|
12
|
+
date: 2013-04-02 00:00:00.000000000 Z
|
20
13
|
dependencies: []
|
21
|
-
|
22
|
-
|
14
|
+
description: Performs the UNIX crypt(3) algorithm using DES (standard 13 character
|
15
|
+
passwords), MD5 (starting with $1$), SHA256 (starting with $5$) and SHA512 (starting
|
16
|
+
with $6$)
|
23
17
|
email: roger@seriousorange.com
|
24
18
|
executables: []
|
25
|
-
|
26
19
|
extensions: []
|
27
|
-
|
28
20
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
21
|
+
files:
|
31
22
|
- lib/unix_crypt.rb
|
32
23
|
- test/unix_crypt_test.rb
|
33
|
-
has_rdoc: true
|
34
24
|
homepage:
|
35
25
|
licenses: []
|
36
|
-
|
37
26
|
post_install_message:
|
38
27
|
rdoc_options: []
|
39
|
-
|
40
|
-
require_paths:
|
28
|
+
require_paths:
|
41
29
|
- lib
|
42
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
31
|
none: false
|
44
|
-
requirements:
|
45
|
-
- -
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
|
48
|
-
|
49
|
-
- 0
|
50
|
-
version: "0"
|
51
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
37
|
none: false
|
53
|
-
requirements:
|
54
|
-
- -
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
|
57
|
-
segments:
|
58
|
-
- 0
|
59
|
-
version: "0"
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
60
42
|
requirements: []
|
61
|
-
|
62
43
|
rubyforge_project:
|
63
|
-
rubygems_version: 1.
|
44
|
+
rubygems_version: 1.8.23
|
64
45
|
signing_key:
|
65
46
|
specification_version: 3
|
66
47
|
summary: Performs the UNIX crypt(3) algorithm using DES, MD5, SHA256 or SHA512
|
67
48
|
test_files: []
|
68
|
-
|