unix-crypt 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
-