Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for SCrypt #709

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions lib/sorcery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ module CryptoProviders
require 'sorcery/crypto_providers/aes256'
require 'sorcery/crypto_providers/bcrypt'
require 'sorcery/crypto_providers/md5'
require 'sorcery/crypto_providers/scrypt'
require 'sorcery/crypto_providers/sha1'
require 'sorcery/crypto_providers/sha256'
require 'sorcery/crypto_providers/sha512'
Expand Down
99 changes: 99 additions & 0 deletions lib/sorcery/crypto_providers/scrypt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
require 'scrypt'

module Sorcery
module CryptoProviders
# For more information on why using scrypt is a good idea:
# https://github.com/pbhogan/scrypt
#
# The scrypt key derivation function is designed to be far more secure against hardware
# brute-force attacks than alternative functions such as PBKDF2 or bcrypt.
#
# Decided SCrypt is for you? Just insall the scrypt gem:
#
# gem install scrypt
#
# Update your initializer to use it:
#
# config.encryption_algorithm = :scrypt
#
# You are good to go!
class SCrypt
class << self

# The designers of scrypt estimate that on modern (2009) hardware, if 5 seconds are spent computing a derived key,
# the cost of a hardware brute-force attack against scrypt is roughly 4000 times greater than the cost of a
# similar attack against bcrypt (to find the same password), and 20000 times greater than a similar attack against PBKDF2.
#
# Default options will result in calculation time of approx. 200 ms with 1 MB memory use.
#
# SCrypt has five options that can be configured. The defaults are below and can be altered
# Please review the SCript documentation for further information

# specifies the length in bytes of the key you want to generate.
# The default is 32 bytes (256 bits).
# Minimum is 16 bytes (128 bits).
# Maximum is 512 bytes (4096 bits).
def key_len
@key_len ||= 32
end
attr_writer :key_len

# specifies the size in bytes of the random salt you want to generate.
# The default and minimum is 8 bytes (64 bits). Maximum is 32 bytes (256 bits).
def salt_size
@salt_size ||= 8
end
attr_writer :salt_size

# specifies the maximum number of seconds the computation should take.
def max_time
@max_time ||= 0.2
end
attr_writer :max_time

# specifies the maximum number of bytes the computation should take.
# A value of 0 specifies no upper limit. The minimum is always 1 MB.
def max_mem
@max_mem ||= 1024 * 1024
end
attr_writer :max_mem

# specifies the maximum memory in a fraction of available resources to use.
# Any value equal to 0 or greater than 0.5 will result in 0.5 being used.
def max_memfrac
@max_memfrac ||= 0.5
end
attr_writer :max_memfrac

# Hashes a secret, returning a SCrypt::Password instance.
def encrypt( *tokens )
::SCrypt::Password.create( join_tokens( tokens ), { key_len: key_len,
salt_size: salt_size,
max_time: max_time,
max_mem: max_mem,
max_memfrac: max_memfrac } )
end

# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
def matches?( crypted, *tokens )
crypted == join_tokens( tokens )
end

def reset!
@key_len = 32
@salt_size = 8
@max_time = 0.2
@max_mem = 1024 * 1024
@max_memfrac = 0.5
end

private

def join_tokens( tokens )
tokens.flatten.join
end

end
end
end
end
1 change: 1 addition & 0 deletions lib/sorcery/model/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def encryption_algorithm=(algo)
when :sha512 then CryptoProviders::SHA512
when :aes256 then CryptoProviders::AES256
when :bcrypt then CryptoProviders::BCrypt
when :scrypt then CryptoProviders::SCrypt
when :custom then @custom_encryption_provider
else raise ArgumentError.new("Encryption algorithm supplied, #{algo}, is invalid")
end
Expand Down
1 change: 1 addition & 0 deletions sorcery.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency "oauth", "~> 0.4", ">= 0.4.4"
s.add_dependency "oauth2", ">= 0.8.0"
s.add_dependency "bcrypt", "~> 3.1"
s.add_dependency "scrypt", "~> 2.0"

s.add_development_dependency "abstract", ">= 1.0.0"
s.add_development_dependency "json", ">= 1.7.7"
Expand Down
25 changes: 25 additions & 0 deletions spec/sorcery_crypto_providers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,29 @@

end

describe Sorcery::CryptoProviders::SCrypt do
before(:all) do
@password = SCrypt::Password.create('Noam Ben-Ari')
end

it "should be comparable with original secret" do
expect( SCrypt::Password.new( Sorcery::CryptoProviders::SCrypt.encrypt('Noam Ben-Ari') ) ).to eq 'Noam Ben-Ari'
end

it "matches? returns true when matches" do
expect( Sorcery::CryptoProviders::SCrypt.matches?(@password, 'Noam Ben-Ari') ).to be true
end

it "matches? returns false when no match" do
expect( Sorcery::CryptoProviders::SCrypt.matches?(@password, 'Some Dude') ).to be false
end

it "properly uses key_len" do
expect( Sorcery::CryptoProviders::SCrypt.encrypt('Noam Ben-Ari').length).to eq 90
Sorcery::CryptoProviders::SCrypt.key_len = 512
expect( Sorcery::CryptoProviders::SCrypt.encrypt('Noam Ben-Ari').length).to eq 1050
Sorcery::CryptoProviders::SCrypt.reset!
end
end

end