unix-crypt 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1d23e0cd7fb0edc71372a52d4bd357dbbbbd0a8b
4
+ data.tar.gz: 0b135e10570ae2b629f52f441997cd304663b728
5
+ SHA512:
6
+ metadata.gz: 769ec3053c3e3af385aa5feb6bee79607763a6691cf33362f08a1198296c57cb1c98d8b78472f4eb354ac0e22cba35fc10d680e7bfecb30820d7ef71eb0a1b6b
7
+ data.tar.gz: a175438b7eedfe60a156aaaf003b3956cf70f7f0b054c181b29c9ca5879dbe421253b80d997331a2f095db1dcece3f5ed130df167c8dbaf82d037706cf31d317
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ .*.swp
3
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,10 @@
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 documentation and/or other materials provided with the distribution.
8
+ 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 without specific prior written permission.
9
+
10
+ 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 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,59 @@
1
+ = unix-crypt
2
+
3
+ == Description
4
+
5
+ unix-crypt creates and checks passwords that you'd normally find in an /etc/shadow file on your UNIX box.
6
+
7
+ It's written entirely in Ruby and has no external dependencies.
8
+
9
+ It handles:
10
+ * DES passwords (the standard 13 character password with a 2 character salt)
11
+ * MD5 passwords (starting with $1$)
12
+ * SHA256 passwords (starting with $5$)
13
+ * SHA512 passwords (starting with $6$)
14
+
15
+ This library is compatible with Ruby 1.8.7 and above. Tested on Ruby 2.0.0p0.
16
+
17
+ == Using the command line tool
18
+
19
+ An executable named +mkunixcrypt+ allows you to generate passwords from the command line.
20
+
21
+ Usage: mkunixcrypt [options]
22
+ Encrypts password using the unix-crypt gem
23
+
24
+ Options:
25
+ -h, --hash [HASH] Set hash algorithm [SHA512 (default), SHA256, MD5]
26
+ -p, --password [PASSWORD] Provide password on command line (insecure!)
27
+ -s, --salt [SALT] Provide hash salt
28
+ -r, --rounds [ROUNDS] Set number of hashing rounds (SHA256/SHA512 only)
29
+ --help Show this message
30
+ -v, --version Show version
31
+
32
+ == Using the library
33
+
34
+ You can either validate a password matches its hash:
35
+
36
+ >> UnixCrypt.valid?("Hello world!", "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5")
37
+ => true
38
+
39
+ Or you can generate a new hash, given a password and salt:
40
+
41
+ >> UnixCrypt::SHA256.build("Hello world!", "saltstring")
42
+ => "$5$saltstring$5B8vYYiY.CVt1RlTTf8KbXBH3hsxY/GNooZaBBGWEc5"
43
+
44
+ If a salt is not specified, one will be generated using random data:
45
+
46
+ >> UnixCrypt::SHA256.build("Hello world!")
47
+ => "$5$v.fjb6lucDCZKjcf$90gzpr9HYo0eAeaN8rubElJdUUOcVYjTnGePBRvCgt1"
48
+
49
+ == License
50
+
51
+ Licensed under the BSD license. See LICENSE file for details.
52
+
53
+ == Author
54
+
55
+ * Roger Nesbitt (roger@seriousorange.com)
56
+
57
+ == Contributors
58
+
59
+ * Patrick Wyatt (pat@codeofhonor.com)
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/**/test*.rb']
6
+ t.verbose = true
7
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'unix_crypt'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'unix_crypt'
8
+ end
9
+
10
+ require 'unix_crypt/command_line'
11
+
12
+ begin
13
+ UnixCrypt::CommandLine.new(ARGV).encrypt
14
+ rescue UnixCrypt::CommandLine::Abort => e
15
+ abort e.message
16
+ end
@@ -2,6 +2,8 @@ require 'digest'
2
2
  require 'securerandom'
3
3
 
4
4
  module UnixCrypt
5
+ VERSION = "1.1.1"
6
+
5
7
  def self.valid?(password, string)
6
8
  # Handle the original DES-based crypt(3)
7
9
  return password.crypt(string) == string if string.length == 13
@@ -16,7 +18,11 @@ module UnixCrypt
16
18
  def self.build(password, salt = nil, rounds = nil)
17
19
  salt ||= generate_salt
18
20
 
19
- "$#{identifier}$#{salt}$#{hash(password, salt, rounds)}"
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)
20
26
  end
21
27
 
22
28
  def self.generate_salt
@@ -56,6 +62,10 @@ module UnixCrypt
56
62
 
57
63
  password
58
64
  end
65
+
66
+ def self.rounds_marker(rounds)
67
+ nil
68
+ end
59
69
  end
60
70
 
61
71
  class MD5 < Base
@@ -68,11 +78,9 @@ module UnixCrypt
68
78
  [[0, 6, 12], [1, 7, 13], [2, 8, 14], [3, 9, 15], [4, 10, 5], [nil, nil, 11]]
69
79
  end
70
80
 
71
- def self.hash(password, salt, ignored = nil)
81
+ def self.internal_hash(password, salt, ignored = nil)
72
82
  salt = salt[0..7]
73
83
 
74
- password = prepare_password(password)
75
-
76
84
  b = digest.digest("#{password}#{salt}#{password}")
77
85
  a_string = "#{password}$1$#{salt}#{b * (password.length/length)}#{b[0...password.length % length]}"
78
86
 
@@ -92,22 +100,19 @@ module UnixCrypt
92
100
  input = digest.digest(c_string)
93
101
  end
94
102
 
95
- bit_specified_base64encode(input)
103
+ input
96
104
  end
97
105
  end
98
106
 
99
107
  class SHABase < Base
108
+ protected
100
109
  def self.default_salt_length; 12; end
110
+ def self.default_rounds; 5000; end
101
111
 
102
- def self.hash(password, salt, rounds = nil)
103
- rounds ||= 5000
104
- rounds = 1000 if rounds < 1000
105
- rounds = 999_999_999 if rounds > 999_999_999
106
-
112
+ def self.internal_hash(password, salt, rounds = nil)
113
+ rounds = apply_rounds_bounds(rounds || default_rounds)
107
114
  salt = salt[0..15]
108
115
 
109
- password = prepare_password(password)
110
-
111
116
  b = digest.digest("#{password}#{salt}#{password}")
112
117
 
113
118
  a_string = password + salt + b * (password.length/length) + b[0...password.length % length]
@@ -118,12 +123,12 @@ module UnixCrypt
118
123
  password_length >>= 1
119
124
  end
120
125
 
121
- input = a = digest.digest(a_string)
126
+ input = digest.digest(a_string)
122
127
 
123
128
  dp = digest.digest(password * password.length)
124
129
  p = dp * (password.length/length) + dp[0...password.length % length]
125
130
 
126
- ds = digest.digest(salt * (16 + a.bytes.first))
131
+ ds = digest.digest(salt * (16 + input.bytes.first))
127
132
  s = ds * (salt.length/length) + ds[0...salt.length % length]
128
133
 
129
134
  rounds.times do |index|
@@ -134,7 +139,19 @@ module UnixCrypt
134
139
  input = digest.digest(c_string)
135
140
  end
136
141
 
137
- bit_specified_base64encode(input)
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
138
155
  end
139
156
  end
140
157
 
@@ -0,0 +1,123 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ begin
4
+ require 'io/console'
5
+ rescue LoadError
6
+ $no_io_console = true
7
+ end
8
+
9
+ class UnixCrypt::CommandLine
10
+ Abort = Class.new(StandardError)
11
+
12
+ attr_reader :options
13
+
14
+ def initialize(argv)
15
+ @options = Opts.parse(argv)
16
+ end
17
+
18
+ def encrypt
19
+ if @options.password.nil?
20
+ @options.password = ask_password
21
+ else
22
+ password_warning
23
+ end
24
+
25
+ puts @options.hasher.build(@options.password, @options.salt, @options.rounds)
26
+ clear_string(@options.password)
27
+ end
28
+
29
+ private
30
+ class Opts
31
+ HASHERS = {
32
+ :SHA512 => UnixCrypt::SHA512,
33
+ :SHA256 => UnixCrypt::SHA256,
34
+ :MD5 => UnixCrypt::MD5
35
+ }
36
+
37
+ def self.parse(args)
38
+ options = OpenStruct.new
39
+ options.hashmethod = :SHA512
40
+ options.hasher = HASHERS[options.hashmethod]
41
+ options.password = nil
42
+ options.salt = nil
43
+ options.rounds = nil
44
+ options.leftovers = OptionParser.new do |opts|
45
+ opts.banner = "Usage: #{File.basename $0} [options]"
46
+ opts.separator "Encrypts password using the unix-crypt gem"
47
+ opts.separator ""
48
+ opts.separator "Options:"
49
+
50
+ opts.on("-h", "--hash [HASH]", String, "Set hash algorithm [SHA512 (default), SHA256, MD5]") do |hasher|
51
+ options.hashmethod = hasher.to_s.upcase.to_sym
52
+ options.hasher = HASHERS[options.hashmethod]
53
+ raise Abort, "Invalid hash algorithm for -h/--hash" if options.hasher.nil?
54
+ end
55
+
56
+ opts.on("-p", "--password [PASSWORD]", String, "Provide password on command line (insecure!)") do |password|
57
+ raise Abort, "Invalid password for -p/--password" if password.nil?
58
+ options.password = password
59
+ $0 = $0 # this invocation will get rid of the command line arguments from the process list
60
+ end
61
+
62
+ opts.on("-s", "--salt [SALT]", String, "Provide hash salt") do |salt|
63
+ raise Abort, "Invalid salt for -s/--salt" if salt.nil?
64
+ options.salt = salt
65
+ end
66
+
67
+ opts.on("-r", "--rounds [ROUNDS]", Integer, "Set number of hashing rounds (SHA256/SHA512 only)") do |rounds|
68
+ raise Abort, "Invalid hashing rounds for -r/--rounds" if rounds.nil? || rounds.to_i <= 0
69
+ options.rounds = rounds
70
+ end
71
+
72
+ opts.on_tail("-h", "--help", "Show this message") do
73
+ puts opts
74
+ exit
75
+ end
76
+
77
+ opts.on_tail("-v", "--version", "Show version") do
78
+ puts UnixCrypt::VERSION
79
+ exit
80
+ end
81
+ end.parse!(args)
82
+ options
83
+ end
84
+ end
85
+
86
+ def ask_noecho(message)
87
+ $stderr.print message
88
+ if $no_io_console
89
+ begin
90
+ `stty -echo`
91
+ result = gets
92
+ ensure
93
+ `stty echo`
94
+ end
95
+ else
96
+ result = $stdin.noecho(&:gets)
97
+ end
98
+ $stderr.puts
99
+ result
100
+ end
101
+
102
+ def password_warning
103
+ $stderr.puts "warning: providing a password on the command line is insecure"
104
+ end
105
+
106
+ def clear_string(string)
107
+ string.replace(" " * string.length)
108
+ end
109
+
110
+ def ask_password
111
+ password = ask_noecho("Enter password: ")
112
+ twice = ask_noecho("Verify password: ")
113
+
114
+ if password != twice
115
+ clear_string(password)
116
+ clear_string(twice)
117
+ raise Abort, "Passwords don't match"
118
+ end
119
+
120
+ clear_string(twice)
121
+ password.chomp!
122
+ end
123
+ end
@@ -8,7 +8,7 @@
8
8
  #
9
9
 
10
10
  require 'test/unit'
11
- require '../lib/unix_crypt'
11
+ require File.expand_path('../../lib/unix_crypt', __FILE__)
12
12
 
13
13
  class UnixCryptTest < Test::Unit::TestCase
14
14
  def test_password_validity
@@ -50,9 +50,42 @@ class UnixCryptTest < Test::Unit::TestCase
50
50
  end
51
51
  end
52
52
 
53
+ def test_md5_password_generation
54
+ hash = UnixCrypt::MD5.build("test")
55
+ assert UnixCrypt.valid?("test", hash)
56
+ end
57
+
58
+ def test_sha256_password_generation
59
+ hash = UnixCrypt::SHA256.build("test")
60
+ assert UnixCrypt.valid?("test", hash)
61
+ end
62
+
63
+ def test_sha512_password_generation
64
+ hash = UnixCrypt::SHA512.build("test")
65
+ assert UnixCrypt.valid?("test", hash)
66
+ end
67
+
53
68
  def test_salt_generation
54
69
  assert_match %r{\A\$1\$[a-zA-Z0-9./]{8}\$[a-zA-Z0-9./]{22}\z}, UnixCrypt::MD5.build("test password")
55
70
  assert_match %r{\A\$5\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{43}\z}, UnixCrypt::SHA256.build("test password")
56
71
  assert_match %r{\A\$6\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password")
57
72
  end
73
+
74
+ def test_password_generation_with_rounds
75
+ hash = UnixCrypt::SHA512.build("test password", nil, 5678)
76
+ assert_match %r{\A\$6\$rounds=5678\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, hash
77
+ assert UnixCrypt.valid?("test password", hash)
78
+
79
+ assert_match %r{\A\$6\$rounds=5678\$salted\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password", "salted", 5678)
80
+ end
81
+
82
+ def test_default_rounds_does_not_add_rounds_marker
83
+ assert_match %r{\A\$6\$salted\$[a-zA-Z0-9./]{86}\z}, UnixCrypt::SHA512.build("test password", "salted", 5000) # the default number of rounds
84
+ end
85
+
86
+ def test_rounds_bounds
87
+ hash = UnixCrypt::SHA512.build("test password", nil, 567)
88
+ assert_match %r{\A\$6\$rounds=1000\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\z}, hash
89
+ assert UnixCrypt.valid?("test password", hash)
90
+ end
58
91
  end
@@ -0,0 +1,88 @@
1
+ require 'test/unit'
2
+ require File.expand_path('../../../lib/unix_crypt', __FILE__)
3
+ require File.expand_path('../../../lib/unix_crypt/command_line', __FILE__)
4
+
5
+ class CommandLineTest < Test::Unit::TestCase
6
+ class CaptureIO
7
+ def initialize(name, buffer, input = [])
8
+ @name = name
9
+ @buffer = buffer
10
+ @input = input
11
+ end
12
+
13
+ def noecho
14
+ yield self
15
+ end
16
+
17
+ def gets
18
+ @buffer << [@name, @input.first.dup]
19
+ @input.shift
20
+ end
21
+
22
+ def write(data)
23
+ @buffer << [@name, data]
24
+ end
25
+
26
+ def print(data)
27
+ write data
28
+ end
29
+
30
+ def puts(data = "")
31
+ write "#{data}\n"
32
+ end
33
+
34
+ def self.redirect(input = [])
35
+ buffer = []
36
+ $stdin = new("stdin", buffer, input)
37
+ $stdout = new("stdout", buffer)
38
+ $stderr = new("stderr", buffer)
39
+ yield
40
+ buffer
41
+ ensure
42
+ $stdin = STDIN
43
+ $stdout = STDOUT
44
+ $stderr = STDERR
45
+ end
46
+ end
47
+
48
+ def test_no_parameter_password_creation
49
+ result = CaptureIO.redirect(["hello\n", "hello\n"]) do
50
+ UnixCrypt::CommandLine.new([]).encrypt
51
+ end
52
+
53
+ expected = [
54
+ ["stderr", "Enter password: "],
55
+ ["stdin", "hello\n"],
56
+ ["stderr", "\n"],
57
+ ["stderr", "Verify password: "],
58
+ ["stdin", "hello\n"],
59
+ ["stderr", "\n"]
60
+ ]
61
+
62
+ assert_equal expected, result[0..-2]
63
+
64
+ channel, password = result[-1]
65
+ assert_equal "stdout", channel
66
+ assert_match %r{\A\$6\$[a-zA-Z0-9./]{16}\$[a-zA-Z0-9./]{86}\n\z}, password
67
+
68
+ assert UnixCrypt.valid?("hello", password)
69
+ end
70
+
71
+ def test_parameters_provided_password_creation
72
+ result = CaptureIO.redirect do
73
+ UnixCrypt::CommandLine.new(%w(-h sha256 -p hello -s salty -r 1234)).encrypt
74
+ end
75
+
76
+ expected = [
77
+ ["stderr", "warning: providing a password on the command line is insecure\n"]
78
+ ]
79
+
80
+ assert_equal expected, result[0..-2]
81
+
82
+ channel, password = result[-1]
83
+ assert_equal "stdout", channel
84
+ assert_match %r{\A\$5\$rounds=1234\$salty\$[a-zA-Z0-9./]{43}\n\z}, password
85
+
86
+ assert UnixCrypt.valid?("hello", password)
87
+ end
88
+ end
@@ -0,0 +1,17 @@
1
+ require './lib/unix_crypt'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = 'unix-crypt'
5
+ s.version = UnixCrypt::VERSION
6
+ s.summary = "Performs the UNIX crypt(3) algorithm using DES, MD5, SHA256 or SHA512"
7
+ s.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$)}
8
+ s.files = `git ls-files`.split($\)
9
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
10
+ s.executables = ["mkunixcrypt"]
11
+ s.require_path = 'lib'
12
+ s.has_rdoc = false
13
+ s.author = "Roger Nesbitt"
14
+ s.email = "roger@seriousorange.com"
15
+ s.homepage = "https://github.com/mogest/unix-crypt"
16
+ s.license = "BSD"
17
+ end
metadata CHANGED
@@ -1,48 +1,58 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unix-crypt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
5
- prerelease:
4
+ version: 1.1.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Roger Nesbitt
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-02 00:00:00.000000000 Z
11
+ date: 2013-09-23 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: Performs the UNIX crypt(3) algorithm using DES (standard 13 character
15
14
  passwords), MD5 (starting with $1$), SHA256 (starting with $5$) and SHA512 (starting
16
15
  with $6$)
17
16
  email: roger@seriousorange.com
18
- executables: []
17
+ executables:
18
+ - mkunixcrypt
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
21
  files:
22
+ - .gitignore
23
+ - LICENSE
24
+ - README.rdoc
25
+ - Rakefile
26
+ - bin/mkunixcrypt
22
27
  - lib/unix_crypt.rb
23
- - test/unix_crypt_test.rb
24
- homepage:
25
- licenses: []
28
+ - lib/unix_crypt/command_line.rb
29
+ - test/test_unix_crypt.rb
30
+ - test/unix_crypt/test_command_line.rb
31
+ - unix-crypt.gemspec
32
+ homepage: https://github.com/mogest/unix-crypt
33
+ licenses:
34
+ - BSD
35
+ metadata: {}
26
36
  post_install_message:
27
37
  rdoc_options: []
28
38
  require_paths:
29
39
  - lib
30
40
  required_ruby_version: !ruby/object:Gem::Requirement
31
- none: false
32
41
  requirements:
33
- - - ! '>='
42
+ - - '>='
34
43
  - !ruby/object:Gem::Version
35
44
  version: '0'
36
45
  required_rubygems_version: !ruby/object:Gem::Requirement
37
- none: false
38
46
  requirements:
39
- - - ! '>='
47
+ - - '>='
40
48
  - !ruby/object:Gem::Version
41
49
  version: '0'
42
50
  requirements: []
43
51
  rubyforge_project:
44
- rubygems_version: 1.8.23
52
+ rubygems_version: 2.0.2
45
53
  signing_key:
46
- specification_version: 3
54
+ specification_version: 4
47
55
  summary: Performs the UNIX crypt(3) algorithm using DES, MD5, SHA256 or SHA512
48
- test_files: []
56
+ test_files:
57
+ - test/test_unix_crypt.rb
58
+ - test/unix_crypt/test_command_line.rb