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