crypt_checkpass 1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +32 -0
- data/.rubocop.yml +96 -0
- data/.yardopts +5 -0
- data/Gemfile +41 -0
- data/LICENSE.txt +19 -0
- data/README.md +114 -0
- data/Rakefile +46 -0
- data/bin/console +29 -0
- data/bin/setup +24 -0
- data/crypt_checkpass.gemspec +55 -0
- data/lib/crypt_checkpass.rb +75 -0
- data/lib/crypt_checkpass/api.rb +212 -0
- data/lib/crypt_checkpass/argon2.rb +204 -0
- data/lib/crypt_checkpass/bcrypt.rb +217 -0
- data/lib/crypt_checkpass/pbkdf2.rb +177 -0
- data/lib/crypt_checkpass/phc_string_format.rb +180 -0
- data/lib/crypt_checkpass/scrypt.rb +214 -0
- data/lib/crypt_checkpass/sha2.rb +162 -0
- metadata +201 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level: 2 -*-
|
3
|
+
# -*- frozen_string_literal: true -*-
|
4
|
+
# -*- warn_indent: true -*-
|
5
|
+
|
6
|
+
# Copyright (c) 2018 Urabe, Shyouhei
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
# Parses what the given _hash_ is, apply the same hasing against _pass_, then
|
27
|
+
# compares the hashed _pass_ and the given _hash_.
|
28
|
+
#
|
29
|
+
# @param pass [String] password string.
|
30
|
+
# @param hash [String] hashed string.
|
31
|
+
# @return [true] they are identical.
|
32
|
+
# @return [false] they are distinct.
|
33
|
+
# @raise [NotImplementedError] don't know how to parse _hash_.
|
34
|
+
def crypt_checkpass? pass, hash
|
35
|
+
return CryptCheckpass::crypt_checkpass? pass, hash
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generates new password hashes. The provided password is randomly salted, then
|
39
|
+
# hashed using the parameter.
|
40
|
+
#
|
41
|
+
# @overload crypt_newhash(password, perf)
|
42
|
+
# The pref argument identifies the preferred hashing algorithm and
|
43
|
+
# parameters. Possible values are:
|
44
|
+
#
|
45
|
+
# - `"bcrypt,<rounds>"`
|
46
|
+
# - `"blowfish,<rounds>"`
|
47
|
+
#
|
48
|
+
# where "rounds" can be a number between 4 and 31, or "a" for default.
|
49
|
+
#
|
50
|
+
# @note This usage is for OpenBSD fans.
|
51
|
+
# @see https://man.openbsd.org/crypt_newhash.3 crypt_newhash(3)
|
52
|
+
# @param password [String] bare, unhashed binary password.
|
53
|
+
# @param pref [String] algorithm preference specifier.
|
54
|
+
# @raise [NotImplementedError] pref not understandable.
|
55
|
+
# @return [String] hashed digest string of password.
|
56
|
+
#
|
57
|
+
# @overload crypt_newhash(password, id:, **kwargs)
|
58
|
+
# At least `:id` argument must be provided this case, which is the name of
|
59
|
+
# key deliveration function (the ID that the PHC string format says).
|
60
|
+
#
|
61
|
+
# @param password [String] bare, unhashed binary password.
|
62
|
+
# @param id [String] name of the function.
|
63
|
+
# @param kwargs [Symbol=>String,Integer] passed to the KDF.
|
64
|
+
# @return [String] hashed digest string of password.
|
65
|
+
# @raise [NotImplementedError] unknown KDF is specified.
|
66
|
+
def crypt_newhash password, pref = nil, id: nil, **kwargs
|
67
|
+
return CryptCheckpass::crypt_newhash password, pref, id: id, **kwargs
|
68
|
+
end
|
69
|
+
|
70
|
+
require_relative 'crypt_checkpass/api'
|
71
|
+
require_relative 'crypt_checkpass/argon2'
|
72
|
+
require_relative 'crypt_checkpass/bcrypt'
|
73
|
+
require_relative 'crypt_checkpass/pbkdf2'
|
74
|
+
require_relative 'crypt_checkpass/scrypt'
|
75
|
+
require_relative 'crypt_checkpass/sha2'
|
@@ -0,0 +1,212 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level: 2 -*-
|
3
|
+
# -*- frozen_string_literal: true -*-
|
4
|
+
# -*- warn_indent: true -*-
|
5
|
+
|
6
|
+
# Copyright (c) 2018 Urabe, Shyouhei
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
# Mother of all KDF classes.
|
27
|
+
#
|
28
|
+
# Subclasses of this are expected to implement the following 4 class methods:
|
29
|
+
#
|
30
|
+
# - `subclass.provide?(id)`
|
31
|
+
# - `subclass.newhash(pass, id: id, ...)`
|
32
|
+
# - `subclass.understand?(hash)`
|
33
|
+
# - `subclass.checkpass?(pass, hash)`
|
34
|
+
#
|
35
|
+
# If a subclass's `provide?` returns `true` for an id, then that class is
|
36
|
+
# responsible for generating new hash of that id. Likewise if `understand?`
|
37
|
+
# returns `true` for a hash, that should be able to checkpass.
|
38
|
+
#
|
39
|
+
# Caveats:
|
40
|
+
#
|
41
|
+
# - You don't have to provide all of those methods. It is completely
|
42
|
+
# reasonable to have a hash that is unable to generate new one, but still
|
43
|
+
# able to check existing ones.
|
44
|
+
class CryptCheckpass
|
45
|
+
@kdfs = [] # see below
|
46
|
+
end
|
47
|
+
|
48
|
+
class << CryptCheckpass
|
49
|
+
public
|
50
|
+
|
51
|
+
# @!group API entry points
|
52
|
+
|
53
|
+
# (see ::#crypt_checkpass?)
|
54
|
+
def crypt_checkpass? pass, hash
|
55
|
+
kdf = find_kdf_by_string hash
|
56
|
+
return kdf.checkpass? pass, hash
|
57
|
+
end
|
58
|
+
|
59
|
+
# (see ::#crypt_newhash)
|
60
|
+
def crypt_newhash password, pref = nil, id: nil, **kwargs
|
61
|
+
raise ArgumentError, <<-"end".strip if pref && id
|
62
|
+
wrong number of arguments (given 2, expected 1)
|
63
|
+
end
|
64
|
+
raise ArgumentError, <<-"end".strip, kwargs.keys if pref &&! kwargs.empty?
|
65
|
+
unknown key: %p
|
66
|
+
end
|
67
|
+
|
68
|
+
if pref then
|
69
|
+
require_relative 'bcrypt'
|
70
|
+
return CryptCheckpass::Bcrypt.new_with_openbsd_pref password, pref
|
71
|
+
else
|
72
|
+
kdf = find_kdf_by_id id
|
73
|
+
return kdf.newhash password, id: id, **kwargs
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @!endgroup
|
78
|
+
|
79
|
+
# @!group Inteacts with subclasses
|
80
|
+
|
81
|
+
# Checks if the given ID can be handled by this class. A class is
|
82
|
+
# free to handle several IDs, like 'argon2i', 'argon2d', ...
|
83
|
+
#
|
84
|
+
# @param id [String] hash function ID.
|
85
|
+
# @return [true] it does.
|
86
|
+
# @return [false] it desn't.
|
87
|
+
def provide? id
|
88
|
+
return false # default false
|
89
|
+
end
|
90
|
+
|
91
|
+
# Checks if the given hash string can be handled by this class.
|
92
|
+
#
|
93
|
+
# @param str [String] a good hashed string.
|
94
|
+
# @return [true] it does.
|
95
|
+
# @return [false] it desn't.
|
96
|
+
def understand? str
|
97
|
+
return false # default false
|
98
|
+
end
|
99
|
+
|
100
|
+
# Checks if the given password matches the hash.
|
101
|
+
#
|
102
|
+
# @param pass [String] a password to test.
|
103
|
+
# @param hash [String] a good hash digest string.
|
104
|
+
# @return [true] they are identical.
|
105
|
+
# @return [false] they are distinct.
|
106
|
+
# @raise [NotImplementedError] don't know how to parse _hash_.
|
107
|
+
def checkpass? pass, hash
|
108
|
+
return false # default false
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generate a new password hash string.
|
112
|
+
#
|
113
|
+
# @note There is no way to specify salt. That's a bad idea.
|
114
|
+
# @return [String] hashed digest string of password.
|
115
|
+
def newhash *;
|
116
|
+
raise 'NOTREACHED'
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
undef :new
|
122
|
+
|
123
|
+
# @!group @shyouhei's "angry extension to core" corner
|
124
|
+
|
125
|
+
# Utility raise + printf function. It is quite hard to think of exceptions
|
126
|
+
# that only concern fixed strings. @shyouhei really doesn't understand why
|
127
|
+
# this is not a canon.
|
128
|
+
#
|
129
|
+
# @overload raise(klass, fmt, *va_args)
|
130
|
+
# @param klass [Class] exception class.
|
131
|
+
# @param fmt [String] printf-format string.
|
132
|
+
# @param va_args [Array] anything.
|
133
|
+
# @raise [klass] always raises a klass instance.
|
134
|
+
#
|
135
|
+
# @overload raise(fmt, *va_args)
|
136
|
+
# @param fmt [String] printf-format string.
|
137
|
+
# @param va_args [Array] anything.
|
138
|
+
# @raise [RuntimeError] always raises a RuntimeError.
|
139
|
+
def raise class_or_string, *argv
|
140
|
+
case class_or_string
|
141
|
+
when Class, Exception then
|
142
|
+
klass = class_or_string
|
143
|
+
string = sprintf(*argv)
|
144
|
+
when String then
|
145
|
+
klass = RuntimeError
|
146
|
+
string = class_or_string % argv
|
147
|
+
else # recursion
|
148
|
+
raise TypeError, <<-"end".strip
|
149
|
+
wrong argument type %p (%p expected)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
return super klass, string, caller
|
153
|
+
end
|
154
|
+
|
155
|
+
# Utility gem + require function. It is often the case a library is a gem
|
156
|
+
# and calling gem before require is desirable.@shyouhei really doesn't
|
157
|
+
# understand why this is not a canon.
|
158
|
+
#
|
159
|
+
# @return [void]
|
160
|
+
# @param gem [String] gem name.
|
161
|
+
# @param lib [String] library name.
|
162
|
+
# @raise [Gem::LoadError] gem not found.
|
163
|
+
# @raise [LoadError] lib not found.
|
164
|
+
def require gem, lib = gem
|
165
|
+
Kernel.gem gem
|
166
|
+
Kernel.require lib
|
167
|
+
end
|
168
|
+
|
169
|
+
if defined? %r/match?/.match? then
|
170
|
+
# Fallback routine counterpart.
|
171
|
+
# @param re [Regexp] the language to accept.
|
172
|
+
# @param str [String] target string to test.
|
173
|
+
# @return [true] accepted.
|
174
|
+
# @return [false] otherwise.
|
175
|
+
def match? re, str
|
176
|
+
return re.match? str
|
177
|
+
end
|
178
|
+
else
|
179
|
+
# Fallback routine for ruby versions without Regexp#match?
|
180
|
+
# @param re [Regexp] the language to accept.
|
181
|
+
# @param str [String] target string to test.
|
182
|
+
# @return [true] accepted.
|
183
|
+
# @return [false] otherwise.
|
184
|
+
def match? re, str
|
185
|
+
md = re.match str
|
186
|
+
return !!md
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# @!endgroup
|
191
|
+
|
192
|
+
def inherited klass
|
193
|
+
super
|
194
|
+
@kdfs.push klass
|
195
|
+
end
|
196
|
+
|
197
|
+
def find_kdf_by_id id
|
198
|
+
kdf = @kdfs.find {|i| i.provide? id }
|
199
|
+
return kdf if kdf
|
200
|
+
raise ArgumentError, <<-"end".strip, id
|
201
|
+
don't know how to generate %s hash.
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def find_kdf_by_string str
|
206
|
+
kdf = @kdfs.find {|i| i.understand? str }
|
207
|
+
return kdf if kdf
|
208
|
+
raise ArgumentError, <<-"end".strip, str
|
209
|
+
don't know how to parse %p, maybe clobbered?
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#! /your/favourite/path/to/ruby
|
2
|
+
# -*- mode: ruby; coding: utf-8; indent-tabs-mode: nil; ruby-indent-level: 2 -*-
|
3
|
+
# -*- frozen_string_literal: true -*-
|
4
|
+
# -*- warn_indent: true -*-
|
5
|
+
|
6
|
+
# Copyright (c) 2018 Urabe, Shyouhei
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
# of this software and associated documentation files (the "Software"), to deal
|
10
|
+
# in the Software without restriction, including without limitation the rights
|
11
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
# copies of the Software, and to permit persons to whom the Software is
|
13
|
+
# furnished to do so, subject to the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
24
|
+
# SOFTWARE.
|
25
|
+
|
26
|
+
# Argon2 the Password Hashing Competition winner.
|
27
|
+
#
|
28
|
+
# ### Newhash:
|
29
|
+
#
|
30
|
+
# You can use `crypto_newhash` to create a new password hash using argon2:
|
31
|
+
#
|
32
|
+
# ```ruby
|
33
|
+
# crypt_newhash(password, id: 'argon2i', m_cost: 12, t_cost: 3)
|
34
|
+
# ```
|
35
|
+
#
|
36
|
+
# where:
|
37
|
+
#
|
38
|
+
# - `password` is the raw binary password that you want to digest.
|
39
|
+
#
|
40
|
+
# - `id` is "argon2i" when you want an argon2 hash. Due to underlying
|
41
|
+
# ruby-argons gem's restriction we do not support other argon2 variants.
|
42
|
+
#
|
43
|
+
# - `m_cost` and `t_cost` are both integer parameter to the algorighm.
|
44
|
+
#
|
45
|
+
# The generated password hash has following format.
|
46
|
+
#
|
47
|
+
# ### Format:
|
48
|
+
#
|
49
|
+
# Argon2 specifies its hash structure in detail named "PHC String Format",
|
50
|
+
# **then ignored the format by itself**. See also [1]. Empirical findings
|
51
|
+
# show that the algorithm now has the following output format:
|
52
|
+
#
|
53
|
+
# ```ruby
|
54
|
+
# %r{
|
55
|
+
# (?<digits> 0|[1-9]\d* ){0}
|
56
|
+
# (?<b64> [a-zA-Z0-9+/] ){0}
|
57
|
+
# (?<id> argon2 (i|d|id) ){0}
|
58
|
+
# (?<v> v=19 ){0}
|
59
|
+
# (?<m> m=\g<digits> ){0}
|
60
|
+
# (?<t> t=\g<digits> ){0}
|
61
|
+
# (?<p> p=\g<digits> ){0}
|
62
|
+
# (?<salt> \g<b64>+ ){0}
|
63
|
+
# (?<csum> \g<b64>+ ){0}
|
64
|
+
#
|
65
|
+
# \A [$] \g<id>
|
66
|
+
# (?: [$] \g<v> )?
|
67
|
+
# [$] \g<m>
|
68
|
+
# [,] \g<t>
|
69
|
+
# [,] \g<p>
|
70
|
+
# [$] \g<salt>
|
71
|
+
# [$] \g<csum>
|
72
|
+
# \z
|
73
|
+
# }x
|
74
|
+
# ```
|
75
|
+
#
|
76
|
+
# - `id` is "argon2" + something that denotes the variant of the
|
77
|
+
# hash. Variant "argon2i" seems most widely adopted.
|
78
|
+
#
|
79
|
+
# - `v` is, when available, a number 19. That doesn't mean anything. What
|
80
|
+
# is important is the _absense_ of that parameter, which means the hash was
|
81
|
+
# genrated using old argon2 1.0 and shall be out of date.
|
82
|
+
#
|
83
|
+
# - `m` is the amount of memory filled by the algorithm (2**m KiB). Memory
|
84
|
+
# consumption depends on this parameter.
|
85
|
+
#
|
86
|
+
# - `t` is the mumber of passes over the memory. The running time depends
|
87
|
+
# linearly on this parameter.
|
88
|
+
#
|
89
|
+
# - `p` is the degree of parallelism, called "lanes" in the C implementation.
|
90
|
+
#
|
91
|
+
# - `salt` and `csum` are the salt and checksum strings. Both are encoded in
|
92
|
+
# base64-like strings that do not strictly follow RFC4648. They both can
|
93
|
+
# be arbitrary length. In case there are "unused" bits at the end of those
|
94
|
+
# fields, they shall be zero-filled.
|
95
|
+
#
|
96
|
+
# [1]: https://github.com/P-H-C/phc-winner-argon2/issues/157
|
97
|
+
#
|
98
|
+
# ### Implementation limitations:
|
99
|
+
#
|
100
|
+
# Ruby binding of argon2 library (ruby-argon2) is pretty well designed and can
|
101
|
+
# be recommended for daily uses. You really should use it whenever possible.
|
102
|
+
# The big problem is however, that it only supports argon2i. That is
|
103
|
+
# definitely OK for hash generation. However in verifying, it is desirable to
|
104
|
+
# support other variants.
|
105
|
+
#
|
106
|
+
# In order to reroute this problem we load the ruby-argon2 gem, then ignore its
|
107
|
+
# ruby part and directly call the canonical C implementation via FFI.
|
108
|
+
#
|
109
|
+
# @see https://en.wikipedia.org/wiki/Argon2
|
110
|
+
# @see https://www.cryptolux.org/index.php/Argon2
|
111
|
+
# @example
|
112
|
+
# crypt_newhash 'password', id: 'argon2i'
|
113
|
+
# # => "$argon2i$v=19$m=4096,t=3,p=1$b9AqucWUJADOdNMW8fW+0A$s3+Yno9+X7rpA2AsaG7KnoBtjQiE+AUevLvT7u1lXeA"
|
114
|
+
# @example
|
115
|
+
# crypt_checkpass? 'password', '$argon2i$v=19$m=4096,t=3,p=1$b9AqucWUJADOdNMW8fW+0A$s3+Yno9+X7rpA2AsaG7KnoBtjQiE+AUevLvT7u1lXeA'
|
116
|
+
# # => true
|
117
|
+
class CryptCheckpass::Argon2 < CryptCheckpass
|
118
|
+
|
119
|
+
# (see CryptCheckpass.understand?)
|
120
|
+
def self.understand? str
|
121
|
+
return match? str, %r{
|
122
|
+
(?<id> argon2 (i|d|id) ){0}
|
123
|
+
(?<digits> 0|[1-9]\d* ){0}
|
124
|
+
(?<b64> [a-zA-Z0-9+/] ){0}
|
125
|
+
(?<v> v=19 ){0}
|
126
|
+
(?<m> m=\g<digits> ){0}
|
127
|
+
(?<t> t=\g<digits> ){0}
|
128
|
+
(?<p> p=\g<digits> ){0}
|
129
|
+
(?<salt> \g<b64>+ ){0}
|
130
|
+
(?<csum> \g<b64>+ ){0}
|
131
|
+
|
132
|
+
\A [$] \g<id>
|
133
|
+
(?: [$] \g<v> )?
|
134
|
+
[$] \g<m>
|
135
|
+
[,] \g<t>
|
136
|
+
[,] \g<p>
|
137
|
+
[$] \g<salt>
|
138
|
+
[$] \g<csum>
|
139
|
+
\z
|
140
|
+
}x
|
141
|
+
end
|
142
|
+
|
143
|
+
# (see CryptCheckpass.checkpass?)
|
144
|
+
def self.checkpass? pass, hash
|
145
|
+
h = hash
|
146
|
+
p = pass
|
147
|
+
n = pass.bytesize
|
148
|
+
|
149
|
+
__load_argon2_dll
|
150
|
+
|
151
|
+
case hash
|
152
|
+
when /\A\$argon2i\$/ then ret = @dll.argon2i_verify h, p, n
|
153
|
+
when /\A\$argon2d\$/ then ret = @dll.argon2d_verify h, p, n
|
154
|
+
when /\A\$argon2id\$/ then ret = @dll.argon2id_verify h, p, n
|
155
|
+
else raise ArgumentError, "unknown hash format %p", hash
|
156
|
+
end
|
157
|
+
|
158
|
+
case ret
|
159
|
+
when 0 then return true
|
160
|
+
when -35 then return false # ARGON2_VERIFY_MISMATCH
|
161
|
+
else
|
162
|
+
errstr = ::Argon2::ERRORS[ret.abs] || ret.to_s
|
163
|
+
raise ::Argon2::ArgonHashFail, "got %s", errstr
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# (see CryptCheckpass.provide?)
|
168
|
+
# @note we don't support generating argon2d hashs.
|
169
|
+
def self.provide? id
|
170
|
+
return id == 'argon2i'
|
171
|
+
end
|
172
|
+
|
173
|
+
# (see CryptCheckpass.newhash)
|
174
|
+
#
|
175
|
+
# @param pass [String] raw binary password string.
|
176
|
+
# @param id [String] name of the algorithm (ignored)
|
177
|
+
# @param m_cost [Integer] argon2 memory usage (2^m KiB)
|
178
|
+
# @param t_cost [Integer] argon2 iterations.
|
179
|
+
def self.newhash pass, id: 'argon2i', m_cost: 12, t_cost: 3
|
180
|
+
require 'argon2'
|
181
|
+
|
182
|
+
argon2 = ::Argon2::Password.new m_cost: m_cost, t_cost: t_cost
|
183
|
+
return argon2.create pass
|
184
|
+
end
|
185
|
+
|
186
|
+
@m = Thread::Mutex.new
|
187
|
+
|
188
|
+
def self.__load_argon2_dll
|
189
|
+
@m.synchronize do
|
190
|
+
next if defined? @dll
|
191
|
+
require 'argon2'
|
192
|
+
@dll = Module.new do
|
193
|
+
extend FFI::Library
|
194
|
+
lib = FFI::Compiler::Loader.find 'argon2_wrap'
|
195
|
+
fun = %i[argon2i_verify argon2d_verify argon2id_verify]
|
196
|
+
ffi_lib lib
|
197
|
+
fun.each do |f|
|
198
|
+
attach_function f, %i[pointer pointer size_t], :int, blocking: true
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
private_class_method :__load_argon2_dll
|
204
|
+
end
|