Skip to content

Commit 347bb4d

Browse files
committed
Provide preset parameters for RFC 9106
GitHub: #8 Allow users to choose from recommended parameters from RFC 9106 and the OWASP Password Storage Cheat Sheet by providing them as constants on `Argon2id`. To allow users to use this as defaults without having to pass them to every call to `Argon2id::Password.create`, add a new `Argon2id.set_defaults` (and corresponding `Argon2id.defaults`) method for setting multiple parameters at once.
1 parent e08609b commit 347bb4d

3 files changed

Lines changed: 229 additions & 5 deletions

File tree

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,35 @@ password.to_s
110110
#=> "$argon2id$v=19$m=12288,t=3,p=1$uukIsLS6y6etvsgoN20kVg$exMvDX/P9exvEPmnZL2gZClRyMdrnqjqyysLMP/VUWA"
111111
```
112112

113+
For convenience, several sets of parameters are available as constants:
114+
115+
1. The first recommended option from [RFC
116+
9106](https://datatracker.ietf.org/doc/rfc9106/):
117+
118+
```ruby
119+
password = Argon2id::Password.create("opensesame", **Argon2id::RFC_9106_HIGH_MEMORY)
120+
password.to_s
121+
#=> "$argon2id$v=19$m=2097152,t=1,p=4$6mZF5heTzNrztem0+ICjpg$ftqgeGJ0Hfsqymu1aeb4cXL11pjgbcIuIjYwFJOOUVM"
122+
```
123+
124+
2. The second recommended option from RFC 9106:
125+
126+
```ruby
127+
password = Argon2id::Password.create("opensesame", **Argon2id::RFC_9106_LOW_MEMORY)
128+
password.to_s
129+
#=> "$argon2id$v=19$m=65536,t=3,p=4$RSoUjYKa5Xg8zoPtv/LJgQ$wKGeEUJXaoG4yRCX5SyINyKWO1a78IL6nVToraNwwqY"
130+
```
131+
132+
3. The second recommended option from the [OWASP Password Storage Cheat
133+
Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id)
134+
(this is the default if no keyword arguments are passed):
135+
136+
```ruby
137+
password = Argon2id::Password.create("opensesame", **Argon2id::OWASP_2)
138+
password.to_s
139+
#=> "$argon2id$v=19$m=19456,t=2,p=1$CG+LJTSf0ghYGvPtUYdyqA$cynug5xL6dRN4YOrG4MCzc/3EWkJxwg+D0gZkoyPeH8"
140+
```
141+
113142
If you want to override the parameters for all calls to
114143
`Argon2id::Password.create`, you can set them on `Argon2id` directly:
115144

@@ -121,6 +150,14 @@ Argon2id.salt_len = 16
121150
Argon2id.output_len = 32
122151
```
123152

153+
To set multiple parameters at once or use one of the constants, you can use
154+
`Argon2id.set_defaults`:
155+
156+
```ruby
157+
Argon2id.set_defaults(t_cost: 3, m_cost: 12288)
158+
Argon2id.set_defaults(**Argon2id::RFC_9106_HIGH_MEMORY)
159+
```
160+
124161
### Verifying passwords
125162

126163
To verify a password against a hash, use `Argon2id::Password#==`:

lib/argon2id.rb

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,53 @@
55
require "argon2id/version"
66

77
module Argon2id
8+
# OWASP Password Storage Cheat Sheet second recommended parameters.
9+
#
10+
# See https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
11+
OWASP_2 = {
12+
t_cost: 2,
13+
m_cost: 19_456,
14+
parallelism: 1,
15+
salt_len: 16,
16+
output_len: 32
17+
}.freeze
18+
19+
# RFC 9106 first recommended parameters.
20+
#
21+
# See 4. Parameter Choice in https://datatracker.ietf.org/doc/rfc9106/
22+
RFC_9106_HIGH_MEMORY = {
23+
t_cost: 1,
24+
parallelism: 4,
25+
m_cost: 2_097_152,
26+
salt_len: 16,
27+
output_len: 32
28+
}.freeze
29+
30+
# RFC 9106 second recommended parameters.
31+
#
32+
# See 4. Parameter Choice in https://datatracker.ietf.org/doc/rfc9106/
33+
RFC_9106_LOW_MEMORY = {
34+
t_cost: 3,
35+
parallelism: 4,
36+
m_cost: 65_536,
37+
salt_len: 16,
38+
output_len: 32
39+
}.freeze
40+
841
# The default "time cost" of 2 iterations recommended by OWASP.
9-
DEFAULT_T_COST = 2
42+
DEFAULT_T_COST = OWASP_2[:t_cost]
1043

1144
# The default "memory cost" of 19 mebibytes recommended by OWASP.
12-
DEFAULT_M_COST = 19_456
45+
DEFAULT_M_COST = OWASP_2[:m_cost]
1346

1447
# The default 1 thread and compute lane recommended by OWASP.
15-
DEFAULT_PARALLELISM = 1
48+
DEFAULT_PARALLELISM = OWASP_2[:parallelism]
1649

1750
# The default salt length of 16 bytes.
18-
DEFAULT_SALT_LEN = 16
51+
DEFAULT_SALT_LEN = OWASP_2[:salt_len]
1952

2053
# The default desired hash length of 32 bytes.
21-
DEFAULT_OUTPUT_LEN = 32
54+
DEFAULT_OUTPUT_LEN = OWASP_2[:output_len]
2255

2356
@t_cost = DEFAULT_T_COST
2457
@m_cost = DEFAULT_M_COST
@@ -41,5 +74,33 @@ class << self
4174

4275
# The default desired length of the hash in bytes used by Argon2id::Password.create
4376
attr_accessor :output_len
77+
78+
# Set default parameters used by Argon2id::Password.create
79+
#
80+
# Argon2id.set_defaults(t_cost: 1, m_cost: 47104, parallelism: 1)
81+
# Argon2id.set_defaults(**Argon2id::RFC_9106_HIGH_MEMORY)
82+
def set_defaults(t_cost: self.t_cost, m_cost: self.m_cost, parallelism: self.parallelism, salt_len: self.salt_len, output_len: self.output_len)
83+
@t_cost = t_cost
84+
@m_cost = m_cost
85+
@parallelism = parallelism
86+
@salt_len = salt_len
87+
@output_len = output_len
88+
89+
defaults
90+
end
91+
92+
# Return all default parameters used by Argon2id::Password.create
93+
#
94+
# Argon2id.defaults
95+
# #=> {:t_cost=>2, :m_cost=>19456, :parallelism=>1, :salt_len=>16, :output_len=>32}
96+
def defaults
97+
{
98+
t_cost: t_cost,
99+
m_cost: m_cost,
100+
parallelism: parallelism,
101+
salt_len: salt_len,
102+
output_len: output_len
103+
}
104+
end
44105
end
45106
end

test/test_argon2id.rb

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,130 @@ def test_output_len_can_be_overridden
6363
ensure
6464
Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN
6565
end
66+
67+
def test_owasp_2_uses_t_cost_of_2
68+
assert_equal 2, Argon2id::OWASP_2[:t_cost]
69+
end
70+
71+
def test_owasp_2_uses_parallelism_of_1
72+
assert_equal 1, Argon2id::OWASP_2[:parallelism]
73+
end
74+
75+
def test_owasp_2_uses_m_cost_of_19_mib
76+
assert_equal 19_456, Argon2id::OWASP_2[:m_cost]
77+
end
78+
79+
def test_owasp_2_uses_salt_len_of_128_bits
80+
assert_equal 128/8, Argon2id::OWASP_2[:salt_len]
81+
end
82+
83+
def test_owasp_2_uses_output_len_of_256_bits
84+
assert_equal 256/8, Argon2id::OWASP_2[:output_len]
85+
end
86+
87+
def test_rfc_9106_high_memory_uses_t_cost_of_1
88+
assert_equal 1, Argon2id::RFC_9106_HIGH_MEMORY[:t_cost]
89+
end
90+
91+
def test_rfc_9106_high_memory_uses_parallelism_of_4
92+
assert_equal 4, Argon2id::RFC_9106_HIGH_MEMORY[:parallelism]
93+
end
94+
95+
def test_rfc_9106_high_memory_uses_m_cost_of_2_gib
96+
assert_equal 2**21, Argon2id::RFC_9106_HIGH_MEMORY[:m_cost]
97+
end
98+
99+
def test_rfc_9106_high_memory_uses_salt_len_of_128_bits
100+
assert_equal 128/8, Argon2id::RFC_9106_HIGH_MEMORY[:salt_len]
101+
end
102+
103+
def test_rfc_9106_high_memory_uses_output_len_of_256_bits
104+
assert_equal 256/8, Argon2id::RFC_9106_HIGH_MEMORY[:output_len]
105+
end
106+
107+
def test_rfc_9106_low_memory_uses_t_cost_of_3
108+
assert_equal 3, Argon2id::RFC_9106_LOW_MEMORY[:t_cost]
109+
end
110+
111+
def test_rfc_9106_low_memory_uses_parallelism_of_4
112+
assert_equal 4, Argon2id::RFC_9106_LOW_MEMORY[:parallelism]
113+
end
114+
115+
def test_rfc_9106_low_memory_uses_m_cost_of_64_mib
116+
assert_equal 2**16, Argon2id::RFC_9106_LOW_MEMORY[:m_cost]
117+
end
118+
119+
def test_rfc_9106_low_memory_uses_salt_len_of_128_bits
120+
assert_equal 128/8, Argon2id::RFC_9106_LOW_MEMORY[:salt_len]
121+
end
122+
123+
def test_rfc_9106_low_memory_uses_output_len_of_256_bits
124+
assert_equal 256/8, Argon2id::RFC_9106_LOW_MEMORY[:output_len]
125+
end
126+
127+
def test_set_defaults_sets_t_cost
128+
Argon2id.set_defaults(t_cost: 1)
129+
130+
assert_equal 1, Argon2id.t_cost
131+
ensure
132+
Argon2id.t_cost = Argon2id::DEFAULT_T_COST
133+
end
134+
135+
def test_set_defaults_does_not_change_missing_parameters
136+
Argon2id.m_cost = 47_014
137+
Argon2id.set_defaults(t_cost: 1)
138+
139+
assert_equal 47_014, Argon2id.m_cost
140+
ensure
141+
Argon2id.t_cost = Argon2id::DEFAULT_T_COST
142+
Argon2id.m_cost = Argon2id::DEFAULT_M_COST
143+
end
144+
145+
def test_set_defaults_sets_m_cost
146+
Argon2id.set_defaults(m_cost: 47_104)
147+
148+
assert_equal 47_104, Argon2id.m_cost
149+
ensure
150+
Argon2id.m_cost = Argon2id::DEFAULT_M_COST
151+
end
152+
153+
def test_set_defaults_sets_parallelism
154+
Argon2id.set_defaults(parallelism: 4)
155+
156+
assert_equal 4, Argon2id.parallelism
157+
ensure
158+
Argon2id.parallelism = Argon2id::DEFAULT_PARALLELISM
159+
end
160+
161+
def test_set_defaults_sets_salt_len
162+
Argon2id.set_defaults(salt_len: 32)
163+
164+
assert_equal 32, Argon2id.salt_len
165+
ensure
166+
Argon2id.salt_len = Argon2id::DEFAULT_SALT_LEN
167+
end
168+
169+
def test_set_defaults_sets_output_len
170+
Argon2id.set_defaults(output_len: 32)
171+
172+
assert_equal 32, Argon2id.output_len
173+
ensure
174+
Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN
175+
end
176+
177+
def test_set_defaults_returns_all_defaults
178+
assert_equal(
179+
{ t_cost: 2, m_cost: 19_456, parallelism: 1, salt_len: 16, output_len: 16 },
180+
Argon2id.set_defaults(output_len: 16)
181+
)
182+
ensure
183+
Argon2id.output_len = Argon2id::DEFAULT_OUTPUT_LEN
184+
end
185+
186+
def test_defaults_returns_all_parameters
187+
assert_equal(
188+
{ t_cost: 2, m_cost: 19_456, parallelism: 1, salt_len: 16, output_len: 32 },
189+
Argon2id.defaults
190+
)
191+
end
66192
end

0 commit comments

Comments
 (0)