Class: BCrypt::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/bcrypt/engine.rb,
ext/mri/bcrypt_ext.c

Overview

A Ruby wrapper for the bcrypt() C extension calls and the Java calls.

Constant Summary collapse

DEFAULT_COST =

The default computational expense parameter.

12
MIN_COST =

The minimum cost supported by the algorithm.

4
MAX_COST =

The maximum cost supported by the algorithm.

31
MAX_SECRET_BYTESIZE =

Maximum possible size of bcrypt() secrets. Older versions of the bcrypt library would truncate passwords longer than 72 bytes, but newer ones do not. We truncate like the old library for forward compatibility. This way users upgrading from Ubuntu 18.04 to 20.04 will not have their user passwords invalidated, for example. A max secret length greater than 255 leads to bcrypt returning nil. github.com/bcrypt-ruby/bcrypt-ruby/issues/225#issuecomment-875908425

72
MAX_SALT_LENGTH =

Maximum possible size of bcrypt() salts.

16

Class Method Summary collapse

Class Method Details

.__bc_crypt(key, setting) ⇒ Object

Given a secret and a salt, generates a salted hash (which you can then store safely).



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'ext/mri/bcrypt_ext.c', line 75

static VALUE bc_crypt(VALUE self, VALUE key, VALUE setting) {
    char * value;
    VALUE out;

    struct bc_crypt_args args;

    if(NIL_P(key) || NIL_P(setting)) return Qnil;

    /* duplicate the parameters for thread safety.  If another thread has a
     * reference to the parameters and mutates them while we are working,
     * that would be very bad.  Duping the strings means that the reference
     * isn't shared. */
    key     = rb_str_new_frozen(key);
    setting = rb_str_new_frozen(setting);

    args.data    = NULL;
    args.size    = 0xDEADBEEF;
    args.key     = NIL_P(key)     ? NULL : StringValueCStr(key);
    args.setting = NIL_P(setting) ? NULL : StringValueCStr(setting);

#ifdef HAVE_RUBY_THREAD_H
    value = rb_thread_call_without_gvl(bc_crypt_nogvl, &args, NULL, NULL);
#else
    value = bc_crypt_nogvl((void *)&args);
#endif

    if(!value || !args.data) return Qnil;

    out = rb_str_new2(value);

    RB_GC_GUARD(key);
    RB_GC_GUARD(setting);
    free(args.data);

    return out;
}

.__bc_salt(prefix, count, input) ⇒ Object

Given a logarithmic cost parameter, generates a salt for use with bc_crypt.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'ext/mri/bcrypt_ext.c', line 26

static VALUE bc_salt(VALUE self, VALUE prefix, VALUE count, VALUE input) {
    char * salt;
    VALUE str_salt;
    struct bc_salt_args args;

    /* duplicate the parameters for thread safety.  If another thread has a
     * reference to the parameters and mutates them while we are working,
     * that would be very bad.  Duping the strings means that the reference
     * isn't shared. */
    prefix = rb_str_new_frozen(prefix);
    input  = rb_str_new_frozen(input);

    args.prefix = StringValueCStr(prefix);
    args.count  = NUM2ULONG(count);
    args.input  = NIL_P(input) ? NULL : StringValuePtr(input);
    args.size   = NIL_P(input) ? 0 : RSTRING_LEN(input);

#ifdef HAVE_RUBY_THREAD_H
    salt = rb_thread_call_without_gvl(bc_salt_nogvl, &args, NULL, NULL);
#else
    salt = bc_salt_nogvl((void *)&args);
#endif

    if(!salt) return Qnil;

    str_salt = rb_str_new2(salt);

    RB_GC_GUARD(prefix);
    RB_GC_GUARD(input);
    free(salt);

    return str_salt;
}

.autodetect_cost(salt) ⇒ Object

Autodetects the cost from the salt string.



129
130
131
# File 'lib/bcrypt/engine.rb', line 129

def self.autodetect_cost(salt)
  salt[4..5].to_i
end

.calibrate(upper_time_limit_in_ms) ⇒ Object

Returns the cost factor which will result in computation times less than upper_time_limit_in_ms.

Example:

BCrypt::Engine.calibrate(200)  #=> 10
BCrypt::Engine.calibrate(1000) #=> 12

# should take less than 200ms
BCrypt::Password.create("woo", :cost => 10)

# should take less than 1000ms
BCrypt::Password.create("woo", :cost => 12)


119
120
121
122
123
124
125
126
# File 'lib/bcrypt/engine.rb', line 119

def self.calibrate(upper_time_limit_in_ms)
  (BCrypt::Engine::MIN_COST..BCrypt::Engine::MAX_COST-1).each do |i|
    start_time = Time.now
    Password.create("testing testing", :cost => i+1)
    end_time = Time.now - start_time
    return i if end_time * 1_000 > upper_time_limit_in_ms
  end
end

.costObject

Returns the cost factor that will be used if one is not specified when creating a password hash. Defaults to DEFAULT_COST if not set.



32
33
34
# File 'lib/bcrypt/engine.rb', line 32

def self.cost
  @cost || DEFAULT_COST
end

.cost=(cost) ⇒ Object

Set a default cost factor that will be used if one is not specified when creating a password hash.

Example:

BCrypt::Engine::DEFAULT_COST            #=> 12
BCrypt::Password.create('secret').cost  #=> 12

BCrypt::Engine.cost = 8
BCrypt::Password.create('secret').cost  #=> 8

# cost can still be overridden as needed
BCrypt::Password.create('secret', :cost => 6).cost  #=> 6


49
50
51
# File 'lib/bcrypt/engine.rb', line 49

def self.cost=(cost)
  @cost = cost
end

.generate_salt(cost = self.cost) ⇒ Object

Generates a random salt with a given computational cost.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/bcrypt/engine.rb', line 81

def self.generate_salt(cost = self.cost)
  cost = cost.to_i
  if cost > 0
    if cost < MIN_COST
      cost = MIN_COST
    end
    if RUBY_PLATFORM == "java"
      Java.bcrypt_jruby.BCrypt.gensalt(cost)
    else
      __bc_salt("$2a$", cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
    end
  else
    raise Errors::InvalidCost.new("cost must be numeric and > 0")
  end
end

.hash_secret(secret, salt, _ = nil) ⇒ Object

Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates a bcrypt() password hash. Secrets longer than 72 bytes are truncated.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/bcrypt/engine.rb', line 55

def self.hash_secret(secret, salt, _ = nil)
  unless _.nil?
    warn "[DEPRECATION] Passing the third argument to " \
         "`BCrypt::Engine.hash_secret` is deprecated. " \
         "Please do not pass the third argument which " \
         "is currently not used."
  end

  if valid_secret?(secret)
    if valid_salt?(salt)
      if RUBY_PLATFORM == "java"
        Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s.to_java_bytes, salt.to_s)
      else
        secret = secret.to_s
        secret = secret.byteslice(0, MAX_SECRET_BYTESIZE) if secret && secret.bytesize > MAX_SECRET_BYTESIZE
        __bc_crypt(secret, salt)
      end
    else
      raise Errors::InvalidSalt.new("invalid salt")
    end
  else
    raise Errors::InvalidSecret.new("invalid secret")
  end
end

.valid_salt?(salt) ⇒ Boolean

Returns true if salt is a valid bcrypt() salt, false if not.

Returns:

  • (Boolean)


98
99
100
# File 'lib/bcrypt/engine.rb', line 98

def self.valid_salt?(salt)
  !!(salt =~ /\A\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}\z/)
end

.valid_secret?(secret) ⇒ Boolean

Returns true if secret is a valid bcrypt() secret, false if not.

Returns:

  • (Boolean)


103
104
105
# File 'lib/bcrypt/engine.rb', line 103

def self.valid_secret?(secret)
  secret.respond_to?(:to_s)
end