Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions lib/bcrypt/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ class Engine
MIN_COST = 4
# The maximum cost supported by the algorithm.
MAX_COST = 31
# 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.
# https://github.com/bcrypt-ruby/bcrypt-ruby/issues/225#issuecomment-875908425
MAX_SECRET_BYTESIZE = 72
# Maximum possible size of bcrypt() salts.
MAX_SALT_LENGTH = 16

Expand Down Expand Up @@ -43,14 +51,16 @@ def self.cost=(cost)
end

# Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
# a bcrypt() password hash.
# a bcrypt() password hash. Secrets longer than 72 bytes are truncated.
def self.hash_secret(secret, salt, _ = nil)
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
__bc_crypt(secret.to_s, salt)
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")
Expand Down
16 changes: 16 additions & 0 deletions spec/bcrypt/engine_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
require 'securerandom'

describe 'BCrypt::Engine' do
describe '.calibrate(upper_time_limit_in_ms)' do
Expand Down Expand Up @@ -157,4 +158,19 @@ class MyInvalidSecret
expect(BCrypt::Engine.hash_secret(secret, salt)).to eql(test_vector)
end
end

specify "should truncate 1-byte character secrets to 72 bytes" do
# 'b' as a base triggers the failure at 256 characters, but 'a' does not.
too_long_secret = 'b'*(BCrypt::Engine::MAX_SECRET_BYTESIZE + 1)
just_right_secret = 'b'*BCrypt::Engine::MAX_SECRET_BYTESIZE
expect(BCrypt::Engine.hash_secret(too_long_secret, @salt)).to eq(BCrypt::Engine.hash_secret(just_right_secret, @salt))
end

specify "should truncate multi-byte character secrets to 72 bytes" do
# 256 times causes bcrypt to return nil for libxcrypt > 4.4.18-4.
too_long_secret = '𐐷'*256
# 𐐷 takes 4 bytes in UTF-8. 18 times is 72 bytes
just_right_secret = '𐐷'*18
expect(BCrypt::Engine.hash_secret(too_long_secret, @salt)).to eq(BCrypt::Engine.hash_secret(just_right_secret, @salt))
end
end
23 changes: 23 additions & 0 deletions spec/bcrypt/password_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
require 'securerandom'

describe "Creating a hashed password" do

Expand Down Expand Up @@ -26,6 +27,28 @@
expect { BCrypt::Password.create( "" ) }.not_to raise_error
expect { BCrypt::Password.create( String.new ) }.not_to raise_error
end

context 'with a 256 character password' do
before :each do
@secret = SecureRandom.hex(256)
Comment thread
asavageiv marked this conversation as resolved.
Resolved
@password = BCrypt::Password.create(@secret, :cost => 4)
end

specify "should return a BCrypt::Password" do
expect(@password).to be_an_instance_of(BCrypt::Password)
end
end

context 'with a 255 character password' do
before :each do
@secret = SecureRandom.hex(255)
@password = BCrypt::Password.create(@secret, :cost => 4)
end

specify "should return a BCrypt::Password" do
expect(@password).to be_an_instance_of(BCrypt::Password)
end
end
end

describe "Reading a hashed password" do
Expand Down