bcrypt-ruby 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bcrypt-ruby might be problematic. Click here for more details.

@@ -0,0 +1,8 @@
1
+ 1.0.0 Feb 27 2007
2
+ - Initial release.
3
+
4
+ 2.0.0 Mar 07 2007
5
+ - Removed BCrypt::Password#exactly_equals -- use BCrypt::Password#eql? instead.
6
+ - Added BCrypt::Password#is_password?.
7
+ - Refactored out BCrypt::Internals into more useful BCrypt::Engine.
8
+ - Added validation of secrets -- nil is not healthy.
data/Rakefile CHANGED
@@ -4,9 +4,10 @@ require 'rake/gempackagetask'
4
4
  require 'rake/contrib/rubyforgepublisher'
5
5
  require 'rake/clean'
6
6
  require 'rake/rdoctask'
7
+ require "benchmark"
7
8
 
8
9
  PKG_NAME = "bcrypt-ruby"
9
- PKG_VERSION = "1.0.0"
10
+ PKG_VERSION = "2.0.0"
10
11
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
11
12
  PKG_FILES = FileList[
12
13
  '[A-Z]*',
@@ -16,6 +17,14 @@ PKG_FILES = FileList[
16
17
  'ext/*.h',
17
18
  'ext/*.rb'
18
19
  ]
20
+ CLEAN.include(
21
+ "ext/*.o",
22
+ "ext/*.bundle",
23
+ "ext/*.so"
24
+ )
25
+ CLOBBER.include(
26
+ "doc/coverage"
27
+ )
19
28
 
20
29
  task :default => [:spec]
21
30
 
@@ -24,30 +33,34 @@ Spec::Rake::SpecTask.new do |t|
24
33
  t.spec_files = FileList['spec/**/*_spec.rb']
25
34
  t.spec_opts = ['--color','--backtrace','--diff']
26
35
  t.rcov = true
27
- t.rcov_dir = 'doc/output/coverage'
36
+ t.rcov_dir = 'doc/coverage'
28
37
  t.rcov_opts = ['--exclude', 'spec\/spec,spec\/.*_spec.rb']
29
38
  end
30
39
 
31
- desc "Run all specs and store html output in doc/output/report.html"
32
- Spec::Rake::SpecTask.new('spec_html') do |t|
33
- t.spec_files = FileList['spec/**/*_spec.rb']
34
- t.spec_opts = ['--diff','--format html','--backtrace','--out doc/output/report.html']
40
+ namespace :spec do
41
+ desc "Run all specs and store html output in doc/specs.html"
42
+ Spec::Rake::SpecTask.new('html') do |t|
43
+ t.spec_files = FileList['spec/**/*_spec.rb']
44
+ t.spec_opts = ['--diff','--format html','--backtrace','--out doc/specs.html']
45
+ end
35
46
  end
36
47
 
37
48
  desc 'Generate RDoc'
38
49
  rd = Rake::RDocTask.new do |rdoc|
39
- rdoc.rdoc_dir = 'doc/output/rdoc'
50
+ rdoc.rdoc_dir = 'doc/rdoc'
40
51
  rdoc.options << '--title' << 'bcrypt-ruby' << '--line-numbers' << '--inline-source' << '--main' << 'README'
41
52
  rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE']
42
- rdoc.rdoc_files.include('README', 'COPYING', 'lib/**/*.rb')
53
+ rdoc.rdoc_files.include('README', 'COPYING', 'CHANGELOG', 'lib/**/*.rb')
43
54
  end
44
55
 
45
56
  spec = Gem::Specification.new do |s|
46
57
  s.name = PKG_NAME
47
58
  s.version = PKG_VERSION
48
- s.summary = "Blah."
59
+ s.summary = "OpenBSD's bcrypt() password hashing algorithm."
49
60
  s.description = <<-EOF
50
- Woot.
61
+ bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project
62
+ for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling
63
+ passwords.
51
64
  EOF
52
65
 
53
66
  s.files = PKG_FILES.to_a
@@ -71,8 +84,19 @@ Rake::GemPackageTask.new(spec) do |pkg|
71
84
  pkg.need_tar = true
72
85
  end
73
86
 
74
- task :compile do
87
+ task :compile => [:clean] do
75
88
  Dir.chdir('./ext')
76
89
  system "ruby extconf.rb"
77
90
  system "make"
78
- end
91
+ end
92
+
93
+ task :benchmark do
94
+ TESTS = 100
95
+ TEST_PWD = "this is a test"
96
+ require "lib/bcrypt"
97
+ Benchmark.bmbm do |results|
98
+ 4.upto(10) do |n|
99
+ results.report("cost #{n}:") { TESTS.times { BCrypt::Password.create(TEST_PWD, :cost => n) } }
100
+ end
101
+ end
102
+ end
@@ -4,7 +4,7 @@ char *bcrypt_gensalt(u_int8_t, u_int8_t *);
4
4
  char *bcrypt(const char *, const char *);
5
5
 
6
6
  VALUE mBCrypt;
7
- VALUE mBCryptInternals;
7
+ VALUE cBCryptEngine;
8
8
 
9
9
  /* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+.
10
10
  */
@@ -21,8 +21,8 @@ static VALUE bc_crypt(VALUE self, VALUE key, VALUE salt) {
21
21
  /* Create the BCrypt and BCrypt::Internals modules, and populate them with methods. */
22
22
  void Init_bcrypt_ext(){
23
23
  mBCrypt = rb_define_module("BCrypt");
24
- mBCryptInternals = rb_define_module_under(mBCrypt, "Internals");
24
+ cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject);
25
25
 
26
- rb_define_method(mBCryptInternals, "__bc_salt", bc_salt, 2);
27
- rb_define_method(mBCryptInternals, "__bc_crypt", bc_crypt, 2);
26
+ rb_define_singleton_method(cBCryptEngine, "__bc_salt", bc_salt, 2);
27
+ rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 2);
28
28
  }
@@ -1,40 +1,46 @@
1
1
  # A wrapper for OpenBSD's bcrypt/crypt_blowfish password-hashing algorithm.
2
2
 
3
- $: << "ext"
4
- require "bcrypt_ext"
3
+ require "ext/bcrypt_ext"
5
4
  require "openssl"
6
5
 
7
6
  # A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
8
7
  # hashing passwords.
9
- module BCrypt
8
+ module BCrypt
10
9
  module Errors # :nodoc:
11
- # The salt parameter provided to bcrypt() is invalid.
12
- class InvalidSalt < Exception; end
13
- # The hash parameter provided to bcrypt() is invalid.
14
- class InvalidHash < Exception; end
15
- # The cost parameter provided to bcrypt() is invalid.
16
- class InvalidCost < Exception; end
10
+ class InvalidSalt < Exception; end # The salt parameter provided to bcrypt() is invalid.
11
+ class InvalidHash < Exception; end # The hash parameter provided to bcrypt() is invalid.
12
+ class InvalidCost < Exception; end # The cost parameter provided to bcrypt() is invalid.
13
+ class InvalidSecret < Exception; end # The secret parameter provided to bcrypt() is invalid.
17
14
  end
18
15
 
19
- # The default computational expense parameter.
20
- DEFAULT_COST = 10
21
-
22
- module Internals # :nodoc:
23
- # contains the C-level methods and other internally bits
24
-
25
- # Maximum length of salts.
16
+ # A Ruby wrapper for the bcrypt() extension calls.
17
+ class Engine
18
+ # The default computational expense parameter.
19
+ DEFAULT_COST = 10
20
+ # Maximum possible size of bcrypt() salts.
26
21
  MAX_SALT_LENGTH = 16
27
22
 
28
- # Wrap this in some error checking otherwise it'll explode.
29
- def _bc_crypt(key, salt)
30
- if valid_salt?(salt)
31
- __bc_crypt(key, salt)
23
+ # C-level routines which, if they don't get the right input, will crash the
24
+ # hell out of the Ruby process.
25
+ private_class_method :__bc_salt
26
+ private_class_method :__bc_crypt
27
+
28
+ # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
29
+ # a bcrypt() password hash.
30
+ def self.hash(secret, salt)
31
+ if valid_secret?(secret)
32
+ if valid_salt?(salt)
33
+ __bc_crypt(secret, salt)
34
+ else
35
+ raise Errors::InvalidSalt.new("invalid salt")
36
+ end
32
37
  else
33
- raise Errors::InvalidSalt.new("invalid salt")
38
+ raise Errors::InvalidSecret.new("invalid secret")
34
39
  end
35
40
  end
36
41
 
37
- def _bc_salt(cost = DEFAULT_COST)
42
+ # Generates a random salt with a given computational cost.
43
+ def self.generate_salt(cost = DEFAULT_COST)
38
44
  if cost.to_i > 0
39
45
  __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
40
46
  else
@@ -42,29 +48,35 @@ module BCrypt
42
48
  end
43
49
  end
44
50
 
45
- def valid_salt?(s)
46
- s =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
51
+ # Returns true if +salt+ is a valid bcrypt() salt, false if not.
52
+ def self.valid_salt?(salt)
53
+ salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/
47
54
  end
48
- end
49
-
50
- # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
51
- #
52
- # Example:
53
- #
54
- # BCrypt.calibrate(200) #=> 10
55
- # BCrypt.calibrate(1000) #=> 12
56
- #
57
- # # should take less than 200ms
58
- # BCrypt::Password.create("woo", :cost => 10)
59
- #
60
- # # should take less than 1000ms
61
- # BCrypt::Password.create("woo", :cost => 12)
62
- def self.calibrate(upper_time_limit_in_ms)
63
- 40.times do |i|
64
- start_time = Time.now
65
- Password.create("testing testing", :cost => i+1)
66
- end_time = Time.now - start_time
67
- return i if end_time * 1_000 > upper_time_limit_in_ms
55
+
56
+ # Returns true if +secret+ is a valid bcrypt() secret, false if not.
57
+ def self.valid_secret?(secret)
58
+ !secret.nil?
59
+ end
60
+
61
+ # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
62
+ #
63
+ # Example:
64
+ #
65
+ # BCrypt.calibrate(200) #=> 10
66
+ # BCrypt.calibrate(1000) #=> 12
67
+ #
68
+ # # should take less than 200ms
69
+ # BCrypt::Password.create("woo", :cost => 10)
70
+ #
71
+ # # should take less than 1000ms
72
+ # BCrypt::Password.create("woo", :cost => 12)
73
+ def self.calibrate(upper_time_limit_in_ms)
74
+ 40.times do |i|
75
+ start_time = Time.now
76
+ Password.create("testing testing", :cost => i+1)
77
+ end_time = Time.now - start_time
78
+ return i if end_time * 1_000 > upper_time_limit_in_ms
79
+ end
68
80
  end
69
81
  end
70
82
 
@@ -109,11 +121,9 @@ module BCrypt
109
121
  # Example:
110
122
  #
111
123
  # @password = BCrypt::Password.create("my secret", :cost => 13)
112
- def create(secret, options = { :cost => DEFAULT_COST })
113
- Password.new(_bc_crypt(secret, _bc_salt(options[:cost])))
124
+ def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST })
125
+ Password.new(BCrypt::Engine.hash(secret, BCrypt::Engine.generate_salt(options[:cost])))
114
126
  end
115
- private
116
- include Internals
117
127
  end
118
128
 
119
129
  # Initializes a BCrypt::Password instance with the data from a stored hash.
@@ -126,12 +136,11 @@ module BCrypt
126
136
  end
127
137
  end
128
138
 
129
- alias_method :exactly_equals, :==
130
-
131
139
  # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
132
140
  def ==(secret)
133
- self.exactly_equals(self.class._bc_crypt(secret, @salt))
141
+ super(BCrypt::Engine.hash(secret, @salt))
134
142
  end
143
+ alias_method :is_password?, :==
135
144
 
136
145
  private
137
146
  # Returns true if +h+ is a valid hash.
@@ -1,35 +1,49 @@
1
1
  require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
2
 
3
+ context "The BCrypt engine" do
4
+ specify "should calculate the optimal cost factor to fit in a specific time" do
5
+ first = BCrypt::Engine.calibrate(100)
6
+ second = BCrypt::Engine.calibrate(300)
7
+ second.should >(first + 1)
8
+ end
9
+ end
10
+
3
11
  context "Generating BCrypt salts" do
4
- include BCrypt::Internals
5
12
 
6
13
  specify "should produce strings" do
7
- _bc_salt.should be_an_instance_of(String)
14
+ BCrypt::Engine.generate_salt.should be_an_instance_of(String)
8
15
  end
9
16
 
10
17
  specify "should produce random data" do
11
- _bc_salt.should_not equal(_bc_salt)
18
+ BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt)
12
19
  end
13
20
 
14
21
  specify "should raise a InvalidCostError if the cost parameter isn't numeric" do
15
- lambda { _bc_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
22
+ lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost)
23
+ end
24
+
25
+ specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do
26
+ lambda { BCrypt::Engine.generate_salt(-1) }.should raise_error(BCrypt::Errors::InvalidCost)
16
27
  end
17
28
  end
18
29
 
19
30
  context "Generating BCrypt hashes" do
20
- include BCrypt::Internals
21
31
 
22
32
  setup do
23
- @salt = _bc_salt
33
+ @salt = BCrypt::Engine.generate_salt(4)
24
34
  @password = "woo"
25
35
  end
26
36
 
27
37
  specify "should produce a string" do
28
- _bc_crypt(@password, @salt).should be_an_instance_of(String)
38
+ BCrypt::Engine.hash(@password, @salt).should be_an_instance_of(String)
39
+ end
40
+
41
+ specify "should raise an InvalidSalt error if the salt is invalid" do
42
+ lambda { BCrypt::Engine.hash(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
29
43
  end
30
44
 
31
- specify "should raise an InvalidSaltError if the salt is invalid" do
32
- lambda { _bc_crypt(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt)
45
+ specify "should raise an InvalidSecret error if the secret is invalid" do
46
+ lambda { BCrypt::Engine.hash(nil, @salt) }.should raise_error(BCrypt::Errors::InvalidSecret)
33
47
  end
34
48
 
35
49
  specify "should be interoperable with other implementations" do
@@ -42,7 +56,7 @@ context "Generating BCrypt hashes" do
42
56
  ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"]
43
57
  ]
44
58
  for secret, salt, test_vector in test_vectors
45
- _bc_crypt(secret, salt).should eql(test_vector)
59
+ BCrypt::Engine.hash(secret, salt).should eql(test_vector)
46
60
  end
47
61
  end
48
62
  end
@@ -4,7 +4,7 @@ context "Creating a hashed password" do
4
4
 
5
5
  setup do
6
6
  @secret = "wheedle"
7
- @password = BCrypt::Password.create(@secret)
7
+ @password = BCrypt::Password.create(@secret, :cost => 4)
8
8
  end
9
9
 
10
10
  specify "should return a BCrypt::Password" do
@@ -15,6 +15,9 @@ context "Creating a hashed password" do
15
15
  lambda { BCrypt::Password.new(@password) }.should_not raise_error
16
16
  end
17
17
 
18
+ specify "should raise an InvalidSecret exception if the secret is nil" do
19
+ lambda { BCrypt::Password.create(nil) }.should raise_error(BCrypt::Errors::InvalidSecret)
20
+ end
18
21
  end
19
22
 
20
23
  context "Reading a hashed password" do
metadata CHANGED
@@ -3,15 +3,15 @@ rubygems_version: 0.9.1
3
3
  specification_version: 1
4
4
  name: bcrypt-ruby
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.0
7
- date: 2007-02-27 00:00:00 -08:00
8
- summary: Blah.
6
+ version: 2.0.0
7
+ date: 2007-03-07 00:00:00 -08:00
8
+ summary: OpenBSD's bcrypt() password hashing algorithm.
9
9
  require_paths:
10
10
  - lib
11
11
  email: coda.hale@gmail.com
12
12
  homepage: http://bcrypt-ruby.rubyforge.org
13
13
  rubyforge_project: bcrypt-ruby
14
- description: Woot.
14
+ description: bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project for hashing passwords. bcrypt-ruby provides a simple, humane wrapper for safely handling passwords.
15
15
  autorequire: bcrypt
16
16
  default_executable:
17
17
  bindir: bin
@@ -29,13 +29,13 @@ post_install_message:
29
29
  authors:
30
30
  - - Coda Hale
31
31
  files:
32
+ - CHANGELOG
32
33
  - COPYING
33
34
  - Rakefile
34
35
  - README
35
36
  - lib/bcrypt.rb
36
37
  - spec/spec_helper.rb
37
- - spec/bcrypt/bcrypt_spec.rb
38
- - spec/bcrypt/internals_spec.rb
38
+ - spec/bcrypt/engine_spec.rb
39
39
  - spec/bcrypt/password_spec.rb
40
40
  - ext/bcrypt.c
41
41
  - ext/bcrypt_ext.c
@@ -54,6 +54,7 @@ rdoc_options:
54
54
  extra_rdoc_files:
55
55
  - README
56
56
  - COPYING
57
+ - CHANGELOG
57
58
  - lib/bcrypt.rb
58
59
  executables: []
59
60
 
@@ -1,9 +0,0 @@
1
- require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
-
3
- context "BCrypt" do
4
- specify "should calculate the optimal cost factor to fit in a specific time" do
5
- first = BCrypt.calibrate(100)
6
- second = BCrypt.calibrate(300)
7
- second.should >(first + 1)
8
- end
9
- end