crypt_checkpass 1
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/.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
|