class BCrypt::Engine

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

Constants

DEFAULT_COST

The default computational expense parameter.

MAX_COST

The maximum cost supported by the algorithm.

MAX_SALT_LENGTH

Maximum possible size of bcrypt() salts.

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

MIN_COST

The minimum cost supported by the algorithm.

Public Class Methods

__bc_crypt(p1, p2) click to toggle source

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

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(p1, p2, p3) click to toggle source

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

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) click to toggle source

Autodetects the cost from the salt string.

    # File lib/bcrypt/engine.rb
129 def self.autodetect_cost(salt)
130   salt[4..5].to_i
131 end
calibrate(upper_time_limit_in_ms) click to toggle source

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)
    # File lib/bcrypt/engine.rb
119 def self.calibrate(upper_time_limit_in_ms)
120   (BCrypt::Engine::MIN_COST..BCrypt::Engine::MAX_COST-1).each do |i|
121     start_time = Time.now
122     Password.create("testing testing", :cost => i+1)
123     end_time = Time.now - start_time
124     return i if end_time * 1_000 > upper_time_limit_in_ms
125   end
126 end
cost() click to toggle source

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.

   # File lib/bcrypt/engine.rb
32 def self.cost
33   @cost || DEFAULT_COST
34 end
cost=(cost) click to toggle source

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
   # File lib/bcrypt/engine.rb
49 def self.cost=(cost)
50   @cost = cost
51 end
generate_salt(cost = self.cost) click to toggle source

Generates a random salt with a given computational cost.

   # File lib/bcrypt/engine.rb
81 def self.generate_salt(cost = self.cost)
82   cost = cost.to_i
83   if cost > 0
84     if cost < MIN_COST
85       cost = MIN_COST
86     end
87     if RUBY_PLATFORM == "java"
88       Java.bcrypt_jruby.BCrypt.gensalt(cost)
89     else
90       __bc_salt("$2a$", cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
91     end
92   else
93     raise Errors::InvalidCost.new("cost must be numeric and > 0")
94   end
95 end
hash_secret(secret, salt, _ = nil) click to toggle source

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

   # File lib/bcrypt/engine.rb
55 def self.hash_secret(secret, salt, _ = nil)
56   unless _.nil?
57     warn "[DEPRECATION] Passing the third argument to " \
58          "`BCrypt::Engine.hash_secret` is deprecated. " \
59          "Please do not pass the third argument which " \
60          "is currently not used."
61   end
62 
63   if valid_secret?(secret)
64     if valid_salt?(salt)
65       if RUBY_PLATFORM == "java"
66         Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s.to_java_bytes, salt.to_s)
67       else
68         secret = secret.to_s
69         secret = secret.byteslice(0, MAX_SECRET_BYTESIZE) if secret && secret.bytesize > MAX_SECRET_BYTESIZE
70         __bc_crypt(secret, salt)
71       end
72     else
73       raise Errors::InvalidSalt.new("invalid salt")
74     end
75   else
76     raise Errors::InvalidSecret.new("invalid secret")
77   end
78 end
valid_salt?(salt) click to toggle source

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

    # File lib/bcrypt/engine.rb
 98 def self.valid_salt?(salt)
 99   !!(salt =~ /\A\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}\z/)
100 end
valid_secret?(secret) click to toggle source

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

    # File lib/bcrypt/engine.rb
103 def self.valid_secret?(secret)
104   secret.respond_to?(:to_s)
105 end