From 49f935c57d16dce78efd54518d6522174ab1d1f7 Mon Sep 17 00:00:00 2001 From: Mark Young Date: Wed, 20 Dec 2023 14:14:48 +0000 Subject: [PATCH 01/15] Provide a 'Changelog' link on rubygems.org/gems/bcrypt By providing a 'changelog_uri' in the metadata of the gemspec a 'Changelog' link will be shown on https://rubygems.org/gems/bcrypt which makes it quick and easy for someone to check on the changes introduced with a new version. --- bcrypt.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bcrypt.gemspec b/bcrypt.gemspec index 68a31ea..c425529 100644 --- a/bcrypt.gemspec +++ b/bcrypt.gemspec @@ -24,4 +24,6 @@ Gem::Specification.new do |s| s.email = "coda.hale@gmail.com" s.homepage = "https://github.com/bcrypt-ruby/bcrypt-ruby" s.license = "MIT" + + s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGELOG" end From 985e6d0579eba5cb534ac388179916fdab5adb9b Mon Sep 17 00:00:00 2001 From: m-nakamura145 Date: Tue, 21 May 2024 22:48:40 +0900 Subject: [PATCH 02/15] Support ruby 3.3 and 3.4.0-preview1 --- .github/workflows/ruby.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 82c49b3..a93f979 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -23,6 +23,8 @@ jobs: - '3.0' - 3.1 - 3.2 + - 3.3 + - 3.4.0-preview1 - head - jruby - jruby-head From 9a3a85ca9de4557b22aa8836bc7c1b9c387c024b Mon Sep 17 00:00:00 2001 From: "T.J. Schuck" Date: Tue, 21 May 2024 09:58:48 -0400 Subject: [PATCH 03/15] Update README to remove specific Ruby version mentions So we don't have to update this in lockstep with the CI matrix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dd55971..88598ad 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ re-hash those passwords. This vulnerability only affected the JRuby gem. The bcrypt gem is available on the following Ruby platforms: * JRuby -* RubyInstaller 2.0 – 3.0 builds on Windows with the DevKit -* Any 2.0 – 3.0 Ruby on a BSD/OS X/Linux system with a compiler +* RubyInstaller builds on Windows with the DevKit +* Any modern Ruby on a BSD/OS X/Linux system with a compiler ## How to use `bcrypt()` in your Rails application From a75cc698c4733a79c213c8a9609563abedc450bc Mon Sep 17 00:00:00 2001 From: "T.J. Schuck" Date: Wed, 22 May 2024 17:08:30 -0400 Subject: [PATCH 04/15] Ruby 3.4.0.-preview1 is not available on Windows See https://github.com/ruby/setup-ruby and https://github.com/ruby/setup-ruby/blob/master/windows-versions.json --- .github/workflows/ruby.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index a93f979..9111c87 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -38,6 +38,7 @@ jobs: - { os: macos, ruby: mingw } - { os: windows, ruby: truffleruby } - { os: windows, ruby: truffleruby-head } + - { os: windows, ruby: 3.4.0-preview1 } runs-on: ${{ matrix.os }}-latest From b8a167ab23959d1f37983b9228e9f01c03c726fd Mon Sep 17 00:00:00 2001 From: "T.J. Schuck" Date: Wed, 22 May 2024 17:18:47 -0400 Subject: [PATCH 05/15] Test old Rubies on older x64 macOS --- .github/workflows/ruby.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 9111c87..e940a81 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -36,6 +36,11 @@ jobs: - { os: ubuntu, ruby: 2.2 } - { os: ubuntu, ruby: mingw } - { os: macos, ruby: mingw } + - { os: macos, ruby: 2.1 } + - { os: macos, ruby: 2.2 } + - { os: macos, ruby: 2.3 } + - { os: macos, ruby: 2.4 } + - { os: macos, ruby: 2.5 } - { os: windows, ruby: truffleruby } - { os: windows, ruby: truffleruby-head } - { os: windows, ruby: 3.4.0-preview1 } @@ -60,11 +65,17 @@ jobs: strategy: fail-fast: false matrix: + os: + - ubuntu-20.04 + - macos-13 ruby: - 2.1 - 2.2 + - 2.3 + - 2.4 + - 2.5 - runs-on: ubuntu-20.04 + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 From ff9084a3f5e993b1ba9609e2326494fd97db61dc Mon Sep 17 00:00:00 2001 From: Federico Date: Sat, 13 Jul 2024 22:15:49 -0300 Subject: [PATCH 06/15] Add == gotcha that can be unintuitive at first --- lib/bcrypt/password.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index 4a2c140..3160c9b 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -73,6 +73,8 @@ def initialize(raw_hash) # @password == @password.to_s # => False # @password.to_s == @password # => True # @password.to_s == @password.to_s # => True + # + # secret == @password # => probably False, because the secret is not a BCrypt::Password instance. def ==(secret) super(BCrypt::Engine.hash_secret(secret, @salt)) end From 7277821447a7f40e605d9ff17a6b3b2b68e5c286 Mon Sep 17 00:00:00 2001 From: Mohamed Hafez Date: Wed, 18 Sep 2024 17:02:41 +0200 Subject: [PATCH 07/15] Mark as ractor-safe --- ext/mri/bcrypt_ext.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/mri/bcrypt_ext.c b/ext/mri/bcrypt_ext.c index 757b068..3e5ac1c 100644 --- a/ext/mri/bcrypt_ext.c +++ b/ext/mri/bcrypt_ext.c @@ -111,6 +111,10 @@ static VALUE bc_crypt(VALUE self, VALUE key, VALUE setting) { /* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */ void Init_bcrypt_ext(){ +#ifdef HAVE_RB_EXT_RACTOR_SAFE + rb_ext_ractor_safe(true); +#endif + mBCrypt = rb_define_module("BCrypt"); cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject); From 9597533827b5274a1947da56000728c20af397e7 Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Thu, 6 Aug 2015 14:06:03 -0400 Subject: [PATCH 08/15] Use a constant-time secure comparison for passwords Use a constant-time byte-by-byte secure comparison to compare potential password hashes rather than `String#==`, which uses strcmp under the hood and stops as soon as there's an unmatched byte. --- lib/bcrypt/password.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index 3160c9b..db09009 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -76,7 +76,14 @@ def initialize(raw_hash) # # secret == @password # => probably False, because the secret is not a BCrypt::Password instance. def ==(secret) - super(BCrypt::Engine.hash_secret(secret, @salt)) + hash = BCrypt::Engine.hash_secret(secret, @salt) + + return false if hash.strip.empty? || strip.empty? || hash.bytesize != bytesize + l = hash.unpack "C#{hash.bytesize}" + + res = 0 + each_byte { |byte| res |= byte ^ l.shift } + res == 0 end alias_method :is_password?, :== From a7a868e12d80c6e0a75d81acdfdc3c4a763a622b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Oct 2024 16:04:46 -0700 Subject: [PATCH 09/15] Lets not allocate anything --- lib/bcrypt/password.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bcrypt/password.rb b/lib/bcrypt/password.rb index db09009..0c432d6 100644 --- a/lib/bcrypt/password.rb +++ b/lib/bcrypt/password.rb @@ -79,10 +79,10 @@ def ==(secret) hash = BCrypt::Engine.hash_secret(secret, @salt) return false if hash.strip.empty? || strip.empty? || hash.bytesize != bytesize - l = hash.unpack "C#{hash.bytesize}" + # Constant time comparison so they can't tell the length. res = 0 - each_byte { |byte| res |= byte ^ l.shift } + bytesize.times { |i| res |= getbyte(i) ^ hash.getbyte(i) } res == 0 end alias_method :is_password?, :== From 27dbab3080c2dbd22ae0652b36fd37eba69dda30 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 10:14:07 -0800 Subject: [PATCH 10/15] Declare development dependencies --- bcrypt.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bcrypt.gemspec b/bcrypt.gemspec index c425529..bae0473 100644 --- a/bcrypt.gemspec +++ b/bcrypt.gemspec @@ -14,6 +14,8 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake-compiler', '~> 1.2.0' s.add_development_dependency 'rspec', '>= 3' + s.add_development_dependency 'rdoc', '>= 7.0.3' + s.add_development_dependency 'benchmark', '>= 0.5.0' s.rdoc_options += ['--title', 'bcrypt-ruby', '--line-numbers', '--inline-source', '--main', 'README.md'] s.extra_rdoc_files += ['README.md', 'COPYING', 'CHANGELOG', *Dir['lib/**/*.rb']] From c1562549b901349c79fb5e96d16c32e25caa7938 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 10:14:19 -0800 Subject: [PATCH 11/15] Modernize CI --- .github/workflows/ruby.yml | 111 ++++++++++++------------------------- 1 file changed, 34 insertions(+), 77 deletions(-) diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index e940a81..c5fb94b 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -3,93 +3,50 @@ name: Test Suite # Run against all commits and pull requests. on: [ push, pull_request ] +env: + JAVA_OPTS: '-Xms60M -Xmx1G' + jobs: - test_matrix: + ruby-versions: + uses: ruby/actions/.github/workflows/ruby_versions.yml@master + with: + engine: cruby + min_version: 3.1 + + test: + needs: ruby-versions + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: - - ubuntu - - macos - - windows - ruby: - - 2.1 - - 2.2 - - 2.3 - - 2.4 - - 2.5 - - 2.6 - - 2.7 - - '3.0' - - 3.1 - - 3.2 - - 3.3 - - 3.4.0-preview1 - - head - - jruby - - jruby-head - - truffleruby - - truffleruby-head - - mingw - exclude: - - { os: ubuntu, ruby: 2.1 } - - { os: ubuntu, ruby: 2.2 } - - { os: ubuntu, ruby: mingw } - - { os: macos, ruby: mingw } - - { os: macos, ruby: 2.1 } - - { os: macos, ruby: 2.2 } - - { os: macos, ruby: 2.3 } - - { os: macos, ruby: 2.4 } - - { os: macos, ruby: 2.5 } - - { os: windows, ruby: truffleruby } - - { os: windows, ruby: truffleruby-head } - - { os: windows, ruby: 3.4.0-preview1 } - - runs-on: ${{ matrix.os }}-latest + ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} + os: [ ubuntu-latest, macos-latest, windows-latest ] + include: + - { os: windows-latest, ruby: jruby-head } + - { os: macos-latest, ruby: jruby-head } + - { os: ubuntu-latest, ruby: jruby-head } + - { os: windows-latest, ruby: ucrt } + - { os: windows-latest, ruby: mingw } steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Set up Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby-pkgs@v1 with: ruby-version: ${{ matrix.ruby }} - bundler-cache: true - env: - JAVA_OPTS: -Djdk.io.File.enableADS=true - - name: Run tests - run: bundle exec rake default - env: - JAVA_OPTS: -Djdk.io.File.enableADS=true + apt-get: "haveged libyaml-dev" + brew: libyaml + vcpkg: libyaml - test_matrix_old_rubies: - strategy: - fail-fast: false - matrix: - os: - - ubuntu-20.04 - - macos-13 - ruby: - - 2.1 - - 2.2 - - 2.3 - - 2.4 - - 2.5 + - name: Set JRuby ENV vars + run: | + echo 'JAVA_OPTS=-Xmx1g' >> $GITHUB_ENV + if: ${{ ! startsWith(matrix.ruby, 'jruby') }} - runs-on: ${{ matrix.os }} + - name: Install dependencies + run: bundle install --jobs 1 - steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - name: Run tests - run: bundle exec rake default - - finish: - runs-on: ubuntu-latest - needs: [ test_matrix ] - steps: - - name: Wait for status checks - run: echo "All Green!" + run: bundle exec rake + continue-on-error: ${{ matrix.ruby == 'jruby-head' }} From d94041a0d2972f4dba1d831a9ebdefad398fe604 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 10:39:17 -0800 Subject: [PATCH 12/15] Try to deal with flaky tests We have a test that tries to test the "calibration" function in BCrypt. This function is supposed to return a "cost" based on some number of milliseconds passed in. We're trying to make the cost relative to some beginning cost (MIN_COST + 1), time that, then use it as the "relative time". The problem is that the time this takes in CI is not constant, so even `min_time_ms * 4` may fit inside the same "cost" as the original. I'm bumping the factor to `5` to hopefully reduce the flakiness. It seems like we just want to test that "some time deadline" returns a greater cost than a different time deadline, so I think bumping the factor to 5 is a legit fix --- spec/bcrypt/engine_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bcrypt/engine_spec.rb b/spec/bcrypt/engine_spec.rb index b527a6b..8abbe9f 100644 --- a/spec/bcrypt/engine_spec.rb +++ b/spec/bcrypt/engine_spec.rb @@ -17,7 +17,7 @@ BCrypt::Password.create("testing testing", :cost => BCrypt::Engine::MIN_COST + 1) min_time_ms = (Time.now - start_time) * 1000 first = BCrypt::Engine.calibrate(min_time_ms) - second = BCrypt::Engine.calibrate(min_time_ms * 4) + second = BCrypt::Engine.calibrate(min_time_ms * 5) expect(second).to be > first end end From 344ca599eed0fc311e3a5be80441ddb85540f34f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 11:33:52 -0800 Subject: [PATCH 13/15] Configure trusted publishing This way we can just push a tag and let GitHub actions do the release --- .github/workflows/release.yml | 62 +++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1325988 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Publish gem to rubygems.org + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + +jobs: + push: + if: github.repository == 'bcrypt-ruby/bcrypt-ruby' + runs-on: ubuntu-latest + + environment: + name: rubygems.org + url: https://rubygems.org/gems/bcrypt-ruby + + permissions: + contents: write + id-token: write + + strategy: + matrix: + ruby: ["ruby", "jruby"] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@v2 + with: + egress-policy: audit + + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + # https://github.com/rubygems/rubygems/issues/5882 + - name: Install dependencies and build for JRuby + run: | + sudo apt install default-jdk maven + gem update --system + gem install ruby-maven rake-compiler --no-document + rake compile + if: matrix.ruby == 'jruby' + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Publish to RubyGems + uses: rubygems/release-gem@v1 + + - name: Create GitHub release + run: | + tag_name="$(git describe --tags --abbrev=0)" + gh release create "${tag_name}" --verify-tag --generate-notes + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: matrix.ruby != 'jruby' From 64605fc1de894ba125de6a7eb61dd8cceb9bc65d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 11:39:38 -0800 Subject: [PATCH 14/15] bump version --- CHANGELOG | 4 ++++ bcrypt.gemspec | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 92a8114..1682923 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +3.1.21 Dec 31 2025 + - Use constant time comparisons + - Mark as Ractor safe + 3.1.20 Nov 17 2023 - Limit packaged files -- decrease gem filesize by ~28% [GH #272 by @pusewicz] diff --git a/bcrypt.gemspec b/bcrypt.gemspec index bae0473..e35a402 100644 --- a/bcrypt.gemspec +++ b/bcrypt.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |s| s.name = 'bcrypt' - s.version = '3.1.20' + s.version = '3.1.21' s.summary = "OpenBSD's bcrypt() password hashing algorithm." s.description = <<-EOF From 4b1fc736c0f4f66d5e2dd4a5c28bd4f3f51aea93 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 31 Dec 2025 11:41:44 -0800 Subject: [PATCH 15/15] add bundler tasks --- Rakefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Rakefile b/Rakefile index 58b51fe..95e22a5 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,8 @@ require 'rake/javaextensiontask' require 'rake/clean' require 'rdoc/task' require 'benchmark' +require "bundler" +Bundler::GemHelper.install_tasks CLEAN.include( "tmp",