argon2id 0.4.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +86 -0
- data/Gemfile +9 -0
- data/LICENSE +11 -0
- data/README.md +285 -0
- data/Rakefile +71 -0
- data/argon2id.gemspec +59 -0
- data/lib/argon2id/password.rb +140 -0
- data/lib/argon2id/version.rb +5 -0
- data/lib/argon2id.rb +105 -0
- data/test/test_hash_encoded.rb +48 -0
- data/test/test_password.rb +172 -0
- data/test/test_verify.rb +35 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ecd94924e9be36c2d5cc914996688424ed5ac72b6a42e9f3c11062d4a95f5a57
|
4
|
+
data.tar.gz: fba3c84cb52265555796ab00e50578fb2711e080fca794e865743f4290c1d43c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a83e14f427cf90a788535a08c992e699501d15678825a1efbef93966859a309d73dfabde9d33aa4e0bde37ac2f97abb0fd911d2faee028eaab55f972583dc2a4
|
7
|
+
data.tar.gz: 9d2d6186851a7ce7ca5ab3a05889ee540820169632d7c98dcf921a61de2731592f6038457f1ad506b858db216710ad6ca1597a4cd56408c1647c297f66a63697
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [0.4.0] - 2024-11-02
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Added support for JRuby 9.4 by adding an implementation of Argon2id hashing
|
13
|
+
and verification using JRuby-OpenSSL's Bouncy Castle internals.
|
14
|
+
- Added `output` to `Argon2id::Password` instances so the actual "output" part
|
15
|
+
of a password hash can be retrieved (and compared)
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
|
19
|
+
- Verifying a password will now consistently raise an `ArgumentError` when
|
20
|
+
given an invalid encoded hash rather than an `Argon2id::Error`
|
21
|
+
|
22
|
+
## [0.3.0] - 2024-11-01
|
23
|
+
|
24
|
+
### Added
|
25
|
+
|
26
|
+
- Expose all parameters of a hash through new readers on `Argon2id::Password`:
|
27
|
+
namely, `type`, `version`, `m_cost`, `t_cost`, and `parallelism`
|
28
|
+
|
29
|
+
### Changed
|
30
|
+
|
31
|
+
- Remove the dependency on the `base64` gem by inlining the definition of
|
32
|
+
`Base64.decode64` (thanks to @etiennebarrie for the tip)
|
33
|
+
|
34
|
+
## [0.2.1] - 2024-11-01
|
35
|
+
|
36
|
+
### Added
|
37
|
+
|
38
|
+
- Anything that can be coerced to a String can now be passed to
|
39
|
+
`Argon2id::Password.new`
|
40
|
+
|
41
|
+
## [0.2.0] - 2024-11-01
|
42
|
+
|
43
|
+
### Added
|
44
|
+
|
45
|
+
- The original salt for an `Argon2id::Password` can now be retrieved with
|
46
|
+
`Argon2id::Password#salt`
|
47
|
+
|
48
|
+
### Changed
|
49
|
+
|
50
|
+
- Encoded hashes are now validated when initialising an `Argon2id::Password`,
|
51
|
+
raising an `ArgumentError` if they are invalid
|
52
|
+
|
53
|
+
## [0.1.2] - 2024-11-01
|
54
|
+
|
55
|
+
### Fixed
|
56
|
+
|
57
|
+
- Validate that the encoded hash passed to `Argon2id::Password.new` is a
|
58
|
+
null-terminated C string, raising an `ArgumentError` if it contains extra null
|
59
|
+
bytes
|
60
|
+
|
61
|
+
## [0.1.1] - 2024-11-01
|
62
|
+
|
63
|
+
### Added
|
64
|
+
|
65
|
+
- RDoc documentation for the API
|
66
|
+
|
67
|
+
### Fixed
|
68
|
+
|
69
|
+
- Saved a superfluous extra byte when allocating the buffer for the encoded
|
70
|
+
hash
|
71
|
+
|
72
|
+
## [0.1.0] - 2024-10-31
|
73
|
+
|
74
|
+
### Added
|
75
|
+
|
76
|
+
- The initial version of the Argon2id gem, providing Ruby bindings to the
|
77
|
+
reference C implementation of Argon2, the password-hashing function that won
|
78
|
+
the Password Hashing Competition.
|
79
|
+
|
80
|
+
[0.4.0]: https://github.com/mudge/argon2id/releases/tag/v0.4.0
|
81
|
+
[0.3.0]: https://github.com/mudge/argon2id/releases/tag/v0.3.0
|
82
|
+
[0.2.1]: https://github.com/mudge/argon2id/releases/tag/v0.2.1
|
83
|
+
[0.2.0]: https://github.com/mudge/argon2id/releases/tag/v0.2.0
|
84
|
+
[0.1.2]: https://github.com/mudge/argon2id/releases/tag/v0.1.2
|
85
|
+
[0.1.1]: https://github.com/mudge/argon2id/releases/tag/v0.1.1
|
86
|
+
[0.1.0]: https://github.com/mudge/argon2id/releases/tag/v0.1.0
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
Copyright (c) 2024 Paul Mucur.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
4
|
+
|
5
|
+
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
6
|
+
|
7
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
8
|
+
|
9
|
+
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
10
|
+
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
# Argon2id - Ruby bindings to the OWASP recommended password-hashing function
|
2
|
+
|
3
|
+
Ruby bindings to the reference C implementation of [Argon2][], the password-hashing
|
4
|
+
function that won the 2015 [Password Hashing Competition][].
|
5
|
+
|
6
|
+
[![Build Status](https://github.com/mudge/argon2id/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/mudge/argon2id/actions)
|
7
|
+
|
8
|
+
**Current version:** 0.4.0
|
9
|
+
**Bundled Argon2 version:** libargon2.1 (20190702)
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Argon2id::Password.create("password").to_s
|
13
|
+
#=> "$argon2id$v=19$m=19456,t=2,p=1$agNV6OfDL1OwE44WdrFCJw$ITrBwvCsW4b5GjgZuL67RCcvVMEWBWXtASc9TVyI3rY"
|
14
|
+
|
15
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")
|
16
|
+
password == "password" #=> true
|
17
|
+
password == "not password" #=> false
|
18
|
+
|
19
|
+
password.m_cost #=> 19456
|
20
|
+
password.salt #=> "e-\xA7\x04U\x81\xA6{v\xF0x\xED\xCC\xD3\x96\xE3"
|
21
|
+
```
|
22
|
+
|
23
|
+
## Table of contents
|
24
|
+
|
25
|
+
* [Why Argon2id?](#why-argon2id)
|
26
|
+
* [Usage](#usage)
|
27
|
+
* [Hashing passwords](#hashing-passwords)
|
28
|
+
* [Verifying passwords](#verifying-passwords)
|
29
|
+
* [Errors](#errors)
|
30
|
+
* [Requirements](#requirements)
|
31
|
+
* [Native gems](#native-gems)
|
32
|
+
* [Verifying the gems](#verifying-the-gems)
|
33
|
+
* [Installing the `ruby` platform gem](#installing-the-ruby-platform-gem)
|
34
|
+
* [Thanks](#thanks)
|
35
|
+
* [Contact](#contact)
|
36
|
+
* [License](#license)
|
37
|
+
* [Dependencies](#dependencies)
|
38
|
+
|
39
|
+
## Why Argon2id?
|
40
|
+
|
41
|
+
> Argon2 is a password-hashing function that summarizes the state of the art in
|
42
|
+
> the design of memory-hard functions and can be used to hash passwords for
|
43
|
+
> credential storage, key derivation, or other applications.
|
44
|
+
>
|
45
|
+
> It has a simple design aimed at the highest memory filling rate and effective
|
46
|
+
> use of multiple computing units, while still providing defense against
|
47
|
+
> tradeoff attacks (by exploiting the cache and memory organization of the
|
48
|
+
> recent processors).
|
49
|
+
|
50
|
+
— [Argon2][]
|
51
|
+
|
52
|
+
> Argon2 was the winner of the 2015 Password Hashing Competition. Out of the
|
53
|
+
> three Argon2 versions, use the Argon2id variant since it provides a balanced
|
54
|
+
> approach to resisting both side-channel and GPU-based attacks.
|
55
|
+
|
56
|
+
— [OWASP Password Storage Cheat Sheet][]
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
|
60
|
+
Install argon2id as a dependency:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# In your Gemfile
|
64
|
+
gem "argon2id"
|
65
|
+
|
66
|
+
# Or without Bundler
|
67
|
+
gem install argon2id
|
68
|
+
```
|
69
|
+
|
70
|
+
Include in your code:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
require "argon2id"
|
74
|
+
```
|
75
|
+
|
76
|
+
### Hashing passwords
|
77
|
+
|
78
|
+
Hash a plain text password (e.g. from user input) with
|
79
|
+
`Argon2id::Password.create`:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
password = Argon2id::Password.create("opensesame")
|
83
|
+
```
|
84
|
+
|
85
|
+
The encoded value of the resulting hash is available via
|
86
|
+
`Argon2id::Password#to_s` (ideal for persisting somewhere):
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
password.to_s
|
90
|
+
#=> "$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU"
|
91
|
+
```
|
92
|
+
|
93
|
+
By default, `Argon2id::Password.create` will use the second set of parameters
|
94
|
+
recommended by [OWASP][OWASP Password Storage Cheat Sheet] but these can be
|
95
|
+
overridden by passing keyword arguments to `Argon2id::Password.create`:
|
96
|
+
|
97
|
+
* `t_cost`: the "time cost" given as a number of iterations (defaults to 2)
|
98
|
+
* `m_cost`: the "memory cost" given in kibibytes (defaults to 19 mebibytes)
|
99
|
+
* `parallelism`: the number of threads and compute lanes to use (defaults to 1)
|
100
|
+
* `salt_len`: the salt size in bytes (defaults to 16)
|
101
|
+
* `output_len`: the desired length of the hash in bytes (defaults to 32)
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
password = Argon2id::Password.create("opensesame", t_cost: 3, m_cost: 12288)
|
105
|
+
password.to_s
|
106
|
+
#=> "$argon2id$v=19$m=12288,t=3,p=1$uukIsLS6y6etvsgoN20kVg$exMvDX/P9exvEPmnZL2gZClRyMdrnqjqyysLMP/VUWA"
|
107
|
+
```
|
108
|
+
|
109
|
+
If you want to override the parameters for all calls to
|
110
|
+
`Argon2id::Password.create`, you can set them on `Argon2id` directly:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
Argon2id.t_cost = 3
|
114
|
+
Argon2id.m_cost = 12288
|
115
|
+
Argon2id.parallelism = 1
|
116
|
+
Argon2id.salt_len = 16
|
117
|
+
Argon2id.output_len = 32
|
118
|
+
```
|
119
|
+
|
120
|
+
### Verifying passwords
|
121
|
+
|
122
|
+
To verify a password against a hash, use `Argon2id::Password#==`:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
password = Argon2id::Password.create("opensesame")
|
126
|
+
password == "opensesame" #=> true
|
127
|
+
password == "notopensesame" #=> false
|
128
|
+
```
|
129
|
+
|
130
|
+
Or, if you only have the hash (e.g. retrieved from storage):
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")
|
134
|
+
password == "opensesame" #=> true
|
135
|
+
password == "notopensesame" #=> false
|
136
|
+
```
|
137
|
+
|
138
|
+
For compatibility with [bcrypt-ruby][], `Argon2id::Password#==` is aliased to `Argon2id::Password.is_password?`:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")
|
142
|
+
password.is_password?("opensesame") #=> true
|
143
|
+
password.is_password?("notopensesame") #=> false
|
144
|
+
```
|
145
|
+
|
146
|
+
The various parts of the encoded password can be retrieved:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
150
|
+
password.type #=> "argon2id"
|
151
|
+
password.version #=> 19
|
152
|
+
password.m_cost #=> 256
|
153
|
+
password.t_cost #=> 2
|
154
|
+
password.parallelism #=> 1
|
155
|
+
password.salt #=> "somesalt"
|
156
|
+
password.output
|
157
|
+
#=> "\x9D\xFE\xB9\x10\xE8\v\xAD\x03\x11\xFE\xE2\x0F\x9C\x0E+\x12\xC1y\x87\xB4\xCA\xC9\f.\xF5M[0!\xC6\x8B\xFE"
|
158
|
+
```
|
159
|
+
|
160
|
+
### Errors
|
161
|
+
|
162
|
+
Any errors returned from Argon2 will be raised as `Argon2id::Error`, e.g.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
Argon2id::Password.create("password", salt_len: 0)
|
166
|
+
# Salt is too short (Argon2id::Error)
|
167
|
+
```
|
168
|
+
|
169
|
+
## Requirements
|
170
|
+
|
171
|
+
This gem requires any of the following to run:
|
172
|
+
|
173
|
+
* [Ruby](https://www.ruby-lang.org/en/) 2.6 to 3.3
|
174
|
+
* [JRuby](https://www.jruby.org) 9.4
|
175
|
+
* [TruffleRuby](https://www.graalvm.org/ruby/) 24.1
|
176
|
+
|
177
|
+
> [!NOTE]
|
178
|
+
> The JRuby version of the gem uses
|
179
|
+
> [JRuby-OpenSSL](https://github.com/jruby/jruby-openssl)'s implementation of
|
180
|
+
> Argon2 instead of the reference C implementation.
|
181
|
+
|
182
|
+
### Native gems
|
183
|
+
|
184
|
+
Where possible, a pre-compiled native gem will be provided for the following platforms:
|
185
|
+
|
186
|
+
* Linux
|
187
|
+
* `aarch64-linux` and `arm-linux` (requires [glibc](https://www.gnu.org/software/libc/) 2.29+)
|
188
|
+
* `x86-linux` and `x86_64-linux` (requires [glibc](https://www.gnu.org/software/libc/) 2.17+)
|
189
|
+
* [musl](https://musl.libc.org/)-based systems such as [Alpine](https://alpinelinux.org) are supported as long as a [glibc-compatible library is installed](https://wiki.alpinelinux.org/wiki/Running_glibc_programs)
|
190
|
+
* macOS `x86_64-darwin` and `arm64-darwin`
|
191
|
+
* Windows `x64-mingw32` and `x64-mingw-ucrt`
|
192
|
+
* Java: any platform running JRuby 9.4 or higher
|
193
|
+
|
194
|
+
### Verifying the gems
|
195
|
+
|
196
|
+
SHA256 checksums are included in the [release
|
197
|
+
notes](https://github.com/mudge/argon2id/releases) for each version and can be
|
198
|
+
checked with `sha256sum`, e.g.
|
199
|
+
|
200
|
+
```console
|
201
|
+
$ gem fetch argon2id -v 0.3.0
|
202
|
+
Fetching argon2id-0.3.0-arm64-darwin.gem
|
203
|
+
Downloaded argon2id-0.3.0-arm64-darwin
|
204
|
+
$ sha256sum argon2id-0.3.0-arm64-darwin.gem
|
205
|
+
9d49de6840942b48d020dddd422a1577fde7289ccb08a637bdb29f4a09b4e181 argon2id-0.3.0-arm64-darwin.gem
|
206
|
+
```
|
207
|
+
|
208
|
+
[GPG](https://www.gnupg.org/) signatures are attached to each release (the
|
209
|
+
assets ending in `.sig`) and can be verified if you import [our signing key
|
210
|
+
`0x39AC3530070E0F75`](https://mudge.name/39AC3530070E0F75.asc) (or fetch it
|
211
|
+
from a public keyserver, e.g. `gpg --keyserver keyserver.ubuntu.com --recv-key
|
212
|
+
0x39AC3530070E0F75`):
|
213
|
+
|
214
|
+
```console
|
215
|
+
$ gpg --verify argon2id-0.3.0-arm64-darwin.gem.sig argon2id-0.3.0-arm64-darwin.gem
|
216
|
+
gpg: Signature made Fri 1 Nov 18:15:47 2024 GMT
|
217
|
+
gpg: using RSA key 702609D9C790F45B577D7BEC39AC3530070E0F75
|
218
|
+
gpg: Good signature from "Paul Mucur <mudge@mudge.name>" [unknown]
|
219
|
+
gpg: aka "Paul Mucur <paul@ghostcassette.com>" [unknown]
|
220
|
+
gpg: WARNING: This key is not certified with a trusted signature!
|
221
|
+
gpg: There is no indication that the signature belongs to the owner.
|
222
|
+
Primary key fingerprint: 7026 09D9 C790 F45B 577D 7BEC 39AC 3530 070E 0F75
|
223
|
+
```
|
224
|
+
|
225
|
+
The fingerprint should be as shown above or you can independently verify it
|
226
|
+
with the ones shown in the footer of https://mudge.name.
|
227
|
+
|
228
|
+
### Installing the `ruby` platform gem
|
229
|
+
|
230
|
+
> [!WARNING]
|
231
|
+
> We strongly recommend using the native gems where possible to avoid the need
|
232
|
+
> for compiling the C extension and its dependencies which will take longer and
|
233
|
+
> be less reliable.
|
234
|
+
|
235
|
+
If you wish to compile the gem, you will need to explicitly install the `ruby` platform gem:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
# In your Gemfile with Bundler 2.3.18+
|
239
|
+
gem "argon2id", force_ruby_platform: true
|
240
|
+
|
241
|
+
# With Bundler 2.1+
|
242
|
+
bundle config set force_ruby_platform true
|
243
|
+
|
244
|
+
# With older versions of Bundler
|
245
|
+
bundle config force_ruby_platform true
|
246
|
+
|
247
|
+
# Without Bundler
|
248
|
+
gem install argon2id --platform=ruby
|
249
|
+
```
|
250
|
+
|
251
|
+
You will need a full compiler toolchain for compiling Ruby C extensions (see
|
252
|
+
[Nokogiri's "The Compiler
|
253
|
+
Toolchain"](https://nokogiri.org/tutorials/installing_nokogiri.html#appendix-a-the-compiler-toolchain))
|
254
|
+
plus the toolchain required for compiling the vendored version of Argon2.
|
255
|
+
|
256
|
+
## Thanks
|
257
|
+
|
258
|
+
* Thanks to [Mike Dalessio](https://github.com/flavorjones) for his advice and
|
259
|
+
[Ruby C Extensions Explained](https://github.com/flavorjones/ruby-c-extensions-explained)
|
260
|
+
project
|
261
|
+
|
262
|
+
## Contact
|
263
|
+
|
264
|
+
All issues and suggestions should go to [GitHub
|
265
|
+
Issues](https://github.com/mudge/argon2id/issues).
|
266
|
+
|
267
|
+
## License
|
268
|
+
|
269
|
+
This library is licensed under the BSD 3-Clause License, see `LICENSE`.
|
270
|
+
|
271
|
+
Copyright © 2024, Paul Mucur.
|
272
|
+
|
273
|
+
### Dependencies
|
274
|
+
|
275
|
+
The source code of [Argon2][] is distributed in the gem. This code is copyright
|
276
|
+
© 2015 Daniel Dinu, Dmitry Khovratovich (main authors), Jean-Philippe Aumasson
|
277
|
+
and Samuel Neves, and dual licensed under the [CC0 License][] and the [Apache
|
278
|
+
2.0 License][].
|
279
|
+
|
280
|
+
[Argon2]: https://github.com/P-H-C/phc-winner-argon2/
|
281
|
+
[OWASP Password Storage Cheat Sheet]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
|
282
|
+
[bcrypt-ruby]: https://github.com/bcrypt-ruby/bcrypt-ruby
|
283
|
+
[CC0 License]: https://creativecommons.org/about/cc0
|
284
|
+
[Apache 2.0 License]: https://www.apache.org/licenses/LICENSE-2.0
|
285
|
+
[Password Hashing Competition]: https://www.password-hashing.net
|
data/Rakefile
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "rake/extensiontask"
|
2
|
+
require "rake_compiler_dock"
|
3
|
+
require "minitest/test_task"
|
4
|
+
|
5
|
+
CLEAN.add("lib/**/*.{o,so,bundle}", "pkg")
|
6
|
+
|
7
|
+
cross_platforms = %w[
|
8
|
+
aarch64-linux
|
9
|
+
arm-linux
|
10
|
+
arm64-darwin
|
11
|
+
x64-mingw-ucrt
|
12
|
+
x64-mingw32
|
13
|
+
x86-linux
|
14
|
+
x86-mingw32
|
15
|
+
x86_64-darwin
|
16
|
+
x86_64-linux
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
ENV["RUBY_CC_VERSION"] = %w[3.3.0 3.2.0 3.1.0 3.0.0 2.7.0 2.6.0].join(":")
|
20
|
+
|
21
|
+
gemspec = Gem::Specification.load("argon2id.gemspec")
|
22
|
+
|
23
|
+
if RUBY_PLATFORM == "java"
|
24
|
+
gemspec.files.reject! { |path| File.fnmatch?("ext/*", path) }
|
25
|
+
gemspec.extensions.clear
|
26
|
+
gemspec.platform = Gem::Platform.new("java")
|
27
|
+
gemspec.required_ruby_version = ">= 3.1.0"
|
28
|
+
end
|
29
|
+
|
30
|
+
Gem::PackageTask.new(gemspec).define
|
31
|
+
|
32
|
+
Rake::ExtensionTask.new("argon2id", gemspec) do |e|
|
33
|
+
e.cross_compile = true
|
34
|
+
e.cross_platform = cross_platforms
|
35
|
+
end
|
36
|
+
|
37
|
+
Minitest::TestTask.create
|
38
|
+
|
39
|
+
begin
|
40
|
+
require "ruby_memcheck"
|
41
|
+
|
42
|
+
namespace :test do
|
43
|
+
RubyMemcheck::TestTask.new(valgrind: :compile)
|
44
|
+
end
|
45
|
+
rescue LoadError
|
46
|
+
# Only define the test:valgrind task if ruby_memcheck is installed
|
47
|
+
end
|
48
|
+
|
49
|
+
namespace :gem do
|
50
|
+
cross_platforms.each do |platform|
|
51
|
+
desc "Compile and build native gem for #{platform}"
|
52
|
+
task platform do
|
53
|
+
RakeCompilerDock.sh <<~SCRIPT, platform: platform, verbose: true
|
54
|
+
gem install bundler --no-document &&
|
55
|
+
bundle &&
|
56
|
+
bundle exec rake native:#{platform} pkg/#{gemspec.full_name}-#{Gem::Platform.new(platform)}.gem PATH="/usr/local/bin:$PATH"
|
57
|
+
SCRIPT
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Compile gem for JRuby"
|
62
|
+
task :jruby do
|
63
|
+
RakeCompilerDock.sh <<~SCRIPT, rubyvm: "jruby", platform: "jruby", verbose: true
|
64
|
+
gem install bundler --no-document &&
|
65
|
+
bundle &&
|
66
|
+
bundle exec rake gem
|
67
|
+
SCRIPT
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
task default: [:compile, :test]
|
data/argon2id.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/argon2id/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "argon2id"
|
7
|
+
s.version = Argon2id::VERSION
|
8
|
+
s.summary = "Ruby bindings to Argon2"
|
9
|
+
s.description = "Ruby bindings to the reference C implementation of Argon2, the password-hashing function that won the 2015 Password Hashing Competition."
|
10
|
+
s.license = "BSD-3-Clause"
|
11
|
+
s.authors = ["Paul Mucur"]
|
12
|
+
s.homepage = "https://github.com/mudge/argon2id"
|
13
|
+
s.metadata = {
|
14
|
+
"bug_tracker_uri" => "https://github.com/mudge/argon2id/issues",
|
15
|
+
"changelog_uri" => "https://github.com/mudge/argon2id/blob/main/CHANGELOG.md",
|
16
|
+
"funding_uri" => "https://github.com/sponsors/mudge",
|
17
|
+
"homepage_uri" => "https://github.com/mudge/argon2id",
|
18
|
+
"source_code_uri" => "https://github.com/mudge/argon2id",
|
19
|
+
"rubygems_mfa_required" => "true"
|
20
|
+
}
|
21
|
+
s.required_ruby_version = ">= 2.6.0"
|
22
|
+
s.extensions = ["ext/argon2id/extconf.rb"]
|
23
|
+
s.files = [
|
24
|
+
"CHANGELOG.md",
|
25
|
+
"Gemfile",
|
26
|
+
"LICENSE",
|
27
|
+
"README.md",
|
28
|
+
"Rakefile",
|
29
|
+
"argon2id.gemspec",
|
30
|
+
"ext/argon2id/argon2id.c",
|
31
|
+
"ext/argon2id/extconf.rb",
|
32
|
+
"ext/argon2id/libargon2/LICENSE",
|
33
|
+
"ext/argon2id/libargon2/argon2.c",
|
34
|
+
"ext/argon2id/libargon2/argon2.h",
|
35
|
+
"ext/argon2id/libargon2/blake2/blake2-impl.h",
|
36
|
+
"ext/argon2id/libargon2/blake2/blake2.h",
|
37
|
+
"ext/argon2id/libargon2/blake2/blake2b.c",
|
38
|
+
"ext/argon2id/libargon2/blake2/blamka-round-opt.h",
|
39
|
+
"ext/argon2id/libargon2/blake2/blamka-round-ref.h",
|
40
|
+
"ext/argon2id/libargon2/core.c",
|
41
|
+
"ext/argon2id/libargon2/core.h",
|
42
|
+
"ext/argon2id/libargon2/encoding.c",
|
43
|
+
"ext/argon2id/libargon2/encoding.h",
|
44
|
+
"ext/argon2id/libargon2/ref.c",
|
45
|
+
"ext/argon2id/libargon2/thread.c",
|
46
|
+
"ext/argon2id/libargon2/thread.h",
|
47
|
+
"lib/argon2id.rb",
|
48
|
+
"lib/argon2id/password.rb",
|
49
|
+
"lib/argon2id/version.rb",
|
50
|
+
"test/test_hash_encoded.rb",
|
51
|
+
"test/test_password.rb",
|
52
|
+
"test/test_verify.rb"
|
53
|
+
]
|
54
|
+
s.rdoc_options = ["--main", "README.md"]
|
55
|
+
|
56
|
+
s.add_development_dependency("rake-compiler", "~> 1.2")
|
57
|
+
s.add_development_dependency("rake-compiler-dock", "~> 1.5")
|
58
|
+
s.add_development_dependency("minitest", "~> 5.25")
|
59
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module Argon2id
|
6
|
+
# The Password class encapsulates an encoded Argon2id password hash.
|
7
|
+
#
|
8
|
+
# To hash a plain text password, use Argon2id::Password.create:
|
9
|
+
#
|
10
|
+
# password = Argon2id::Password.create("password")
|
11
|
+
# password.to_s
|
12
|
+
# #=> "$argon2id$v=19$m=19456,t=2,p=1$+Lrjry9Ifq0poLr15OGU1Q$utkDvejJB0ugwm4s9+a+vF6+1a/W+Y3CYa5Wte/85ig"
|
13
|
+
#
|
14
|
+
# To wrap an encoded Argon2id password hash, use Argon2id::Password.new:
|
15
|
+
#
|
16
|
+
# password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
17
|
+
#
|
18
|
+
# You can then verify it matches a given plain text:
|
19
|
+
#
|
20
|
+
# password == "password" #=> true
|
21
|
+
# password == "not password" #=> false
|
22
|
+
#
|
23
|
+
# password.is_password?("password") #=> true
|
24
|
+
# password.is_password?("not password") #=> false
|
25
|
+
#
|
26
|
+
# You can read various parameters out of a password hash:
|
27
|
+
#
|
28
|
+
# password.type #=> "argon2id"
|
29
|
+
# password.version #=> 19
|
30
|
+
# password.m_cost #=> 19456
|
31
|
+
# password.t_cost #=> 2
|
32
|
+
# password.parallelism #=> 1
|
33
|
+
# password.salt #=> "somesalt"
|
34
|
+
class Password
|
35
|
+
# A regular expression to match valid hashes.
|
36
|
+
PATTERN = %r{
|
37
|
+
\A
|
38
|
+
\$
|
39
|
+
(argon2(?:id|i|d))
|
40
|
+
(?:\$v=(\d+))?
|
41
|
+
\$m=(\d+)
|
42
|
+
,t=(\d+)
|
43
|
+
,p=(\d+)
|
44
|
+
\$
|
45
|
+
([a-zA-Z0-9+/]+)
|
46
|
+
\$
|
47
|
+
([a-zA-Z0-9+/]+)
|
48
|
+
\z
|
49
|
+
}x.freeze
|
50
|
+
|
51
|
+
# The encoded password hash.
|
52
|
+
attr_reader :encoded
|
53
|
+
|
54
|
+
# The type of the hashing function.
|
55
|
+
attr_reader :type
|
56
|
+
|
57
|
+
# The version number of the hashing function.
|
58
|
+
attr_reader :version
|
59
|
+
|
60
|
+
# The "time cost" of the hashing function.
|
61
|
+
attr_reader :t_cost
|
62
|
+
|
63
|
+
# The "memory cost" of the hashing function.
|
64
|
+
attr_reader :m_cost
|
65
|
+
|
66
|
+
# The number of threads and compute lanes of the hashing function.
|
67
|
+
attr_reader :parallelism
|
68
|
+
|
69
|
+
# The salt.
|
70
|
+
attr_reader :salt
|
71
|
+
|
72
|
+
# The hash output.
|
73
|
+
attr_reader :output
|
74
|
+
|
75
|
+
# Create a new Password object that hashes a given plain text password +pwd+.
|
76
|
+
#
|
77
|
+
# - +:t_cost+: integer (default 2) the "time cost" given as a number of iterations
|
78
|
+
# - +:m_cost+: integer (default 19456) the "memory cost" given in kibibytes
|
79
|
+
# - +:parallelism+: integer (default 1) the number of threads and compute lanes to use
|
80
|
+
# - +:salt_len+: integer (default 16) the salt size in bytes
|
81
|
+
# - +:output_len+: integer (default 32) the desired length of the hash in bytes
|
82
|
+
#
|
83
|
+
# For example, with the default configuration:
|
84
|
+
#
|
85
|
+
# password = Argon2id::Password.create("password")
|
86
|
+
# password.to_s
|
87
|
+
# #=> "$argon2id$v=19$m=19456,t=2,p=1$FI8yp1gXbthJCskBlpKPoQ$nOfCCpS2r+I8GRN71cZND4cskn7YKBNzuHUEO3YpY2s"
|
88
|
+
#
|
89
|
+
# When overriding the configuration:
|
90
|
+
#
|
91
|
+
# password = Argon2id::Password.create("password", t_cost: 3, m_cost: 12288)
|
92
|
+
# password.to_s
|
93
|
+
# #=> "$argon2id$v=19$m=12288,t=3,p=1$JigW7fFn+N3NImt+aWpuzw$eM5F1cKeIBALNTU6LuWra75Zi2nymGvQLWzJzVFv0Nc"
|
94
|
+
def self.create(pwd, t_cost: Argon2id.t_cost, m_cost: Argon2id.m_cost, parallelism: Argon2id.parallelism, salt_len: Argon2id.salt_len, output_len: Argon2id.output_len)
|
95
|
+
new(
|
96
|
+
Argon2id.hash_encoded(
|
97
|
+
Integer(t_cost),
|
98
|
+
Integer(m_cost),
|
99
|
+
Integer(parallelism),
|
100
|
+
String(pwd),
|
101
|
+
OpenSSL::Random.random_bytes(Integer(salt_len)),
|
102
|
+
Integer(output_len)
|
103
|
+
)
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Create a new Password with the given encoded password hash.
|
108
|
+
#
|
109
|
+
# password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$FI8yp1gXbthJCskBlpKPoQ$nOfCCpS2r+I8GRN71cZND4cskn7YKBNzuHUEO3YpY2s")
|
110
|
+
#
|
111
|
+
# Raises an ArgumentError if given an invalid hash.
|
112
|
+
def initialize(encoded)
|
113
|
+
raise ArgumentError, "invalid hash" unless PATTERN =~ String(encoded)
|
114
|
+
|
115
|
+
@encoded = $&
|
116
|
+
@type = $1
|
117
|
+
@version = Integer($2 || 0x10)
|
118
|
+
@m_cost = Integer($3)
|
119
|
+
@t_cost = Integer($4)
|
120
|
+
@parallelism = Integer($5)
|
121
|
+
@salt = $6.unpack1("m")
|
122
|
+
@output = $7.unpack1("m")
|
123
|
+
end
|
124
|
+
|
125
|
+
# Return the encoded password hash.
|
126
|
+
alias_method :to_s, :encoded
|
127
|
+
|
128
|
+
# Compare the password with the given plain text, returning true if it
|
129
|
+
# verifies successfully.
|
130
|
+
#
|
131
|
+
# password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$FI8yp1gXbthJCskBlpKPoQ$nOfCCpS2r+I8GRN71cZND4cskn7YKBNzuHUEO3YpY2s")
|
132
|
+
# password == "password" #=> true
|
133
|
+
# password == "notpassword" #=> false
|
134
|
+
def ==(other)
|
135
|
+
Argon2id.verify(encoded, String(other))
|
136
|
+
end
|
137
|
+
|
138
|
+
alias_method :is_password?, :==
|
139
|
+
end
|
140
|
+
end
|
data/lib/argon2id.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if RUBY_PLATFORM == "java"
|
4
|
+
require "openssl"
|
5
|
+
else
|
6
|
+
begin
|
7
|
+
::RUBY_VERSION =~ /(\d+\.\d+)/
|
8
|
+
require_relative "#{Regexp.last_match(1)}/argon2id.so"
|
9
|
+
rescue LoadError
|
10
|
+
require "argon2id.so"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require "argon2id/version"
|
15
|
+
require "argon2id/password"
|
16
|
+
|
17
|
+
module Argon2id
|
18
|
+
# The default "time cost" of 2 iterations recommended by OWASP.
|
19
|
+
DEFAULT_T_COST = 2
|
20
|
+
|
21
|
+
# The default "memory cost" of 19 mebibytes recommended by OWASP.
|
22
|
+
DEFAULT_M_COST = 19_456
|
23
|
+
|
24
|
+
# The default 1 thread and compute lane recommended by OWASP.
|
25
|
+
DEFAULT_PARALLELISM = 1
|
26
|
+
|
27
|
+
# The default salt length of 16 bytes.
|
28
|
+
DEFAULT_SALT_LEN = 16
|
29
|
+
|
30
|
+
# The default desired hash length of 32 bytes.
|
31
|
+
DEFAULT_OUTPUT_LEN = 32
|
32
|
+
|
33
|
+
@t_cost = DEFAULT_T_COST
|
34
|
+
@m_cost = DEFAULT_M_COST
|
35
|
+
@parallelism = DEFAULT_PARALLELISM
|
36
|
+
@salt_len = DEFAULT_SALT_LEN
|
37
|
+
@output_len = DEFAULT_OUTPUT_LEN
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# The default number of iterations used by Argon2id::Password.create
|
41
|
+
attr_accessor :t_cost
|
42
|
+
|
43
|
+
# The default memory cost in kibibytes used by Argon2id::Password.create
|
44
|
+
attr_accessor :m_cost
|
45
|
+
|
46
|
+
# The default number of threads and compute lanes used by Argon2id::Password.create
|
47
|
+
attr_accessor :parallelism
|
48
|
+
|
49
|
+
# The default salt size in bytes used by Argon2id::Password.create
|
50
|
+
attr_accessor :salt_len
|
51
|
+
|
52
|
+
# The default desired length of the hash in bytes used by Argon2id::Password.create
|
53
|
+
attr_accessor :output_len
|
54
|
+
end
|
55
|
+
|
56
|
+
if RUBY_PLATFORM == "java"
|
57
|
+
Error = Class.new(StandardError)
|
58
|
+
|
59
|
+
def self.hash_encoded(t_cost, m_cost, parallelism, pwd, salt, hashlen)
|
60
|
+
raise Error, "Salt is too short" unless String(salt).bytesize.positive?
|
61
|
+
|
62
|
+
hash = Java::byte[Integer(hashlen)].new
|
63
|
+
params = Java::OrgBouncycastleCryptoParams::Argon2Parameters::Builder
|
64
|
+
.new(Java::OrgBouncycastleCryptoParams::Argon2Parameters::ARGON2_id)
|
65
|
+
.with_salt(String(salt).to_java_bytes)
|
66
|
+
.with_parallelism(Integer(parallelism))
|
67
|
+
.with_memory_as_kb(Integer(m_cost))
|
68
|
+
.with_iterations(Integer(t_cost))
|
69
|
+
.build
|
70
|
+
generator = Java::OrgBouncycastleCryptoGenerators::Argon2BytesGenerator.new
|
71
|
+
encoder = Java::JavaUtil::Base64.get_encoder.without_padding
|
72
|
+
|
73
|
+
generator.init(params)
|
74
|
+
generator.generate_bytes(String(pwd).to_java_bytes, hash)
|
75
|
+
|
76
|
+
encoded_salt = encoder.encode_to_string(params.get_salt)
|
77
|
+
encoded_output = encoder.encode_to_string(hash)
|
78
|
+
|
79
|
+
"$argon2id$v=#{params.get_version}$m=#{params.get_memory}," \
|
80
|
+
"t=#{params.get_iterations},p=#{params.get_lanes}" \
|
81
|
+
"$#{encoded_salt}$#{encoded_output}"
|
82
|
+
rescue => e
|
83
|
+
raise Error, e.message
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.verify(encoded, pwd)
|
87
|
+
password = Password.new(encoded)
|
88
|
+
other_password = Password.new(
|
89
|
+
hash_encoded(
|
90
|
+
password.t_cost,
|
91
|
+
password.m_cost,
|
92
|
+
password.parallelism,
|
93
|
+
String(pwd),
|
94
|
+
password.salt,
|
95
|
+
password.output.bytesize
|
96
|
+
)
|
97
|
+
)
|
98
|
+
|
99
|
+
Java::OrgBouncycastleUtil::Arrays.constant_time_are_equal(
|
100
|
+
password.output.to_java_bytes,
|
101
|
+
other_password.output.to_java_bytes
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "argon2id"
|
5
|
+
|
6
|
+
class TestHashEncoded < Minitest::Test
|
7
|
+
def test_valid_password_and_salt_encodes_successfully
|
8
|
+
encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
|
9
|
+
|
10
|
+
assert_equal "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4", encoded
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_valid_password_does_not_include_trailing_null_byte
|
14
|
+
encoded = Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 32)
|
15
|
+
|
16
|
+
refute encoded.end_with?("\x00")
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_raises_with_too_short_output
|
20
|
+
assert_raises(Argon2id::Error) do
|
21
|
+
Argon2id.hash_encoded(2, 256, 1, "password", "somesalt", 1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_raises_with_too_few_lanes
|
26
|
+
assert_raises(Argon2id::Error) do
|
27
|
+
Argon2id.hash_encoded(2, 256, 0, "password", "somesalt", 32)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_raises_with_too_small_memory_cost
|
32
|
+
assert_raises(Argon2id::Error) do
|
33
|
+
Argon2id.hash_encoded(2, 0, 1, "password", "somesalt", 32)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_raises_with_too_small_time_cost
|
38
|
+
assert_raises(Argon2id::Error) do
|
39
|
+
Argon2id.hash_encoded(0, 256, 1, "password", "somesalt", 32)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_raises_with_too_short_salt
|
44
|
+
assert_raises(Argon2id::Error) do
|
45
|
+
Argon2id.hash_encoded(2, 256, 1, "password", "", 32)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "argon2id"
|
5
|
+
|
6
|
+
class TestPassword < Minitest::Test
|
7
|
+
def test_create_returns_encoded_password_with_defaults
|
8
|
+
password = Argon2id::Password.create("opensesame")
|
9
|
+
|
10
|
+
assert password.to_s.start_with?("$argon2id$")
|
11
|
+
assert password.to_s.include?("t=2")
|
12
|
+
assert password.to_s.include?("m=19456")
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_options_can_override_parameters
|
16
|
+
password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
|
17
|
+
|
18
|
+
assert password.to_s.include?("t=2")
|
19
|
+
assert password.to_s.include?("m=256")
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_create_uses_argon2id_configuration
|
23
|
+
Argon2id.t_cost = 2
|
24
|
+
Argon2id.m_cost = 256
|
25
|
+
|
26
|
+
password = Argon2id::Password.create("opensesame")
|
27
|
+
|
28
|
+
assert password.to_s.include?("t=2")
|
29
|
+
assert password.to_s.include?("m=256")
|
30
|
+
ensure
|
31
|
+
Argon2id.t_cost = Argon2id::DEFAULT_T_COST
|
32
|
+
Argon2id.m_cost = Argon2id::DEFAULT_M_COST
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_create_coerces_pwd_to_string
|
36
|
+
password = Argon2id::Password.create(123, t_cost: 2, m_cost: 256)
|
37
|
+
|
38
|
+
assert password.to_s.start_with?("$argon2id$")
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_create_coerces_costs_to_integer
|
42
|
+
password = Argon2id::Password.create("opensesame", t_cost: "2", m_cost: "256", parallelism: "1", salt_len: "8", output_len: "32")
|
43
|
+
|
44
|
+
assert password.to_s.start_with?("$argon2id$")
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_create_raises_if_given_non_integer_costs
|
48
|
+
assert_raises(ArgumentError) do
|
49
|
+
Argon2id::Password.create("opensesame", t_cost: "not an integer")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_equals_correct_password
|
54
|
+
password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
|
55
|
+
|
56
|
+
assert password == "opensesame"
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_does_not_equal_invalid_password
|
60
|
+
password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
|
61
|
+
|
62
|
+
refute password == "notopensesame"
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_is_password_returns_true_with_correct_password
|
66
|
+
password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
|
67
|
+
|
68
|
+
assert password.is_password?("opensesame")
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_is_password_returns_false_with_incorrect_password
|
72
|
+
password = Argon2id::Password.create("opensesame", t_cost: 2, m_cost: 256)
|
73
|
+
|
74
|
+
refute password.is_password?("notopensesame")
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_salt_returns_the_original_salt
|
78
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
79
|
+
|
80
|
+
assert_equal "somesalt", password.salt
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_salt_returns_raw_bytes
|
84
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$KmIxrXv4lrnSJPO0LN7Gdw$lB3724qLPL9MNi10lkvIb4VxIk3q841CLvq0WTCZ0VQ")
|
85
|
+
|
86
|
+
assert_equal "*b1\xAD{\xF8\x96\xB9\xD2$\xF3\xB4,\xDE\xC6w".b, password.salt
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_raises_for_invalid_hashes
|
90
|
+
assert_raises(ArgumentError) do
|
91
|
+
Argon2id::Password.new("not a valid hash")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_raises_for_partial_hashes
|
96
|
+
assert_raises(ArgumentError) do
|
97
|
+
Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$KmIxrXv4lrnSJPO0LN7Gdw")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_salt_supports_versionless_hashes
|
102
|
+
password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
103
|
+
|
104
|
+
assert_equal "somesalt", password.salt
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_coerces_given_hash_to_string
|
108
|
+
password = Argon2id::Password.create("password")
|
109
|
+
|
110
|
+
assert Argon2id::Password.new(password) == "password"
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_extracting_type_from_hash
|
114
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
115
|
+
|
116
|
+
assert_equal "argon2id", password.type
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_extracting_type_from_argoni_hash
|
120
|
+
password = Argon2id::Password.new("$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
121
|
+
|
122
|
+
assert_equal "argon2i", password.type
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_extracting_version_from_hash
|
126
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
127
|
+
|
128
|
+
assert_equal 19, password.version
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_extracting_version_from_versionless_hash
|
132
|
+
password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
133
|
+
|
134
|
+
assert_equal 16, password.version
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_extracting_time_cost_from_hash
|
138
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
139
|
+
|
140
|
+
assert_equal 2, password.t_cost
|
141
|
+
end
|
142
|
+
|
143
|
+
def test_extracting_time_cost_from_versionless_hash
|
144
|
+
password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
145
|
+
|
146
|
+
assert_equal 2, password.t_cost
|
147
|
+
end
|
148
|
+
|
149
|
+
def test_extracting_memory_cost_from_hash
|
150
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
151
|
+
|
152
|
+
assert_equal 256, password.m_cost
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_extracting_memory_cost_from_versionless_hash
|
156
|
+
password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
157
|
+
|
158
|
+
assert_equal 256, password.m_cost
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_extracting_parallelism_from_hash
|
162
|
+
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
163
|
+
|
164
|
+
assert_equal 1, password.parallelism
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_extracting_parallelism_from_versionless_hash
|
168
|
+
password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
|
169
|
+
|
170
|
+
assert_equal 1, password.parallelism
|
171
|
+
end
|
172
|
+
end
|
data/test/test_verify.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "minitest/autorun"
|
4
|
+
require "argon2id"
|
5
|
+
|
6
|
+
class TestVerify < Minitest::Test
|
7
|
+
def test_returns_true_with_correct_password
|
8
|
+
assert Argon2id.verify(
|
9
|
+
"$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4",
|
10
|
+
"password"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_returns_false_with_incorrect_password
|
15
|
+
refute Argon2id.verify(
|
16
|
+
"$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4",
|
17
|
+
"not password"
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_raises_if_given_invalid_encoded
|
22
|
+
assert_raises(ArgumentError) do
|
23
|
+
Argon2id.verify("", "opensesame")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_raises_if_given_encoded_with_null_byte
|
28
|
+
assert_raises(ArgumentError) do
|
29
|
+
Argon2id.verify(
|
30
|
+
"$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4\x00foo",
|
31
|
+
"password"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: argon2id
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: java
|
6
|
+
authors:
|
7
|
+
- Paul Mucur
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '1.2'
|
19
|
+
name: rake-compiler
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.5'
|
33
|
+
name: rake-compiler-dock
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '5.25'
|
47
|
+
name: minitest
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.25'
|
55
|
+
description: Ruby bindings to the reference C implementation of Argon2, the password-hashing
|
56
|
+
function that won the 2015 Password Hashing Competition.
|
57
|
+
email:
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- CHANGELOG.md
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- argon2id.gemspec
|
68
|
+
- lib/argon2id.rb
|
69
|
+
- lib/argon2id/password.rb
|
70
|
+
- lib/argon2id/version.rb
|
71
|
+
- test/test_hash_encoded.rb
|
72
|
+
- test/test_password.rb
|
73
|
+
- test/test_verify.rb
|
74
|
+
homepage: https://github.com/mudge/argon2id
|
75
|
+
licenses:
|
76
|
+
- BSD-3-Clause
|
77
|
+
metadata:
|
78
|
+
bug_tracker_uri: https://github.com/mudge/argon2id/issues
|
79
|
+
changelog_uri: https://github.com/mudge/argon2id/blob/main/CHANGELOG.md
|
80
|
+
funding_uri: https://github.com/sponsors/mudge
|
81
|
+
homepage_uri: https://github.com/mudge/argon2id
|
82
|
+
source_code_uri: https://github.com/mudge/argon2id
|
83
|
+
rubygems_mfa_required: 'true'
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options:
|
86
|
+
- "--main"
|
87
|
+
- README.md
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 3.1.0
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.3.25
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Ruby bindings to Argon2
|
105
|
+
test_files: []
|