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.
Files changed (3) hide show
  1. data/lib/unix_crypt.rb +32 -4
  2. data/test/unix_crypt_test.rb +8 -0
  3. metadata +21 -41
@@ -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.base64encode(input)
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
- base64encode(input)
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
- base64encode(input)
137
+ bit_specified_base64encode(input)
110
138
  end
111
139
  end
112
140
 
@@ -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
- hash: 19
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
- description: Performs the UNIX crypt(3) algorithm using DES (standard 13 character passwords), MD5 (starting with $1$), SHA256 (starting with $5$) and SHA512 (starting with $6$)
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
- hash: 3
48
- segments:
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
- hash: 3
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.6.2
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
-