authentication-logic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/lib/auth/logic/acts_as_authentic/base.rb +118 -0
- data/lib/auth/logic/acts_as_authentic/email.rb +32 -0
- data/lib/auth/logic/acts_as_authentic/logged_in_status.rb +87 -0
- data/lib/auth/logic/acts_as_authentic/login.rb +65 -0
- data/lib/auth/logic/acts_as_authentic/magic_columns.rb +40 -0
- data/lib/auth/logic/acts_as_authentic/password.rb +362 -0
- data/lib/auth/logic/acts_as_authentic/perishable_token.rb +125 -0
- data/lib/auth/logic/acts_as_authentic/persistence_token.rb +72 -0
- data/lib/auth/logic/acts_as_authentic/queries/case_sensitivity.rb +55 -0
- data/lib/auth/logic/acts_as_authentic/queries/find_with_case.rb +85 -0
- data/lib/auth/logic/acts_as_authentic/session_maintenance.rb +189 -0
- data/lib/auth/logic/acts_as_authentic/single_access_token.rb +85 -0
- data/lib/auth/logic/config.rb +41 -0
- data/lib/auth/logic/controller_adapters/abstract_adapter.rb +121 -0
- data/lib/auth/logic/controller_adapters/rack_adapter.rb +74 -0
- data/lib/auth/logic/controller_adapters/rails_adapter.rb +49 -0
- data/lib/auth/logic/controller_adapters/sinatra_adapter.rb +69 -0
- data/lib/auth/logic/cookie_credentials.rb +65 -0
- data/lib/auth/logic/crypto_providers/bcrypt.rb +116 -0
- data/lib/auth/logic/crypto_providers/md5/v2.rb +37 -0
- data/lib/auth/logic/crypto_providers/md5.rb +38 -0
- data/lib/auth/logic/crypto_providers/scrypt.rb +96 -0
- data/lib/auth/logic/crypto_providers/sha1/v2.rb +42 -0
- data/lib/auth/logic/crypto_providers/sha1.rb +43 -0
- data/lib/auth/logic/crypto_providers/sha256/v2.rb +60 -0
- data/lib/auth/logic/crypto_providers/sha256.rb +61 -0
- data/lib/auth/logic/crypto_providers/sha512/v2.rb +41 -0
- data/lib/auth/logic/crypto_providers/sha512.rb +40 -0
- data/lib/auth/logic/crypto_providers.rb +89 -0
- data/lib/auth/logic/errors.rb +52 -0
- data/lib/auth/logic/i18n/translator.rb +20 -0
- data/lib/auth/logic/i18n.rb +100 -0
- data/lib/auth/logic/random.rb +18 -0
- data/lib/auth/logic/session/base.rb +2205 -0
- data/lib/auth/logic/session/magic_column/assigns_last_request_at.rb +49 -0
- data/lib/auth/logic/test_case/mock_api_controller.rb +53 -0
- data/lib/auth/logic/test_case/mock_controller.rb +59 -0
- data/lib/auth/logic/test_case/mock_cookie_jar.rb +112 -0
- data/lib/auth/logic/test_case/mock_logger.rb +14 -0
- data/lib/auth/logic/test_case/mock_request.rb +36 -0
- data/lib/auth/logic/test_case/rails_request_adapter.rb +40 -0
- data/lib/auth/logic/test_case.rb +216 -0
- data/lib/auth/logic/version.rb +7 -0
- data/lib/auth/logic.rb +46 -0
- metadata +426 -0
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bcrypt"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
# The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2)
|
9
|
+
# is the best choice for password storage today. They have the
|
10
|
+
# three properties of password hashing that are desirable. They
|
11
|
+
# are one-way, unique, and slow. While a salted SHA or MD5 hash is
|
12
|
+
# one-way and unique, preventing rainbow table attacks, they are
|
13
|
+
# still lightning fast and attacks on the stored passwords are
|
14
|
+
# much more effective. This benchmark demonstrates the effective
|
15
|
+
# slowdown that BCrypt provides:
|
16
|
+
#
|
17
|
+
# require "bcrypt"
|
18
|
+
# require "digest"
|
19
|
+
# require "benchmark"
|
20
|
+
#
|
21
|
+
# Benchmark.bm(18) do |x|
|
22
|
+
# x.report("BCrypt (cost = 10:") {
|
23
|
+
# 100.times { BCrypt::Password.create("mypass", :cost => 10) }
|
24
|
+
# }
|
25
|
+
# x.report("BCrypt (cost = 4:") {
|
26
|
+
# 100.times { BCrypt::Password.create("mypass", :cost => 4) }
|
27
|
+
# }
|
28
|
+
# x.report("Sha512:") {
|
29
|
+
# 100.times { Digest::SHA512.hexdigest("mypass") }
|
30
|
+
# }
|
31
|
+
# x.report("Sha1:") {
|
32
|
+
# 100.times { Digest::SHA1.hexdigest("mypass") }
|
33
|
+
# }
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# user system total real
|
37
|
+
# BCrypt (cost = 10): 37.360000 0.020000 37.380000 ( 37.558943)
|
38
|
+
# BCrypt (cost = 4): 0.680000 0.000000 0.680000 ( 0.677460)
|
39
|
+
# Sha512: 0.000000 0.000000 0.000000 ( 0.000672)
|
40
|
+
# Sha1: 0.000000 0.000000 0.000000 ( 0.000454)
|
41
|
+
#
|
42
|
+
# You can play around with the cost to get that perfect balance
|
43
|
+
# between performance and security. A default cost of 10 is the
|
44
|
+
# best place to start.
|
45
|
+
#
|
46
|
+
# Decided BCrypt is for you? Just install the bcrypt gem:
|
47
|
+
#
|
48
|
+
# gem install bcrypt
|
49
|
+
#
|
50
|
+
# Tell acts_as_authentic to use it:
|
51
|
+
#
|
52
|
+
# acts_as_authentic do |c|
|
53
|
+
# c.crypto_provider = Authentication::Logic::CryptoProviders::BCrypt
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# You are good to go!
|
57
|
+
class BCrypt
|
58
|
+
class << self
|
59
|
+
# This is the :cost option for the BCrpyt library. The higher the cost
|
60
|
+
# the more secure it is and the longer is take the generate a hash. By
|
61
|
+
# default this is 10. Set this to any value >= the engine's minimum
|
62
|
+
# (currently 4), play around with it to get that perfect balance between
|
63
|
+
# security and performance.
|
64
|
+
def cost
|
65
|
+
@cost ||= 10
|
66
|
+
end
|
67
|
+
|
68
|
+
def cost=(val)
|
69
|
+
if val < ::BCrypt::Engine::MIN_COST
|
70
|
+
raise ArgumentError, "Authentication::Logic's bcrypt cost cannot be set below the engine's " \
|
71
|
+
"min cost (#{::BCrypt::Engine::MIN_COST})"
|
72
|
+
end
|
73
|
+
@cost = val
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creates a BCrypt hash for the password passed.
|
77
|
+
def encrypt(*tokens)
|
78
|
+
::BCrypt::Password.create(join_tokens(tokens), cost: cost)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Does the hash match the tokens? Uses the same tokens that were used to
|
82
|
+
# encrypt.
|
83
|
+
def matches?(hash, *tokens)
|
84
|
+
hash = new_from_hash(hash)
|
85
|
+
return false if hash.blank?
|
86
|
+
|
87
|
+
hash == join_tokens(tokens)
|
88
|
+
end
|
89
|
+
|
90
|
+
# This method is used as a flag to tell Authentication::Logic to "resave" the
|
91
|
+
# password upon a successful login, using the new cost
|
92
|
+
def cost_matches?(hash)
|
93
|
+
hash = new_from_hash(hash)
|
94
|
+
if hash.blank?
|
95
|
+
false
|
96
|
+
else
|
97
|
+
hash.cost == cost
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def join_tokens(tokens)
|
104
|
+
tokens.flatten.join
|
105
|
+
end
|
106
|
+
|
107
|
+
def new_from_hash(hash)
|
108
|
+
::BCrypt::Password.new(hash)
|
109
|
+
rescue ::BCrypt::Errors::InvalidHash
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
class MD5
|
9
|
+
# A poor choice. There are known attacks against this algorithm.
|
10
|
+
class V2
|
11
|
+
class << self
|
12
|
+
attr_accessor :join_token
|
13
|
+
|
14
|
+
# The number of times to loop through the encryption.
|
15
|
+
def stretches
|
16
|
+
@stretches ||= 1
|
17
|
+
end
|
18
|
+
attr_writer :stretches
|
19
|
+
|
20
|
+
# Turns your raw password into a MD5 hash.
|
21
|
+
def encrypt(*tokens)
|
22
|
+
digest = tokens.flatten.join(join_token)
|
23
|
+
stretches.times { digest = Digest::MD5.digest(digest) }
|
24
|
+
digest.unpack1("H*")
|
25
|
+
end
|
26
|
+
|
27
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
28
|
+
# were used to encrypt.
|
29
|
+
def matches?(crypted, *tokens)
|
30
|
+
encrypt(*tokens) == crypted
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/md5"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
# A poor choice. There are known attacks against this algorithm.
|
9
|
+
class MD5
|
10
|
+
# V2 hashes the digest bytes in repeated stretches instead of hex characters.
|
11
|
+
autoload :V2, File.join(__dir__, "md5", "v2")
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :join_token
|
15
|
+
|
16
|
+
# The number of times to loop through the encryption.
|
17
|
+
def stretches
|
18
|
+
@stretches ||= 1
|
19
|
+
end
|
20
|
+
attr_writer :stretches
|
21
|
+
|
22
|
+
# Turns your raw password into a MD5 hash.
|
23
|
+
def encrypt(*tokens)
|
24
|
+
digest = tokens.flatten.join(join_token)
|
25
|
+
stretches.times { digest = Digest::MD5.hexdigest(digest) }
|
26
|
+
digest
|
27
|
+
end
|
28
|
+
|
29
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
30
|
+
# were used to encrypt.
|
31
|
+
def matches?(crypted, *tokens)
|
32
|
+
encrypt(*tokens) == crypted
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "scrypt"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
# SCrypt is the default provider for Authentication::Logic. It is the only
|
9
|
+
# choice in the adaptive hash family that accounts for hardware
|
10
|
+
# based attacks by compensating with memory bound as well as cpu
|
11
|
+
# bound computational constraints. It offers the same guarantees
|
12
|
+
# as BCrypt in the way of one-way, unique and slow.
|
13
|
+
#
|
14
|
+
# Decided SCrypt is for you? Just install the scrypt gem:
|
15
|
+
#
|
16
|
+
# gem install scrypt
|
17
|
+
#
|
18
|
+
# Tell acts_as_authentic to use it:
|
19
|
+
#
|
20
|
+
# acts_as_authentic do |c|
|
21
|
+
# c.crypto_provider = Authentication::Logic::CryptoProviders::SCrypt
|
22
|
+
# end
|
23
|
+
class SCrypt
|
24
|
+
class << self
|
25
|
+
DEFAULTS = {
|
26
|
+
key_len: 32,
|
27
|
+
salt_size: 8,
|
28
|
+
max_time: 0.2,
|
29
|
+
max_mem: 1024 * 1024,
|
30
|
+
max_memfrac: 0.5
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
attr_writer :key_len, :salt_size, :max_time, :max_mem, :max_memfrac
|
34
|
+
|
35
|
+
# Key length - length in bytes of generated key, from 16 to 512.
|
36
|
+
def key_len
|
37
|
+
@key_len ||= DEFAULTS[:key_len]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Salt size - size in bytes of random salt, from 8 to 32
|
41
|
+
def salt_size
|
42
|
+
@salt_size ||= DEFAULTS[:salt_size]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Max time - maximum time spent in computation
|
46
|
+
def max_time
|
47
|
+
@max_time ||= DEFAULTS[:max_time]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Max memory - maximum memory usage. The minimum is always 1MB
|
51
|
+
def max_mem
|
52
|
+
@max_mem ||= DEFAULTS[:max_mem]
|
53
|
+
end
|
54
|
+
|
55
|
+
# Max memory fraction - maximum memory out of all available. Always
|
56
|
+
# greater than zero and <= 0.5.
|
57
|
+
def max_memfrac
|
58
|
+
@max_memfrac ||= DEFAULTS[:max_memfrac]
|
59
|
+
end
|
60
|
+
|
61
|
+
# Creates an SCrypt hash for the password passed.
|
62
|
+
def encrypt(*tokens)
|
63
|
+
::SCrypt::Password.create(
|
64
|
+
join_tokens(tokens),
|
65
|
+
key_len: key_len,
|
66
|
+
salt_size: salt_size,
|
67
|
+
max_mem: max_mem,
|
68
|
+
max_memfrac: max_memfrac,
|
69
|
+
max_time: max_time
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Does the hash match the tokens? Uses the same tokens that were used to encrypt.
|
74
|
+
def matches?(hash, *tokens)
|
75
|
+
hash = new_from_hash(hash)
|
76
|
+
return false if hash.blank?
|
77
|
+
|
78
|
+
hash == join_tokens(tokens)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def join_tokens(tokens)
|
84
|
+
tokens.flatten.join
|
85
|
+
end
|
86
|
+
|
87
|
+
def new_from_hash(hash)
|
88
|
+
::SCrypt::Password.new(hash)
|
89
|
+
rescue ::SCrypt::Errors::InvalidHash
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
class Sha1
|
9
|
+
# A poor choice. There are known attacks against this algorithm.
|
10
|
+
class V2
|
11
|
+
class << self
|
12
|
+
def join_token
|
13
|
+
@join_token ||= "--"
|
14
|
+
end
|
15
|
+
attr_writer :join_token, :stretches
|
16
|
+
|
17
|
+
# The number of times to loop through the encryption.
|
18
|
+
def stretches
|
19
|
+
@stretches ||= 10
|
20
|
+
end
|
21
|
+
|
22
|
+
# Turns your raw password into a Sha1 hash.
|
23
|
+
def encrypt(*tokens)
|
24
|
+
tokens = tokens.flatten
|
25
|
+
digest = tokens.shift
|
26
|
+
stretches.times do
|
27
|
+
digest = Digest::SHA1.digest([digest, *tokens].join(join_token))
|
28
|
+
end
|
29
|
+
digest.unpack1("H*")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
33
|
+
# were used to encrypt.
|
34
|
+
def matches?(crypted, *tokens)
|
35
|
+
encrypt(*tokens) == crypted
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
# A poor choice. There are known attacks against this algorithm.
|
9
|
+
class Sha1
|
10
|
+
# V2 hashes the digest bytes in repeated stretches instead of hex characters.
|
11
|
+
autoload :V2, File.join(__dir__, "sha1", "v2")
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def join_token
|
15
|
+
@join_token ||= "--"
|
16
|
+
end
|
17
|
+
attr_writer :join_token, :stretches
|
18
|
+
|
19
|
+
# The number of times to loop through the encryption.
|
20
|
+
def stretches
|
21
|
+
@stretches ||= 10
|
22
|
+
end
|
23
|
+
|
24
|
+
# Turns your raw password into a Sha1 hash.
|
25
|
+
def encrypt(*tokens)
|
26
|
+
tokens = tokens.flatten
|
27
|
+
digest = tokens.shift
|
28
|
+
stretches.times do
|
29
|
+
digest = Digest::SHA1.hexdigest([digest, *tokens].join(join_token))
|
30
|
+
end
|
31
|
+
digest
|
32
|
+
end
|
33
|
+
|
34
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
35
|
+
# were used to encrypt.
|
36
|
+
def matches?(crypted, *tokens)
|
37
|
+
encrypt(*tokens) == crypted
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha2"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
# The acts_as_authentic method has a crypto_provider option. This allows you
|
8
|
+
# to use any type of encryption you like. Just create a class with a class
|
9
|
+
# level encrypt and matches? method. See example below.
|
10
|
+
#
|
11
|
+
# === Example
|
12
|
+
#
|
13
|
+
# class MyAwesomeEncryptionMethod
|
14
|
+
# def self.encrypt(*tokens)
|
15
|
+
# # the tokens passed will be an array of objects, what type of object
|
16
|
+
# # is irrelevant, just do what you need to do with them and return a
|
17
|
+
# # single encrypted string. for example, you will most likely join all
|
18
|
+
# # of the objects into a single string and then encrypt that string
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def self.matches?(crypted, *tokens)
|
22
|
+
# # return true if the crypted string matches the tokens. Depending on
|
23
|
+
# # your algorithm you might decrypt the string then compare it to the
|
24
|
+
# # token, or you might encrypt the tokens and make sure it matches the
|
25
|
+
# # crypted string, its up to you.
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
module CryptoProviders
|
29
|
+
class Sha256
|
30
|
+
# = Sha256
|
31
|
+
#
|
32
|
+
# Uses the Sha256 hash algorithm to encrypt passwords.
|
33
|
+
class V2
|
34
|
+
class << self
|
35
|
+
attr_accessor :join_token
|
36
|
+
|
37
|
+
# The number of times to loop through the encryption.
|
38
|
+
def stretches
|
39
|
+
@stretches ||= 20
|
40
|
+
end
|
41
|
+
attr_writer :stretches
|
42
|
+
|
43
|
+
# Turns your raw password into a Sha256 hash.
|
44
|
+
def encrypt(*tokens)
|
45
|
+
digest = tokens.flatten.join(join_token)
|
46
|
+
stretches.times { digest = Digest::SHA256.digest(digest) }
|
47
|
+
digest.unpack1("H*")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
51
|
+
# were used to encrypt.
|
52
|
+
def matches?(crypted, *tokens)
|
53
|
+
encrypt(*tokens) == crypted
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha2"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
# The acts_as_authentic method has a crypto_provider option. This allows you
|
8
|
+
# to use any type of encryption you like. Just create a class with a class
|
9
|
+
# level encrypt and matches? method. See example below.
|
10
|
+
#
|
11
|
+
# === Example
|
12
|
+
#
|
13
|
+
# class MyAwesomeEncryptionMethod
|
14
|
+
# def self.encrypt(*tokens)
|
15
|
+
# # the tokens passed will be an array of objects, what type of object
|
16
|
+
# # is irrelevant, just do what you need to do with them and return a
|
17
|
+
# # single encrypted string. for example, you will most likely join all
|
18
|
+
# # of the objects into a single string and then encrypt that string
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def self.matches?(crypted, *tokens)
|
22
|
+
# # return true if the crypted string matches the tokens. Depending on
|
23
|
+
# # your algorithm you might decrypt the string then compare it to the
|
24
|
+
# # token, or you might encrypt the tokens and make sure it matches the
|
25
|
+
# # crypted string, its up to you.
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
module CryptoProviders
|
29
|
+
# = Sha256
|
30
|
+
#
|
31
|
+
# Uses the Sha256 hash algorithm to encrypt passwords.
|
32
|
+
class Sha256
|
33
|
+
# V2 hashes the digest bytes in repeated stretches instead of hex characters.
|
34
|
+
autoload :V2, File.join(__dir__, "sha256", "v2")
|
35
|
+
|
36
|
+
class << self
|
37
|
+
attr_accessor :join_token
|
38
|
+
|
39
|
+
# The number of times to loop through the encryption.
|
40
|
+
def stretches
|
41
|
+
@stretches ||= 20
|
42
|
+
end
|
43
|
+
attr_writer :stretches
|
44
|
+
|
45
|
+
# Turns your raw password into a Sha256 hash.
|
46
|
+
def encrypt(*tokens)
|
47
|
+
digest = tokens.flatten.join(join_token)
|
48
|
+
stretches.times { digest = Digest::SHA256.hexdigest(digest) }
|
49
|
+
digest
|
50
|
+
end
|
51
|
+
|
52
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
53
|
+
# were used to encrypt.
|
54
|
+
def matches?(crypted, *tokens)
|
55
|
+
encrypt(*tokens) == crypted
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha2"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
class Sha512
|
9
|
+
# SHA-512 does not have any practical known attacks against it. However,
|
10
|
+
# there are better choices. We recommend transitioning to a more secure,
|
11
|
+
# adaptive hashing algorithm, like scrypt.
|
12
|
+
class V2
|
13
|
+
class << self
|
14
|
+
attr_accessor :join_token
|
15
|
+
|
16
|
+
# The number of times to loop through the encryption.
|
17
|
+
def stretches
|
18
|
+
@stretches ||= 20
|
19
|
+
end
|
20
|
+
attr_writer :stretches
|
21
|
+
|
22
|
+
# Turns your raw password into a Sha512 hash.
|
23
|
+
def encrypt(*tokens)
|
24
|
+
digest = tokens.flatten.join(join_token)
|
25
|
+
stretches.times do
|
26
|
+
digest = Digest::SHA512.digest(digest)
|
27
|
+
end
|
28
|
+
digest.unpack1("H*")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
32
|
+
# were used to encrypt.
|
33
|
+
def matches?(crypted, *tokens)
|
34
|
+
encrypt(*tokens) == crypted
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha2"
|
4
|
+
|
5
|
+
module Authentication
|
6
|
+
module Logic
|
7
|
+
module CryptoProviders
|
8
|
+
# SHA-512 does not have any practical known attacks against it. However,
|
9
|
+
# there are better choices. We recommend transitioning to a more secure,
|
10
|
+
# adaptive hashing algorithm, like scrypt.
|
11
|
+
class Sha512
|
12
|
+
# V2 hashes the digest bytes in repeated stretches instead of hex characters.
|
13
|
+
autoload :V2, File.join(__dir__, "sha512", "v2")
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :join_token
|
17
|
+
|
18
|
+
# The number of times to loop through the encryption.
|
19
|
+
def stretches
|
20
|
+
@stretches ||= 20
|
21
|
+
end
|
22
|
+
attr_writer :stretches
|
23
|
+
|
24
|
+
# Turns your raw password into a Sha512 hash.
|
25
|
+
def encrypt(*tokens)
|
26
|
+
digest = tokens.flatten.join(join_token)
|
27
|
+
stretches.times { digest = Digest::SHA512.hexdigest(digest) }
|
28
|
+
digest
|
29
|
+
end
|
30
|
+
|
31
|
+
# Does the crypted password match the tokens? Uses the same tokens that
|
32
|
+
# were used to encrypt.
|
33
|
+
def matches?(crypted, *tokens)
|
34
|
+
encrypt(*tokens) == crypted
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Authentication
|
4
|
+
module Logic
|
5
|
+
# The acts_as_authentic method has a crypto_provider option. This allows you
|
6
|
+
# to use any type of encryption you like. Just create a class with a class
|
7
|
+
# level encrypt and matches? method. See example below.
|
8
|
+
#
|
9
|
+
# === Example
|
10
|
+
#
|
11
|
+
# class MyAwesomeEncryptionMethod
|
12
|
+
# def self.encrypt(*tokens)
|
13
|
+
# # The tokens passed will be an array of objects, what type of object
|
14
|
+
# # is irrelevant, just do what you need to do with them and return a
|
15
|
+
# # single encrypted string. For example, you will most likely join all
|
16
|
+
# # of the objects into a single string and then encrypt that string.
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def self.matches?(crypted, *tokens)
|
20
|
+
# # Return true if the crypted string matches the tokens. Depending on
|
21
|
+
# # your algorithm you might decrypt the string then compare it to the
|
22
|
+
# # token, or you might encrypt the tokens and make sure it matches the
|
23
|
+
# # crypted string, its up to you.
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
module CryptoProviders
|
27
|
+
autoload :MD5, "auth/logic/crypto_providers/md5"
|
28
|
+
autoload :Sha1, "auth/logic/crypto_providers/sha1"
|
29
|
+
autoload :Sha256, "auth/logic/crypto_providers/sha256"
|
30
|
+
autoload :Sha512, "auth/logic/crypto_providers/sha512"
|
31
|
+
autoload :BCrypt, "auth/logic/crypto_providers/bcrypt"
|
32
|
+
autoload :SCrypt, "auth/logic/crypto_providers/scrypt"
|
33
|
+
|
34
|
+
# Guide users to choose a better crypto provider.
|
35
|
+
class Guidance
|
36
|
+
BUILTIN_PROVIDER_PREFIX = "Authentication::Logic::CryptoProviders::"
|
37
|
+
NONADAPTIVE_ALGORITHM = <<~EOS
|
38
|
+
You have selected %s as your auth-logic crypto provider. This algorithm
|
39
|
+
does not have any practical known attacks against it. However, there are
|
40
|
+
better choices.
|
41
|
+
|
42
|
+
Authentication::Logic has no plans yet to deprecate this crypto provider. However,
|
43
|
+
we recommend transitioning to a more secure, adaptive hashing algorithm,
|
44
|
+
like scrypt. Adaptive algorithms are designed to slow down brute force
|
45
|
+
attacks, and over time the iteration count can be increased to make it
|
46
|
+
slower, so it remains resistant to brute-force search attacks even in
|
47
|
+
the face of increasing computation power.
|
48
|
+
|
49
|
+
Use the transition_from_crypto_providers option to make the transition
|
50
|
+
painless for your users.
|
51
|
+
EOS
|
52
|
+
VULNERABLE_ALGORITHM = <<~EOS
|
53
|
+
You have selected %s as your auth-logic crypto provider. It is a poor
|
54
|
+
choice because there are known attacks against this algorithm.
|
55
|
+
|
56
|
+
Authentication::Logic has no plans yet to deprecate this crypto provider. However,
|
57
|
+
we recommend transitioning to a secure hashing algorithm. We recommend
|
58
|
+
an adaptive algorithm, like scrypt.
|
59
|
+
|
60
|
+
Use the transition_from_crypto_providers option to make the transition
|
61
|
+
painless for your users.
|
62
|
+
EOS
|
63
|
+
|
64
|
+
def initialize(provider)
|
65
|
+
@provider = provider
|
66
|
+
end
|
67
|
+
|
68
|
+
def impart_wisdom
|
69
|
+
return unless @provider.is_a?(Class)
|
70
|
+
|
71
|
+
# We can only impart wisdom about our own built-in providers.
|
72
|
+
absolute_name = @provider.name
|
73
|
+
return unless absolute_name.start_with?(BUILTIN_PROVIDER_PREFIX)
|
74
|
+
|
75
|
+
# Inspect the string name of the provider, rather than using the
|
76
|
+
# constants in our `when` clauses. If we used the constants, we'd
|
77
|
+
# negate the benefits of the `autoload` above.
|
78
|
+
name = absolute_name.demodulize
|
79
|
+
case name
|
80
|
+
when "MD5", "Sha1"
|
81
|
+
warn(format(VULNERABLE_ALGORITHM, name))
|
82
|
+
when "Sha256", "Sha512"
|
83
|
+
warn(format(NONADAPTIVE_ALGORITHM, name))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|