Commit 03c07302c3f65146490bf60e9a39bd16fe1dcf67
Got rid of BCrypt::Internals in favor of an Engine singleton class with accurate method names.
git-svn-id: http://bcrypt-ruby.rubyforge.org/svn/trunk@8 b1e0f299-433e-4bb3-9895-84128a6cfb6acodahale committed on 2/28/2007, 2:43:14 AM
Parent: e61acd33cc691daece9560ddd9e2367cfc31fab9
Files changed
ext/bcrypt_ext.c | changed |
lib/bcrypt.rb | changed |
spec/bcrypt/internals_spec.rb | deleted |
spec/bcrypt/engine_spec.rb | added |
ext/bcrypt_ext.c | ||
---|---|---|
@@ -3,9 +3,9 @@ | ||
3 | 3 … | 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 … | */ |
11 | 11 … | static VALUE bc_salt(VALUE self, VALUE cost, VALUE seed) { |
@@ -20,9 +20,9 @@ | ||
20 | 20 … | |
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 … | } |
lib/bcrypt.rb | ||
---|---|---|
@@ -15,36 +15,40 @@ | ||
15 | 15 … | # The cost parameter provided to bcrypt() is invalid. |
16 | 16 … | class InvalidCost < Exception; end |
17 | 17 … | end |
18 | 18 … | |
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 | |
19 … | + # A Ruby wrapper for the bcrypt() extension calls. | |
20 … | + class Engine | |
21 … | + DEFAULT_COST = 10 # The default computational expense parameter. | |
22 … | + MAX_SALT_LENGTH = 16 # Maximum possible size of bcrypt() salts. | |
24 | 23 … | |
25 | - # Maximum length of salts. | |
26 | - MAX_SALT_LENGTH = 16 | |
24 … | + # C-level routines which, if they don't get the right input, will crash the | |
25 … | + # hell out of the Ruby process. | |
26 … | + private_class_method :__bc_salt | |
27 … | + private_class_method :__bc_crypt | |
27 | 28 … | |
28 | - # Wrap this in some error checking otherwise it'll explode. | |
29 | - def _bc_crypt(key, salt) | |
29 … | + # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates | |
30 … | + # a bcrypt() password hash. | |
31 … | + def self.hash(secret, salt) | |
30 | 32 … | if valid_salt?(salt) |
31 | - __bc_crypt(key, salt) | |
33 … | + __bc_crypt(secret, salt) | |
32 | 34 … | else |
33 | 35 … | raise Errors::InvalidSalt.new("invalid salt") |
34 | 36 … | end |
35 | 37 … | end |
36 | 38 … | |
37 | - def _bc_salt(cost = DEFAULT_COST) | |
39 … | + # Generates a random salt with a given computational cost. | |
40 … | + def self.generate_salt(cost = DEFAULT_COST) | |
38 | 41 … | if cost.to_i > 0 |
39 | 42 … | __bc_salt(cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH)) |
40 | 43 … | else |
41 | 44 … | raise Errors::InvalidCost.new("cost must be numeric and > 0") |
42 | 45 … | end |
43 | 46 … | end |
44 | 47 … | |
45 | - def valid_salt?(s) | |
46 | - s =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/ | |
48 … | + # Returns true if +salt+ is a valid bcrypt() salt, false if not. | |
49 … | + def self.valid_salt?(salt) | |
50 … | + salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/ | |
47 | 51 … | end |
48 | 52 … | end |
49 | 53 … | |
50 | 54 … | # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+. |
@@ -108,13 +112,11 @@ | ||
108 | 112 … | # |
109 | 113 … | # Example: |
110 | 114 … | # |
111 | 115 … | # @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]))) | |
116 … | + def create(secret, options = { :cost => BCrypt::Engine::DEFAULT_COST }) | |
117 … | + Password.new(BCrypt::Engine.hash(secret, BCrypt::Engine.generate_salt(options[:cost]))) | |
114 | 118 … | end |
115 | - private | |
116 | - include Internals | |
117 | 119 … | end |
118 | 120 … | |
119 | 121 … | # Initializes a BCrypt::Password instance with the data from a stored hash. |
120 | 122 … | def initialize(raw_hash) |
@@ -129,9 +131,9 @@ | ||
129 | 131 … | alias_method :exactly_equals, :== |
130 | 132 … | |
131 | 133 … | # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise. |
132 | 134 … | def ==(secret) |
133 | - self.exactly_equals(self.class._bc_crypt(secret, @salt)) | |
135 … | + self.exactly_equals(BCrypt::Engine.hash(secret, @salt)) | |
134 | 136 … | end |
135 | 137 … | |
136 | 138 … | private |
137 | 139 … | # Returns true if +h+ is a valid hash. |
spec/bcrypt/internals_spec.rb | ||
---|---|---|
@@ -1,48 +1,0 @@ | ||
1 | -require File.join(File.dirname(__FILE__), "..", "spec_helper") | |
2 | - | |
3 | -context "Generating BCrypt salts" do | |
4 | - include BCrypt::Internals | |
5 | - | |
6 | - specify "should produce strings" do | |
7 | - _bc_salt.should be_an_instance_of(String) | |
8 | - end | |
9 | - | |
10 | - specify "should produce random data" do | |
11 | - _bc_salt.should_not equal(_bc_salt) | |
12 | - end | |
13 | - | |
14 | - specify "should raise a InvalidCostError if the cost parameter isn't numeric" do | |
15 | - lambda { _bc_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost) | |
16 | - end | |
17 | -end | |
18 | - | |
19 | -context "Generating BCrypt hashes" do | |
20 | - include BCrypt::Internals | |
21 | - | |
22 | - setup do | |
23 | - @salt = _bc_salt | |
24 | - @password = "woo" | |
25 | - end | |
26 | - | |
27 | - specify "should produce a string" do | |
28 | - _bc_crypt(@password, @salt).should be_an_instance_of(String) | |
29 | - end | |
30 | - | |
31 | - specify "should raise an InvalidSaltError if the salt is invalid" do | |
32 | - lambda { _bc_crypt(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt) | |
33 | - end | |
34 | - | |
35 | - specify "should be interoperable with other implementations" do | |
36 | - # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/> | |
37 | - test_vectors = [ | |
38 | - ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"], | |
39 | - ["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"], | |
40 | - ["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"], | |
41 | - ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"], | |
42 | - ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"] | |
43 | - ] | |
44 | - for secret, salt, test_vector in test_vectors | |
45 | - _bc_crypt(secret, salt).should eql(test_vector) | |
46 | - end | |
47 | - end | |
48 | -end |
spec/bcrypt/engine_spec.rb | ||
---|---|---|
@@ -1,0 +1,46 @@ | ||
1 … | +require File.join(File.dirname(__FILE__), "..", "spec_helper") | |
2 … | + | |
3 … | +context "Generating BCrypt salts" do | |
4 … | + | |
5 … | + specify "should produce strings" do | |
6 … | + BCrypt::Engine.generate_salt.should be_an_instance_of(String) | |
7 … | + end | |
8 … | + | |
9 … | + specify "should produce random data" do | |
10 … | + BCrypt::Engine.generate_salt.should_not equal(BCrypt::Engine.generate_salt) | |
11 … | + end | |
12 … | + | |
13 … | + specify "should raise a InvalidCostError if the cost parameter isn't numeric" do | |
14 … | + lambda { BCrypt::Engine.generate_salt('woo') }.should raise_error(BCrypt::Errors::InvalidCost) | |
15 … | + end | |
16 … | +end | |
17 … | + | |
18 … | +context "Generating BCrypt hashes" do | |
19 … | + | |
20 … | + setup do | |
21 … | + @salt = BCrypt::Engine.generate_salt(5) | |
22 … | + @password = "woo" | |
23 … | + end | |
24 … | + | |
25 … | + specify "should produce a string" do | |
26 … | + BCrypt::Engine.hash(@password, @salt).should be_an_instance_of(String) | |
27 … | + end | |
28 … | + | |
29 … | + specify "should raise an InvalidSaltError if the salt is invalid" do | |
30 … | + lambda { BCrypt::Engine.hash(@password, 'nino') }.should raise_error(BCrypt::Errors::InvalidSalt) | |
31 … | + end | |
32 … | + | |
33 … | + specify "should be interoperable with other implementations" do | |
34 … | + # test vectors from the OpenWall implementation <http://www.openwall.com/crypt/> | |
35 … | + test_vectors = [ | |
36 … | + ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"], | |
37 … | + ["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"], | |
38 … | + ["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"], | |
39 … | + ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"], | |
40 … | + ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"] | |
41 … | + ] | |
42 … | + for secret, salt, test_vector in test_vectors | |
43 … | + BCrypt::Engine.hash(secret, salt).should eql(test_vector) | |
44 … | + end | |
45 … | + end | |
46 … | +end |
Built with git-ssb-web