Skip to content

Commit

Permalink
Increase complexity logarithmicly to slowdown brute force attacks
Browse files Browse the repository at this point in the history
Complexity is increased for each IP address during the last 24 hours.
Thus it allows to decrease the base complexity to less penalize legitimate users.

Log2 has been choosen to match to the power of 2 complexity of hashcash.
  • Loading branch information
alexisbernard committed Mar 1, 2024
1 parent 4889db3 commit 42a14bc
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Increase complexity logarithmicly to slowdown brute force attacks
- Store stamps into the database instead of Redis
- Fix ActiveHashcash::Store#add? by converting stamp to a string

Expand Down
19 changes: 13 additions & 6 deletions lib/active_hashcash.rb
Expand Up @@ -11,10 +11,12 @@ module ActiveHashcash
end

mattr_accessor :resource, instance_accessor: false
mattr_accessor :bits, instance_accessor: false, default: 20
mattr_accessor :date_format, instance_accessor: false, default: "%y%m%d"

# TODO: protect_from_brute_force bits: 20, exception: ActionController::InvalidAuthenticityToken, with: :handle_failed_hashcash
# This is base complexity.
# Consider lowering it to not exclude people with old and slow devices.
mattr_accessor :bits, instance_accessor: false, default: 16

mattr_accessor :date_format, instance_accessor: false, default: "%y%m%d"

# Call me via a before_action when the form is submitted : `before_action :check_hashcash, only: :create`
def check_hashcash
Expand Down Expand Up @@ -45,10 +47,15 @@ def hashcash_resource
ActiveHashcash.resource || request.host
end

# Define the complexity, the higher the slower it is. Consider lowering this value to not exclude people with old and slow devices.
# On a decent laptop, it takes around 30 seconds for the JavaScript implementation to solve a 20 bits complexity and few seconds when it's 16.
# Returns the complexity, the higher the slower it is.
# Complexity is increased logarithmicly for each IP during the last 24H to slowdown brute force attacks.
# The minimun value returned is `ActiveHashcash.bits`.
def hashcash_bits
ActiveHashcash.bits
if (previous_stamp_count = ActiveHashcash::Stamp.where(ip_address: hashcash_ip_address).where(created_at: 1.day.ago..).count) > 0
(ActiveHashcash.bits + Math.log2(previous_stamp_count)).floor
else
ActiveHashcash.bits
end
end

# Override if you want to rename the hashcash param.
Expand Down
38 changes: 36 additions & 2 deletions test/active_hashcash_test.rb
@@ -1,7 +1,41 @@
require "test_helper"

class ActiveHashcashTest < ActiveSupport::TestCase
test "it has a version number" do
assert ActiveHashcash::VERSION

class SampleController < ApplicationController
include ActiveHashcash

def hashcash_ip_address
"127.0.0.1"
end
end

def test_hashcash_bits
controller = SampleController.new
assert_equal(ActiveHashcash.bits, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:001").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:002").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 1, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:003").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 1, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:004").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 2, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:005").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 2, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:006").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 2, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:007").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 2, controller.hashcash_bits)

ActiveHashcash::Stamp.parse("1:20:220623:test::MPWRGuN3itbd1NiQ:008").update!(ip_address: "127.0.0.1")
assert_equal(ActiveHashcash.bits + 3, controller.hashcash_bits)
end
end

0 comments on commit 42a14bc

Please sign in to comment.