argon2id 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f52b40e15650850965678538bc94eb2aa34da651e62e536da3bbca058e3d2c3
4
- data.tar.gz: 852859c505d84f7ec6bfd0045d1c1bafe52c24e1c59ef52ccca5e997cfe48aa3
3
+ metadata.gz: fe0651aa144c9dd31ce3eec05fff4a92b19f9c3123ccca67c2f19ae0e0047e85
4
+ data.tar.gz: fdc003fc28fd0258bf838c599f5a7bf784296aefbd3658efc87e6ca23a9ea4a4
5
5
  SHA512:
6
- metadata.gz: e89d4b69c0b98da64ff04c4dc15496ffe8cd11418fd4e466dcf382eca137b65b2301ad4c56f62a4387938067b4346269f7c6408d0ac498f6ef7359fe2224e3de
7
- data.tar.gz: 9a31d89ff9bbd79294342c269c53ffd22574135ccde8c96c4efe99d772da1265a479964d031c0d6cd7e3b6923a383d0497dda78da6562faadc7f1af85f6ce1da
6
+ metadata.gz: b6eae30f919739df31878696ec9ce8d2bfd26bbc4c8dfe8a442021d139b51c80c6bc362c9810cad9dc706b4f32d36333ca85bbcdd5620ae169981d6766fd3d8c
7
+ data.tar.gz: d9c614f7e76e809677402b3f451b932ccfe413775d4bbf27131552f4c6249f819cef39db7285a85f5f91aaa37ad4b2f3cde6f24545f9d7eb412598df3cdcdbb8
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2024-11-08
9
+
10
+ ### Fixed
11
+
12
+ - Fixed verifying Argon2id encoded hashes without a version number on JRuby
13
+
14
+ ### Added
15
+
16
+ - Added a new `Argon2id::Password.valid_hash?` API for testing if a given
17
+ encoded hash is a valid Argon2id hash or not (e.g. if you want to check
18
+ which hashing function was used to store a user's password)
19
+
8
20
  ## [0.6.0] - 2024-11-05
9
21
 
10
22
  ### Changed
@@ -99,6 +111,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
111
  reference C implementation of Argon2, the password-hashing function that won
100
112
  the Password Hashing Competition.
101
113
 
114
+ [0.7.0]: https://github.com/mudge/argon2id/releases/tag/v0.7.0
102
115
  [0.6.0]: https://github.com/mudge/argon2id/releases/tag/v0.6.0
103
116
  [0.5.0]: https://github.com/mudge/argon2id/releases/tag/v0.5.0
104
117
  [0.4.1]: https://github.com/mudge/argon2id/releases/tag/v0.4.1
data/README.md CHANGED
@@ -5,7 +5,7 @@ Ruby bindings to [Argon2][], the password-hashing function that won the 2015
5
5
 
6
6
  [![Build Status](https://github.com/mudge/argon2id/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/mudge/argon2id/actions)
7
7
 
8
- **Current version:** 0.6.0
8
+ **Current version:** 0.7.0
9
9
  **Bundled Argon2 version:** libargon2.1 (20190702)
10
10
 
11
11
  ```ruby
@@ -26,6 +26,7 @@ password.salt #=> "e-\xA7\x04U\x81\xA6{v\xF0x\xED\xCC\xD3\x96\xE3"
26
26
  * [Usage](#usage)
27
27
  * [Hashing passwords](#hashing-passwords)
28
28
  * [Verifying passwords](#verifying-passwords)
29
+ * [Validating encoded hashes](#validating-encoded-hashes)
29
30
  * [Errors](#errors)
30
31
  * [Requirements](#requirements)
31
32
  * [Native gems](#native-gems)
@@ -55,6 +56,8 @@ password.salt #=> "e-\xA7\x04U\x81\xA6{v\xF0x\xED\xCC\xD3\x96\xE3"
55
56
 
56
57
  — [OWASP Password Storage Cheat Sheet][]
57
58
 
59
+ See also [argon2-cffi's "Why 'just use bcrypt' Is Not the Best Answer (Anymore)"](https://argon2-cffi.readthedocs.io/en/23.1.0/argon2.html#why-just-use-bcrypt-is-not-the-best-answer-anymore).
60
+
58
61
  ## Usage
59
62
 
60
63
  Install argon2id as a dependency:
@@ -147,6 +150,18 @@ password.is_password?("opensesame") #=> true
147
150
  password.is_password?("notopensesame") #=> false
148
151
  ```
149
152
 
153
+ > [!CAUTION]
154
+ > `Argon2id::Password#==` only works if the plain text password is on the right, e.g. the following behaviour may be surprising:
155
+ >
156
+ > ```ruby
157
+ > password = Argon2id::Password.create("password")
158
+ > password == "password" #=> true
159
+ > "password" == password #=> false
160
+ > password == password #=> false
161
+ > ```
162
+ >
163
+ > If you want to avoid this ambiguity, prefer the `Argon2id::Password#is_password?` alias instead.
164
+
150
165
  The various parts of the encoded hash can be retrieved:
151
166
 
152
167
  ```ruby
@@ -160,6 +175,18 @@ password.output
160
175
  #=> "\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"
161
176
  ```
162
177
 
178
+ ### Validating encoded hashes
179
+
180
+ If you need to check ahead of time whether an encoded password hash is a valid Argon2id hash (e.g. if you're migrating between hashing functions and need to test what kind of password has been stored for a user), you can use `Argon2id::Password.valid_hash?` like so:
181
+
182
+ ```ruby
183
+ Argon2id::Password.valid_hash?("$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc")
184
+ #=> true
185
+
186
+ Argon2id::Password.valid_hash?("$2a$12$stsRn7Mi9r02.keRyF4OK.Aq4UWOU185lWggfUQfcupAi.b7AI/nS")
187
+ #=> false
188
+ ```
189
+
163
190
  ### Errors
164
191
 
165
192
  Any errors returned from Argon2 will be raised as `Argon2id::Error`, e.g.
@@ -201,11 +228,11 @@ notes](https://github.com/mudge/argon2id/releases) for each version and can be
201
228
  checked with `sha256sum`, e.g.
202
229
 
203
230
  ```console
204
- $ gem fetch argon2id -v 0.5.0
205
- Fetching argon2id-0.5.0-arm64-darwin.gem
206
- Downloaded argon2id-0.5.0-arm64-darwin
207
- $ sha256sum argon2id-0.5.0-arm64-darwin.gem
208
- 871e9d9bcad09e75620ce9ddd32cd99a4ebc3a6db1516e487680787faa7368a3 argon2id-0.5.0-arm64-darwin.gem
231
+ $ gem fetch argon2id -v 0.6.0
232
+ Fetching argon2id-0.6.0-arm64-darwin.gem
233
+ Downloaded argon2id-0.6.0-arm64-darwin
234
+ $ sha256sum argon2id-0.6.0-arm64-darwin.gem
235
+ 18f1f04be4b5e7badb4d491762e57874febeeb46c64ce1b0a5e3a75b39b5baeb argon2id-0.6.0-arm64-darwin.gem
209
236
  ```
210
237
 
211
238
  [GPG](https://www.gnupg.org/) signatures are attached to each release (the
@@ -215,8 +242,8 @@ from a public keyserver, e.g. `gpg --keyserver keyserver.ubuntu.com --recv-key
215
242
  0x39AC3530070E0F75`):
216
243
 
217
244
  ```console
218
- $ gpg --verify argon2id-0.5.0-arm64-darwin.gem.sig argon2id-0.5.0-arm64-darwin.gem
219
- gpg: Signature made Sat 2 Nov 21:09:51 2024 GMT
245
+ $ gpg --verify argon2id-0.6.0-arm64-darwin.gem.sig argon2id-0.6.0-arm64-darwin.gem
246
+ gpg: Signature made Tue 5 Nov 11:30:47 2024 GMT
220
247
  gpg: using RSA key 702609D9C790F45B577D7BEC39AC3530070E0F75
221
248
  gpg: Good signature from "Paul Mucur <mudge@mudge.name>" [unknown]
222
249
  gpg: aka "Paul Mucur <paul@ghostcassette.com>" [unknown]
data/argon2id.gemspec CHANGED
@@ -48,7 +48,8 @@ Gem::Specification.new do |s|
48
48
  "lib/argon2id/extension.rb",
49
49
  "lib/argon2id/password.rb",
50
50
  "lib/argon2id/version.rb",
51
- "test/test_password.rb"
51
+ "test/argon2id/test_password.rb",
52
+ "test/test_argon2id.rb"
52
53
  ]
53
54
  s.rdoc_options = ["--main", "README.md"]
54
55
 
@@ -47,6 +47,7 @@ if RUBY_PLATFORM == "java"
47
47
  .with_parallelism(parallelism)
48
48
  .with_memory_as_kb(m_cost)
49
49
  .with_iterations(t_cost)
50
+ .with_version(version)
50
51
  .build
51
52
  generator = Java::OrgBouncycastleCryptoGenerators::Argon2BytesGenerator.new
52
53
  generator.init(params)
@@ -100,6 +100,13 @@ module Argon2id
100
100
  )
101
101
  end
102
102
 
103
+ # Check an encoded hash is a valid Argon2id hash.
104
+ #
105
+ # Returns true if so and false if not.
106
+ def self.valid_hash?(encoded)
107
+ PATTERN.match?(String(encoded))
108
+ end
109
+
103
110
  # Create a new Password with the given encoded password hash.
104
111
  #
105
112
  # password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$FI8yp1gXbthJCskBlpKPoQ$nOfCCpS2r+I8GRN71cZND4cskn7YKBNzuHUEO3YpY2s")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Argon2id
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -0,0 +1,548 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "argon2id"
5
+
6
+ class StringLike
7
+ def initialize(str)
8
+ @str = str
9
+ end
10
+
11
+ def to_s
12
+ @str
13
+ end
14
+ end
15
+
16
+ class TestPassword < Minitest::Test
17
+ def test_valid_hash_with_argon2id_hash_returns_true
18
+ assert Argon2id::Password.valid_hash?(
19
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \
20
+ "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"
21
+ )
22
+ end
23
+
24
+ def test_valid_hash_with_versionless_argon2id_hash_returns_true
25
+ assert Argon2id::Password.valid_hash?(
26
+ "$argon2id$m=65536,t=2,p=1$c29tZXNhbHQ" \
27
+ "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"
28
+ )
29
+ end
30
+
31
+ def test_valid_hash_with_argon2i_hash_returns_false
32
+ refute Argon2id::Password.valid_hash?(
33
+ "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" \
34
+ "$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
35
+ )
36
+ end
37
+
38
+ def test_valid_hash_with_partial_argon2id_hash_returns_false
39
+ refute Argon2id::Password.valid_hash?(
40
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ"
41
+ )
42
+ end
43
+
44
+ def test_valid_hash_with_argon2id_hash_with_null_bytes_returns_false
45
+ refute Argon2id::Password.valid_hash?(
46
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \
47
+ "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc\x00foo"
48
+ )
49
+ end
50
+
51
+ def test_valid_hash_with_bcrypt_hash_returns_false
52
+ refute Argon2id::Password.valid_hash?(
53
+ "$2a$12$stsRn7Mi9r02.keRyF4OK.Aq4UWOU185lWggfUQfcupAi.b7AI/nS"
54
+ )
55
+ end
56
+
57
+ def test_valid_hash_with_nil_returns_false
58
+ refute Argon2id::Password.valid_hash?(nil)
59
+ end
60
+
61
+ def test_valid_hash_with_coercible_argon2id_hash_returns_true
62
+ assert Argon2id::Password.valid_hash?(
63
+ StringLike.new(
64
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \
65
+ "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"
66
+ )
67
+ )
68
+ end
69
+
70
+ def test_valid_hash_with_coercible_bcrypt_hash_returns_false
71
+ refute Argon2id::Password.valid_hash?(
72
+ StringLike.new(
73
+ "$2a$12$stsRn7Mi9r02.keRyF4OK.Aq4UWOU185lWggfUQfcupAi.b7AI/nS"
74
+ )
75
+ )
76
+ end
77
+
78
+ def test_new_m_65536_t_2_p_1_equals_password
79
+ password = Argon2id::Password.new(
80
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \
81
+ "$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc"
82
+ )
83
+
84
+ assert password == "password"
85
+ end
86
+
87
+ def test_new_m_262144_t_2_p_1_equals_password
88
+ password = Argon2id::Password.new(
89
+ "$argon2id$v=19$m=262144,t=2,p=1$c29tZXNhbHQ" \
90
+ "$eP4eyR+zqlZX1y5xCFTkw9m5GYx0L5YWwvCFvtlbLow"
91
+ )
92
+
93
+ assert password == "password"
94
+ end
95
+
96
+ def test_new_m_256_t_2_p_1_equals_password
97
+ password = Argon2id::Password.new(
98
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
99
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
100
+ )
101
+
102
+ assert password == "password"
103
+ end
104
+
105
+ def test_new_m_256_t_2_p_2_equals_password
106
+ password = Argon2id::Password.new(
107
+ "$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ" \
108
+ "$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc"
109
+ )
110
+
111
+ assert password == "password"
112
+ end
113
+
114
+ def test_new_m_65536_t_1_p_1_equals_password
115
+ password = Argon2id::Password.new(
116
+ "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" \
117
+ "$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg"
118
+ )
119
+
120
+ assert password == "password"
121
+ end
122
+
123
+ def test_new_m_65536_t_4_p_1_equals_password
124
+ password = Argon2id::Password.new(
125
+ "$argon2id$v=19$m=65536,t=4,p=1$c29tZXNhbHQ" \
126
+ "$kCXUjmjvc5XMqQedpMTsOv+zyJEf5PhtGiUghW9jFyw"
127
+ )
128
+
129
+ assert password == "password"
130
+ end
131
+
132
+ def test_new_m_65536_t_2_p_1_equals_differentpassword
133
+ password = Argon2id::Password.new(
134
+ "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ" \
135
+ "$C4TWUs9rDEvq7w3+J4umqA32aWKB1+DSiRuBfYxFj94"
136
+ )
137
+
138
+ assert password == "differentpassword"
139
+ end
140
+
141
+ def test_new_m_65536_t_2_p_1_with_diffsalt_equals_password
142
+ password = Argon2id::Password.new(
143
+ "$argon2id$v=19$m=65536,t=2,p=1$ZGlmZnNhbHQ" \
144
+ "$vfMrBczELrFdWP0ZsfhWsRPaHppYdP3MVEMIVlqoFBw"
145
+ )
146
+
147
+ assert password == "password"
148
+ end
149
+
150
+ def test_new_with_versionless_hash_equals_password
151
+ password = Argon2id::Password.new(
152
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
153
+ "$2gcOV25Q8vOKPIl8vdxsf7QCjocJcf+erntOGHkpXm4"
154
+ )
155
+
156
+ assert password == "password"
157
+ end
158
+
159
+ def test_new_with_non_argon2id_hash_raises_argument_error
160
+ assert_raises(ArgumentError) do
161
+ Argon2id::Password.new(
162
+ "$argon2i$m=65536,t=2,p=1$c29tZXNhbHQ" \
163
+ "$9sTbSlTio3Biev89thdrlKKiCaYsjjYVJxGAL3swxpQ"
164
+ )
165
+ end
166
+ end
167
+
168
+ def test_new_with_invalid_hash_raises_argument_error
169
+ assert_raises(ArgumentError) do
170
+ Argon2id::Password.new("not a valid hash")
171
+ end
172
+ end
173
+
174
+ def test_new_with_nil_raises_argument_error
175
+ assert_raises(ArgumentError) do
176
+ Argon2id::Password.new(nil)
177
+ end
178
+ end
179
+
180
+ def test_new_with_coercible_equals_password
181
+ password = Argon2id::Password.new(
182
+ StringLike.new(
183
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
184
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
185
+ )
186
+ )
187
+
188
+ assert password == "password"
189
+ end
190
+
191
+ def test_encoded_returns_the_full_encoded_hash
192
+ password = Argon2id::Password.new(
193
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
194
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
195
+ )
196
+
197
+ assert_equal(
198
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4",
199
+ password.encoded
200
+ )
201
+ end
202
+
203
+ def test_version_returns_the_version
204
+ password = Argon2id::Password.new(
205
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
206
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
207
+ )
208
+
209
+ assert_equal(19, password.version)
210
+ end
211
+
212
+ def test_version_with_no_version_returns_the_default_version
213
+ password = Argon2id::Password.new(
214
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
215
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
216
+ )
217
+
218
+ assert_equal(16, password.version)
219
+ end
220
+
221
+ def test_m_cost_returns_m_cost
222
+ password = Argon2id::Password.new(
223
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
224
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
225
+ )
226
+
227
+ assert_equal(256, password.m_cost)
228
+ end
229
+
230
+ def test_t_cost_returns_t_cost
231
+ password = Argon2id::Password.new(
232
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
233
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
234
+ )
235
+
236
+ assert_equal(2, password.t_cost)
237
+ end
238
+
239
+ def test_parallelism_returns_parallelism
240
+ password = Argon2id::Password.new(
241
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
242
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
243
+ )
244
+
245
+ assert_equal(1, password.parallelism)
246
+ end
247
+
248
+ def test_salt_returns_decoded_salt
249
+ password = Argon2id::Password.new(
250
+ "$argon2id$m=256,t=2,p=1$c29tZXNhbHQ" \
251
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
252
+ )
253
+
254
+ assert_equal("somesalt", password.salt)
255
+ end
256
+
257
+ def test_salt_returns_decoded_binary_salt
258
+ password = Argon2id::Password.new(
259
+ "$argon2id$v=19$m=256,t=2,p=1$FImSDfu1p8vf1mZBL2PCkg" \
260
+ "$vG4bIkTJGMx6OvkLuKTeq37DTyAf8gF2Ouf3zSLlYVc"
261
+ )
262
+
263
+ assert_equal(
264
+ "\x14\x89\x92\r\xFB\xB5\xA7\xCB\xDF\xD6fA/c\xC2\x92".b,
265
+ password.salt
266
+ )
267
+ end
268
+
269
+ def test_output_returns_decoded_output
270
+ password = Argon2id::Password.new(
271
+ "$argon2id$v=19$m=65536,t=1,p=1$c29tZXNhbHQ" \
272
+ "$9qWtwbpyPd3vm1rB1GThgPzZ3/ydHL92zKL+15XZypg"
273
+ )
274
+
275
+ assert_equal(
276
+ "\xF6\xA5\xAD\xC1\xBAr=\xDD\xEF\x9BZ\xC1\xD4d\xE1\x80\xFC\xD9\xDF\xFC\x9D\x1C\xBFv\xCC\xA2\xFE\xD7\x95\xD9\xCA\x98".b,
277
+ password.output
278
+ )
279
+ end
280
+
281
+ def test_to_s_returns_the_full_encoded_hash
282
+ password = Argon2id::Password.new(
283
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
284
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
285
+ )
286
+
287
+ assert_equal(
288
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4",
289
+ password.to_s
290
+ )
291
+ end
292
+
293
+ def test_equals_correct_password_returns_true
294
+ password = Argon2id::Password.new(
295
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
296
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
297
+ )
298
+
299
+ assert password == "password"
300
+ end
301
+
302
+ def test_equals_incorrect_password_returns_false
303
+ password = Argon2id::Password.new(
304
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
305
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
306
+ )
307
+
308
+ refute password == "differentpassword"
309
+ end
310
+
311
+ def test_equals_nil_returns_false
312
+ password = Argon2id::Password.new(
313
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
314
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
315
+ )
316
+
317
+ refute password == nil
318
+ end
319
+
320
+ def test_equals_coercible_correct_password_returns_true
321
+ password = Argon2id::Password.new(
322
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
323
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
324
+ )
325
+
326
+ assert password == StringLike.new("password")
327
+ end
328
+
329
+ def test_equals_coercible_incorrect_password_returns_false
330
+ password = Argon2id::Password.new(
331
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
332
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
333
+ )
334
+
335
+ refute password == StringLike.new("differentpassword")
336
+ end
337
+
338
+ def test_is_password_correct_password_returns_true
339
+ password = Argon2id::Password.new(
340
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
341
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
342
+ )
343
+
344
+ assert password.is_password?("password")
345
+ end
346
+
347
+ def test_is_password_incorrect_password_returns_false
348
+ password = Argon2id::Password.new(
349
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
350
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
351
+ )
352
+
353
+ refute password.is_password?("differentpassword")
354
+ end
355
+
356
+ def test_is_password_nil_returns_false
357
+ password = Argon2id::Password.new(
358
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
359
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
360
+ )
361
+
362
+ refute password.is_password?(nil)
363
+ end
364
+
365
+ def test_is_password_coercible_correct_password_returns_true
366
+ password = Argon2id::Password.new(
367
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
368
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
369
+ )
370
+
371
+ assert password.is_password?(StringLike.new("password"))
372
+ end
373
+
374
+ def test_is_password_coercible_incorrect_password_returns_false
375
+ password = Argon2id::Password.new(
376
+ "$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ" \
377
+ "$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4"
378
+ )
379
+
380
+ refute password.is_password?(StringLike.new("differentpassword"))
381
+ end
382
+
383
+ def test_create_password_returns_password
384
+ password = Argon2id::Password.create("password")
385
+
386
+ assert_instance_of Argon2id::Password, password
387
+ end
388
+
389
+ def test_create_password_uses_default_t_cost
390
+ password = Argon2id::Password.create("password")
391
+
392
+ assert_equal 2, password.t_cost
393
+ end
394
+
395
+ def test_create_password_uses_default_m_cost
396
+ password = Argon2id::Password.create("password")
397
+
398
+ assert_equal 19_456, password.m_cost
399
+ end
400
+
401
+ def test_create_password_uses_default_parallelism
402
+ password = Argon2id::Password.create("password")
403
+
404
+ assert_equal 1, password.parallelism
405
+ end
406
+
407
+ def test_create_password_uses_default_salt_len
408
+ password = Argon2id::Password.create("password")
409
+
410
+ assert_equal 16, password.salt.bytesize
411
+ end
412
+
413
+ def test_create_password_uses_default_output_len
414
+ password = Argon2id::Password.create("password")
415
+
416
+ assert_equal 32, password.output.bytesize
417
+ end
418
+
419
+ def test_create_password_with_t_cost_changes_t_cost
420
+ password = Argon2id::Password.create("password", t_cost: 1)
421
+
422
+ assert_equal(1, password.t_cost)
423
+ end
424
+
425
+ def test_create_password_with_too_small_t_cost_raises_error
426
+ assert_raises(Argon2id::Error) do
427
+ Argon2id::Password.create("password", t_cost: 0)
428
+ end
429
+ end
430
+
431
+ def test_create_password_with_m_cost_changes_m_cost
432
+ password = Argon2id::Password.create("password", m_cost: 8)
433
+
434
+ assert_equal(8, password.m_cost)
435
+ end
436
+
437
+ def test_create_password_with_too_small_m_cost_raises_error
438
+ assert_raises(Argon2id::Error) do
439
+ Argon2id::Password.create("password", m_cost: 0)
440
+ end
441
+ end
442
+
443
+ def test_create_password_with_parallelism_changes_parallelism
444
+ password = Argon2id::Password.create("password", parallelism: 2)
445
+
446
+ assert_equal(2, password.parallelism)
447
+ end
448
+
449
+ def test_create_password_with_too_small_parallelism_raises_error
450
+ assert_raises(Argon2id::Error) do
451
+ Argon2id::Password.create("password", parallelism: 0)
452
+ end
453
+ end
454
+
455
+ def test_create_password_with_too_small_salt_raises_error
456
+ assert_raises(Argon2id::Error) do
457
+ Argon2id::Password.create("password", salt_len: 0)
458
+ end
459
+ end
460
+
461
+ def test_create_password_with_output_len_changes_output_len
462
+ password = Argon2id::Password.create("password", output_len: 8)
463
+
464
+ assert_equal 8, password.output.bytesize
465
+ end
466
+
467
+ def test_create_password_with_too_output_len_raises_error
468
+ assert_raises(Argon2id::Error) do
469
+ Argon2id::Password.create("password", output_len: 0)
470
+ end
471
+ end
472
+
473
+ def test_create_password_inherits_t_cost_from_argon2id
474
+ Argon2id.t_cost = 1
475
+
476
+ password = Argon2id::Password.create("password")
477
+
478
+ assert_equal(1, password.t_cost)
479
+ ensure
480
+ Argon2id.t_cost = Argon2id::DEFAULT_T_COST
481
+ end
482
+
483
+ def test_create_password_inherits_m_cost_from_argon2id
484
+ Argon2id.m_cost = 8
485
+
486
+ password = Argon2id::Password.create("password")
487
+
488
+ assert_equal(8, password.m_cost)
489
+ ensure
490
+ Argon2id.m_cost = Argon2id::DEFAULT_M_COST
491
+ end
492
+
493
+ def test_create_password_inherits_parallelism_from_argon2id
494
+ Argon2id.parallelism = 2
495
+
496
+ password = Argon2id::Password.create("password")
497
+
498
+ assert_equal(2, password.parallelism)
499
+ ensure
500
+ Argon2id.parallelism = Argon2id::DEFAULT_PARALLELISM
501
+ end
502
+
503
+ def test_create_password_inherits_salt_len_from_argon2id
504
+ Argon2id.salt_len = 8
505
+
506
+ password = Argon2id::Password.create("password")
507
+
508
+ assert_equal(8, password.salt.bytesize)
509
+ ensure
510
+ Argon2id.salt_len = Argon2id::DEFAULT_SALT_LEN
511
+ end
512
+
513
+ def test_create_password_inherits_output_len_from_argon2id
514
+ Argon2id.output_len = 8
515
+
516
+ password = Argon2id::Password.create("password")
517
+
518
+ assert_equal(8, password.output.bytesize)
519
+ ensure
520
+ Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN
521
+ end
522
+
523
+ def test_create_password_equals_correct_password
524
+ password = Argon2id::Password.create("password")
525
+
526
+ assert password == "password"
527
+ end
528
+
529
+ def test_create_password_does_not_equal_incorrect_password
530
+ password = Argon2id::Password.create("password")
531
+
532
+ refute password == "differentpassword"
533
+ end
534
+
535
+ def test_hashing_password_verifies_correct_password
536
+ hash = Argon2id::Password.create("password").to_s
537
+ password = Argon2id::Password.new(hash)
538
+
539
+ assert password == "password"
540
+ end
541
+
542
+ def test_hashing_password_does_not_verify_incorrect_password
543
+ hash = Argon2id::Password.create("password").to_s
544
+ password = Argon2id::Password.new(hash)
545
+
546
+ refute password == "differentpassword"
547
+ end
548
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "argon2id"
5
+
6
+ class TestArgon2id < Minitest::Test
7
+ def test_t_cost_is_default_t_cost
8
+ assert_equal 2, Argon2id.t_cost
9
+ end
10
+
11
+ def test_m_cost_is_default_m_cost
12
+ assert_equal 19_456, Argon2id.m_cost
13
+ end
14
+
15
+ def test_parallelism_is_default_parallelism
16
+ assert_equal 1, Argon2id.parallelism
17
+ end
18
+
19
+ def test_salt_len_is_default_salt_len
20
+ assert_equal 16, Argon2id.salt_len
21
+ end
22
+
23
+ def test_output_len_is_default_output_len
24
+ assert_equal 32, Argon2id.output_len
25
+ end
26
+
27
+ def test_t_cost_can_be_overridden
28
+ Argon2id.t_cost = 1
29
+
30
+ assert_equal 1, Argon2id.t_cost
31
+ ensure
32
+ Argon2id.t_cost = Argon2id::DEFAULT_T_COST
33
+ end
34
+
35
+ def test_m_cost_can_be_overridden
36
+ Argon2id.m_cost = 256
37
+
38
+ assert_equal 256, Argon2id.m_cost
39
+ ensure
40
+ Argon2id.m_cost = Argon2id::DEFAULT_M_COST
41
+ end
42
+
43
+ def test_parallelism_can_be_overridden
44
+ Argon2id.parallelism = 2
45
+
46
+ assert_equal 2, Argon2id.parallelism
47
+ ensure
48
+ Argon2id.parallelism = Argon2id::DEFAULT_PARALLELISM
49
+ end
50
+
51
+ def test_salt_len_can_be_overridden
52
+ Argon2id.salt_len = 8
53
+
54
+ assert_equal 8, Argon2id.salt_len
55
+ ensure
56
+ Argon2id.salt_len = Argon2id::DEFAULT_SALT_LEN
57
+ end
58
+
59
+ def test_output_len_can_be_overridden
60
+ Argon2id.output_len = 16
61
+
62
+ assert_equal 16, Argon2id.output_len
63
+ ensure
64
+ Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN
65
+ end
66
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: argon2id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Mucur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-05 00:00:00.000000000 Z
11
+ date: 2024-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -87,7 +87,8 @@ files:
87
87
  - lib/argon2id/extension.rb
88
88
  - lib/argon2id/password.rb
89
89
  - lib/argon2id/version.rb
90
- - test/test_password.rb
90
+ - test/argon2id/test_password.rb
91
+ - test/test_argon2id.rb
91
92
  homepage: https://github.com/mudge/argon2id
92
93
  licenses:
93
94
  - BSD-3-Clause
@@ -115,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubygems_version: 3.5.16
119
+ rubygems_version: 3.5.22
119
120
  signing_key:
120
121
  specification_version: 4
121
122
  summary: Ruby bindings to Argon2
@@ -1,232 +0,0 @@
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_raises_for_hashes_with_null_bytes
102
- assert_raises(ArgumentError) do
103
- Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4\x00foo")
104
- end
105
- end
106
-
107
- def test_raises_for_non_argon2id_hashes
108
- assert_raises(ArgumentError) do
109
- Argon2id::Password.new("$argon2i$v=19$m=256,t=2,p=1$c29tZXNhbHQ$iekCn0Y3spW+sCcFanM2xBT63UP2sghkUoHLIUpWRS8")
110
- end
111
- end
112
-
113
- def test_salt_supports_versionless_hashes
114
- password = Argon2id::Password.new("$argon2id$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
115
-
116
- assert_equal "somesalt", password.salt
117
- end
118
-
119
- def test_coerces_given_hash_to_string
120
- password = Argon2id::Password.create("password")
121
-
122
- assert Argon2id::Password.new(password) == "password"
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
-
173
- def test_extracting_output_from_hash
174
- password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
175
-
176
- assert_equal "\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".b, password.output
177
- end
178
-
179
- def test_libargon2_test_case_1
180
- password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
181
-
182
- assert password == "password"
183
- end
184
-
185
- def test_libargon2_test_case_1_returns_false_with_incorrect_password
186
- password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")
187
-
188
- refute password == "not password"
189
- end
190
-
191
- def test_libargon2_test_case_2
192
- password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=2$c29tZXNhbHQ$bQk8UB/VmZZF4Oo79iDXuL5/0ttZwg2f/5U52iv1cDc")
193
-
194
- assert password == "password"
195
- end
196
-
197
- def test_encoded_password_does_not_include_trailing_null_byte
198
- password = Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 8)
199
-
200
- refute password.to_s.end_with?("\x00")
201
- end
202
-
203
- def test_raises_with_too_short_output
204
- assert_raises(Argon2id::Error) do
205
- Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 8, output_len: 1)
206
- end
207
- end
208
-
209
- def test_raises_with_too_few_threads_and_compute_lanes
210
- assert_raises(Argon2id::Error) do
211
- Argon2id::Password.create("password", t_cost: 2, m_cost: 256, parallelism: 0, salt_len: 8)
212
- end
213
- end
214
-
215
- def test_raises_with_too_small_memory_cost
216
- assert_raises(Argon2id::Error) do
217
- Argon2id::Password.create("password", t_cost: 2, m_cost: 0, salt_len: 8)
218
- end
219
- end
220
-
221
- def test_raises_with_too_small_time_cost
222
- assert_raises(Argon2id::Error) do
223
- Argon2id::Password.create("password", t_cost: 0, m_cost: 256, salt_len: 8)
224
- end
225
- end
226
-
227
- def test_raises_with_too_short_salt
228
- assert_raises(Argon2id::Error) do
229
- Argon2id::Password.create("password", t_cost: 2, m_cost: 256, salt_len: 0)
230
- end
231
- end
232
- end