v2 / vlib / crypto / argon2 / argon2.v
687 lines · 620 sloc · 18.89 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4// Based off: https://github.com/golang/crypto/tree/master/argon2
5// Package argon2 implements the Argon2 password hashing and key derivation
6// functions described in RFC 9106.
7module argon2
8
9import crypto.blake2b
10import crypto.hmac
11import crypto.rand
12import encoding.base64
13import encoding.binary
14import math.bits
15
16pub const version = u32(0x13)
17
18pub const default_time = u32(3)
19pub const default_memory = u32(64 * 1024)
20pub const default_threads = u8(4)
21pub const default_key_len = u32(32)
22pub const default_salt_len = 16
23
24const argon2_d = 0
25const argon2_i = 1
26const argon2_id = 2
27const block_length = 128
28const block_bytes = 1024
29const sync_points = u32(4)
30
31// Params controls Argon2 password hashing output.
32pub struct Params {
33pub:
34 time u32
35 memory u32
36 threads u8
37 key_len u32
38 salt_len int
39}
40
41struct InvalidTimeError {
42 Error
43 time u32
44}
45
46fn (err InvalidTimeError) msg() string {
47 return 'the number of rounds ${err.time} must be greater than 0'
48}
49
50struct InvalidThreadsError {
51 Error
52 threads u8
53}
54
55fn (err InvalidThreadsError) msg() string {
56 return 'the parallelism degree ${err.threads} must be greater than 0'
57}
58
59struct InvalidKeyLengthError {
60 Error
61 length u32
62}
63
64fn (err InvalidKeyLengthError) msg() string {
65 return 'the output length ${err.length} must be greater than 0'
66}
67
68struct InvalidSaltLengthError {
69 Error
70 length int
71}
72
73fn (err InvalidSaltLengthError) msg() string {
74 return 'the salt length ${err.length} must be greater than 0'
75}
76
77struct InvalidHashError {
78 Error
79 reason string
80}
81
82fn (err InvalidHashError) msg() string {
83 return err.reason
84}
85
86// default_params returns the recommended default Argon2id password hashing parameters.
87pub fn default_params() Params {
88 return Params{
89 time: default_time
90 memory: default_memory
91 threads: default_threads
92 key_len: default_key_len
93 salt_len: default_salt_len
94 }
95}
96
97// key derives a key from `password` and `salt` using Argon2i.
98pub fn key(password []u8, salt []u8, time u32, memory u32, threads u8, key_len u32) ![]u8 {
99 return derive_key(argon2_i, password, salt, []u8{}, []u8{}, time, memory, threads, key_len)
100}
101
102// d_key derives a key from `password` and `salt` using Argon2d.
103pub fn d_key(password []u8, salt []u8, time u32, memory u32, threads u8, key_len u32) ![]u8 {
104 return derive_key(argon2_d, password, salt, []u8{}, []u8{}, time, memory, threads, key_len)
105}
106
107// id_key derives a key from `password` and `salt` using Argon2id.
108pub fn id_key(password []u8, salt []u8, time u32, memory u32, threads u8, key_len u32) ![]u8 {
109 return derive_key(argon2_id, password, salt, []u8{}, []u8{}, time, memory, threads, key_len)
110}
111
112// generate_from_password hashes `password` with default Argon2id parameters
113// and returns a PHC-formatted encoded string.
114pub fn generate_from_password(password []u8) !string {
115 return generate_from_password_with_params(password, default_params())
116}
117
118// generate_from_password_with_params hashes `password` with Argon2id using `params`
119// and returns a PHC-formatted encoded string.
120pub fn generate_from_password_with_params(password []u8, params Params) !string {
121 normalized := normalize_params(params)
122 if normalized.salt_len <= 0 {
123 return InvalidSaltLengthError{
124 length: normalized.salt_len
125 }
126 }
127 salt := rand.bytes(normalized.salt_len)!
128 hash := derive_key(argon2_id, password, salt, []u8{}, []u8{}, normalized.time,
129 normalized.memory, normalized.threads, normalized.key_len)!
130 return encode_hash(argon2_id, salt, hash, normalized.time, normalized.memory,
131 normalized.threads)
132}
133
134// compare_hash_and_password verifies a PHC-formatted Argon2 hash against `password`.
135pub fn compare_hash_and_password(password []u8, encoded_hash []u8) ! {
136 decoded := decode_hash(encoded_hash.bytestr())!
137 other_hash := derive_key(decoded.mode, password, decoded.salt, []u8{}, []u8{}, decoded.time,
138 decoded.memory, decoded.threads, u32(decoded.hash.len))!
139 if !hmac.equal(other_hash, decoded.hash) {
140 return error('mismatched hash and password')
141 }
142}
143
144struct DecodedHash {
145 mode int
146 time u32
147 memory u32
148 threads u8
149 salt []u8
150 hash []u8
151}
152
153fn derive_key(mode int, password []u8, salt []u8, secret []u8, data []u8, time u32, memory u32, threads u8, key_len u32) ![]u8 {
154 if time < 1 {
155 return InvalidTimeError{
156 time: time
157 }
158 }
159 if threads < 1 {
160 return InvalidThreadsError{
161 threads: threads
162 }
163 }
164 if key_len < 1 {
165 return InvalidKeyLengthError{
166 length: key_len
167 }
168 }
169 threads_u32 := u32(threads)
170 effective_memory := normalize_memory(memory, threads_u32)
171 h0 :=
172 init_hash(password, salt, secret, data, time, effective_memory, threads_u32, key_len, mode)!
173 mut blocks := init_blocks(h0, effective_memory, threads_u32)!
174 process_blocks(mut blocks, time, effective_memory, threads_u32, mode)
175 return extract_key(mut blocks, effective_memory, threads_u32, key_len)
176}
177
178fn normalize_params(params Params) Params {
179 threads := if params.threads == 0 { default_threads } else { params.threads }
180 memory := if params.memory == 0 { default_memory } else { params.memory }
181 return Params{
182 time: if params.time == 0 { default_time } else { params.time }
183 memory: normalize_memory(memory, u32(threads))
184 threads: threads
185 key_len: if params.key_len == 0 { default_key_len } else { params.key_len }
186 salt_len: if params.salt_len == 0 { default_salt_len } else { params.salt_len }
187 }
188}
189
190@[inline]
191fn normalize_memory(memory u32, threads u32) u32 {
192 mut effective_memory := memory / (sync_points * threads) * (sync_points * threads)
193 if effective_memory < 2 * sync_points * threads {
194 effective_memory = 2 * sync_points * threads
195 }
196 return effective_memory
197}
198
199fn init_hash(password []u8, salt []u8, secret []u8, data []u8, time u32, memory u32, threads u32, key_len u32, mode int) ![]u8 {
200 mut params := []u8{len: 24, init: 0}
201 mut tmp := []u8{len: 4, init: 0}
202 mut d := blake2b.new512()!
203 binary.little_endian_put_u32_at(mut params, threads, 0)
204 binary.little_endian_put_u32_at(mut params, key_len, 4)
205 binary.little_endian_put_u32_at(mut params, memory, 8)
206 binary.little_endian_put_u32_at(mut params, time, 12)
207 binary.little_endian_put_u32_at(mut params, version, 16)
208 binary.little_endian_put_u32_at(mut params, u32(mode), 20)
209 d.write(params)!
210 binary.little_endian_put_u32(mut tmp, u32(password.len))
211 d.write(tmp)!
212 d.write(password)!
213 binary.little_endian_put_u32(mut tmp, u32(salt.len))
214 d.write(tmp)!
215 d.write(salt)!
216 binary.little_endian_put_u32(mut tmp, u32(secret.len))
217 d.write(tmp)!
218 d.write(secret)!
219 binary.little_endian_put_u32(mut tmp, u32(data.len))
220 d.write(tmp)!
221 d.write(data)!
222 mut h0 := []u8{len: blake2b.size512 + 8, init: 0}
223 sum := d.checksum()
224 copy(mut h0[..blake2b.size512], sum)
225 return h0
226}
227
228fn init_blocks(h0 []u8, memory u32, threads u32) ![]u64 {
229 mut block := []u8{len: block_bytes, init: 0}
230 mut blocks := []u64{len: int(memory) * block_length, init: u64(0)}
231 mut h0_block := h0.clone()
232 for lane := u32(0); lane < threads; lane++ {
233 start := lane * (memory / threads)
234 binary.little_endian_put_u32_at(mut h0_block, lane, blake2b.size512 + 4)
235 binary.little_endian_put_u32_at(mut h0_block, 0, blake2b.size512)
236 block = blake2b_hash(block_bytes, h0_block)!
237 set_block(mut blocks, start, block)
238 binary.little_endian_put_u32_at(mut h0_block, 1, blake2b.size512)
239 block = blake2b_hash(block_bytes, h0_block)!
240 set_block(mut blocks, start + 1, block)
241 }
242 return blocks
243}
244
245fn process_blocks(mut blocks []u64, time u32, memory u32, threads u32, mode int) {
246 lanes := memory / threads
247 segments := lanes / sync_points
248 for pass := u32(0); pass < time; pass++ {
249 for slice := u32(0); slice < sync_points; slice++ {
250 for lane := u32(0); lane < threads; lane++ {
251 process_segment(mut blocks, pass, slice, lane, time, memory, lanes, segments,
252 threads, mode)
253 }
254 }
255 }
256}
257
258fn process_segment(mut blocks []u64, pass u32, slice u32, lane u32, time u32, memory u32, lanes u32, segments u32, threads u32, mode int) {
259 mut addresses := [block_length]u64{}
260 mut input := [block_length]u64{}
261 zero := [block_length]u64{}
262 if mode == argon2_i || (mode == argon2_id && pass == 0 && slice < sync_points / 2) {
263 input[0] = u64(pass)
264 input[1] = u64(lane)
265 input[2] = u64(slice)
266 input[3] = u64(memory)
267 input[4] = u64(time)
268 input[5] = u64(mode)
269 }
270 mut index := u32(0)
271 if pass == 0 && slice == 0 {
272 index = 2
273 if mode == argon2_i || mode == argon2_id {
274 input[6]++
275 process_block(mut addresses, input, zero)
276 process_block(mut addresses, addresses, zero)
277 }
278 }
279 mut offset := lane * lanes + slice * segments + index
280 for index < segments {
281 mut prev := offset - 1
282 if index == 0 && slice == 0 {
283 prev += lanes
284 }
285 mut random := u64(0)
286 if mode == argon2_i || (mode == argon2_id && pass == 0 && slice < sync_points / 2) {
287 if index % u32(block_length) == 0 {
288 input[6]++
289 process_block(mut addresses, input, zero)
290 process_block(mut addresses, addresses, zero)
291 }
292 random = addresses[int(index % u32(block_length))]
293 } else {
294 random = blocks[block_offset(prev)]
295 }
296 new_offset := index_alpha(random, lanes, segments, threads, pass, slice, lane, index)
297 process_block_xor_in_place(mut blocks, offset, prev, new_offset)
298 index++
299 offset++
300 }
301}
302
303fn extract_key(mut blocks []u64, memory u32, threads u32, key_len u32) ![]u8 {
304 lanes := memory / threads
305 last_block := memory - 1
306 last_base := block_offset(last_block)
307 for lane := u32(0); lane < threads - 1; lane++ {
308 base := block_offset(lane * lanes + lanes - 1)
309 for i := 0; i < block_length; i++ {
310 blocks[last_base + i] ^= blocks[base + i]
311 }
312 }
313 mut block := []u8{len: block_bytes, init: 0}
314 for i := 0; i < block_length; i++ {
315 binary.little_endian_put_u64_at(mut block, blocks[last_base + i], i * 8)
316 }
317 return blake2b_hash(int(key_len), block)
318}
319
320@[inline]
321fn block_offset(index u32) int {
322 return int(index) * block_length
323}
324
325fn set_block(mut blocks []u64, index u32, data []u8) {
326 base := block_offset(index)
327 for i := 0; i < block_length; i++ {
328 blocks[base + i] = binary.little_endian_u64_at(data, i * 8)
329 }
330}
331
332fn blake2b_hash(out_len int, input []u8) ![]u8 {
333 mut prefix := []u8{len: 4, init: 0}
334 binary.little_endian_put_u32(mut prefix, u32(out_len))
335 mut initial := prefix.clone()
336 initial << input
337 if out_len <= blake2b.size512 {
338 mut d := blake2b.new_digest(u8(out_len), []u8{})!
339 d.write(initial)!
340 return d.checksum()
341 }
342 mut out := []u8{len: out_len, init: 0}
343 mut block := blake2b.sum512(initial)
344 copy(mut out[..32], block[..32])
345 mut pos := 32
346 for out_len - pos > blake2b.size512 {
347 block = blake2b.sum512(block)
348 copy(mut out[pos..pos + 32], block[..32])
349 pos += 32
350 }
351 remaining := out_len - pos
352 mut d := blake2b.new_digest(u8(remaining), []u8{})!
353 d.write(block)!
354 copy(mut out[pos..], d.checksum())
355 return out
356}
357
358fn process_block(mut out [block_length]u64, in1 [block_length]u64, in2 [block_length]u64) {
359 process_block_generic(mut out, in1, in2, false)
360}
361
362fn process_block_generic(mut out [block_length]u64, in1 [block_length]u64, in2 [block_length]u64, xor bool) {
363 mut t := [block_length]u64{}
364 for i := 0; i < block_length; i++ {
365 t[i] = in1[i] ^ in2[i]
366 }
367 for i := 0; i < block_length; i += 16 {
368 blamka_generic(mut t, i + 0, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9,
369
370 i + 10, i + 11, i + 12, i + 13, i + 14, i + 15)
371 }
372 for i := 0; i < block_length / 8; i += 2 {
373 blamka_generic(mut t, i, i + 1, 16 + i, 16 + i + 1, 32 + i, 32 + i + 1, 48 + i, 48 + i + 1,
374
375 64 + i, 64 + i + 1, 80 + i, 80 + i + 1, 96 + i, 96 + i + 1, 112 + i, 112 + i + 1)
376 }
377 for i := 0; i < block_length; i++ {
378 if xor {
379 out[i] ^= in1[i] ^ in2[i] ^ t[i]
380 } else {
381 out[i] = in1[i] ^ in2[i] ^ t[i]
382 }
383 }
384}
385
386fn process_block_xor_in_place(mut blocks []u64, out_index u32, in1_index u32, in2_index u32) {
387 mut t := [block_length]u64{}
388 out_base := block_offset(out_index)
389 in1_base := block_offset(in1_index)
390 in2_base := block_offset(in2_index)
391 for i := 0; i < block_length; i++ {
392 t[i] = blocks[in1_base + i] ^ blocks[in2_base + i]
393 }
394 for i := 0; i < block_length; i += 16 {
395 blamka_generic(mut t, i + 0, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7, i + 8, i + 9,
396
397 i + 10, i + 11, i + 12, i + 13, i + 14, i + 15)
398 }
399 for i := 0; i < block_length / 8; i += 2 {
400 blamka_generic(mut t, i, i + 1, 16 + i, 16 + i + 1, 32 + i, 32 + i + 1, 48 + i, 48 + i + 1,
401
402 64 + i, 64 + i + 1, 80 + i, 80 + i + 1, 96 + i, 96 + i + 1, 112 + i, 112 + i + 1)
403 }
404 for i := 0; i < block_length; i++ {
405 blocks[out_base + i] ^= blocks[in1_base + i] ^ blocks[in2_base + i] ^ t[i]
406 }
407}
408
409fn blamka_generic(mut block [block_length]u64, a int, b int, c int, d int, e int, f int, g int, h int, i int, j int, k int, l int, m int, n int, o int, p int) {
410 mut v00 := block[a]
411 mut v01 := block[b]
412 mut v02 := block[c]
413 mut v03 := block[d]
414 mut v04 := block[e]
415 mut v05 := block[f]
416 mut v06 := block[g]
417 mut v07 := block[h]
418 mut v08 := block[i]
419 mut v09 := block[j]
420 mut v10 := block[k]
421 mut v11 := block[l]
422 mut v12 := block[m]
423 mut v13 := block[n]
424 mut v14 := block[o]
425 mut v15 := block[p]
426
427 v00 += v04 + 2 * u64(u32(v00)) * u64(u32(v04))
428 v12 ^= v00
429 v12 = bits.rotate_left_64(v12, -32)
430 v08 += v12 + 2 * u64(u32(v08)) * u64(u32(v12))
431 v04 ^= v08
432 v04 = bits.rotate_left_64(v04, -24)
433
434 v00 += v04 + 2 * u64(u32(v00)) * u64(u32(v04))
435 v12 ^= v00
436 v12 = bits.rotate_left_64(v12, -16)
437 v08 += v12 + 2 * u64(u32(v08)) * u64(u32(v12))
438 v04 ^= v08
439 v04 = bits.rotate_left_64(v04, -63)
440
441 v01 += v05 + 2 * u64(u32(v01)) * u64(u32(v05))
442 v13 ^= v01
443 v13 = bits.rotate_left_64(v13, -32)
444 v09 += v13 + 2 * u64(u32(v09)) * u64(u32(v13))
445 v05 ^= v09
446 v05 = bits.rotate_left_64(v05, -24)
447
448 v01 += v05 + 2 * u64(u32(v01)) * u64(u32(v05))
449 v13 ^= v01
450 v13 = bits.rotate_left_64(v13, -16)
451 v09 += v13 + 2 * u64(u32(v09)) * u64(u32(v13))
452 v05 ^= v09
453 v05 = bits.rotate_left_64(v05, -63)
454
455 v02 += v06 + 2 * u64(u32(v02)) * u64(u32(v06))
456 v14 ^= v02
457 v14 = bits.rotate_left_64(v14, -32)
458 v10 += v14 + 2 * u64(u32(v10)) * u64(u32(v14))
459 v06 ^= v10
460 v06 = bits.rotate_left_64(v06, -24)
461
462 v02 += v06 + 2 * u64(u32(v02)) * u64(u32(v06))
463 v14 ^= v02
464 v14 = bits.rotate_left_64(v14, -16)
465 v10 += v14 + 2 * u64(u32(v10)) * u64(u32(v14))
466 v06 ^= v10
467 v06 = bits.rotate_left_64(v06, -63)
468
469 v03 += v07 + 2 * u64(u32(v03)) * u64(u32(v07))
470 v15 ^= v03
471 v15 = bits.rotate_left_64(v15, -32)
472 v11 += v15 + 2 * u64(u32(v11)) * u64(u32(v15))
473 v07 ^= v11
474 v07 = bits.rotate_left_64(v07, -24)
475
476 v03 += v07 + 2 * u64(u32(v03)) * u64(u32(v07))
477 v15 ^= v03
478 v15 = bits.rotate_left_64(v15, -16)
479 v11 += v15 + 2 * u64(u32(v11)) * u64(u32(v15))
480 v07 ^= v11
481 v07 = bits.rotate_left_64(v07, -63)
482
483 v00 += v05 + 2 * u64(u32(v00)) * u64(u32(v05))
484 v15 ^= v00
485 v15 = bits.rotate_left_64(v15, -32)
486 v10 += v15 + 2 * u64(u32(v10)) * u64(u32(v15))
487 v05 ^= v10
488 v05 = bits.rotate_left_64(v05, -24)
489
490 v00 += v05 + 2 * u64(u32(v00)) * u64(u32(v05))
491 v15 ^= v00
492 v15 = bits.rotate_left_64(v15, -16)
493 v10 += v15 + 2 * u64(u32(v10)) * u64(u32(v15))
494 v05 ^= v10
495 v05 = bits.rotate_left_64(v05, -63)
496
497 v01 += v06 + 2 * u64(u32(v01)) * u64(u32(v06))
498 v12 ^= v01
499 v12 = bits.rotate_left_64(v12, -32)
500 v11 += v12 + 2 * u64(u32(v11)) * u64(u32(v12))
501 v06 ^= v11
502 v06 = bits.rotate_left_64(v06, -24)
503
504 v01 += v06 + 2 * u64(u32(v01)) * u64(u32(v06))
505 v12 ^= v01
506 v12 = bits.rotate_left_64(v12, -16)
507 v11 += v12 + 2 * u64(u32(v11)) * u64(u32(v12))
508 v06 ^= v11
509 v06 = bits.rotate_left_64(v06, -63)
510
511 v02 += v07 + 2 * u64(u32(v02)) * u64(u32(v07))
512 v13 ^= v02
513 v13 = bits.rotate_left_64(v13, -32)
514 v08 += v13 + 2 * u64(u32(v08)) * u64(u32(v13))
515 v07 ^= v08
516 v07 = bits.rotate_left_64(v07, -24)
517
518 v02 += v07 + 2 * u64(u32(v02)) * u64(u32(v07))
519 v13 ^= v02
520 v13 = bits.rotate_left_64(v13, -16)
521 v08 += v13 + 2 * u64(u32(v08)) * u64(u32(v13))
522 v07 ^= v08
523 v07 = bits.rotate_left_64(v07, -63)
524
525 v03 += v04 + 2 * u64(u32(v03)) * u64(u32(v04))
526 v14 ^= v03
527 v14 = bits.rotate_left_64(v14, -32)
528 v09 += v14 + 2 * u64(u32(v09)) * u64(u32(v14))
529 v04 ^= v09
530 v04 = bits.rotate_left_64(v04, -24)
531
532 v03 += v04 + 2 * u64(u32(v03)) * u64(u32(v04))
533 v14 ^= v03
534 v14 = bits.rotate_left_64(v14, -16)
535 v09 += v14 + 2 * u64(u32(v09)) * u64(u32(v14))
536 v04 ^= v09
537 v04 = bits.rotate_left_64(v04, -63)
538
539 block[a] = v00
540 block[b] = v01
541 block[c] = v02
542 block[d] = v03
543 block[e] = v04
544 block[f] = v05
545 block[g] = v06
546 block[h] = v07
547 block[i] = v08
548 block[j] = v09
549 block[k] = v10
550 block[l] = v11
551 block[m] = v12
552 block[n] = v13
553 block[o] = v14
554 block[p] = v15
555}
556
557fn index_alpha(random u64, lanes u32, segments u32, threads u32, pass u32, slice u32, lane u32, index u32) u32 {
558 mut ref_lane := u32(random >> 32) % threads
559 if pass == 0 && slice == 0 {
560 ref_lane = lane
561 }
562 mut m := u32(3) * segments
563 mut s := ((slice + 1) % sync_points) * segments
564 if lane == ref_lane {
565 m += index
566 }
567 if pass == 0 {
568 m = slice * segments
569 s = 0
570 if slice == 0 || lane == ref_lane {
571 m += index
572 }
573 }
574 if index == 0 || lane == ref_lane {
575 m--
576 }
577 return phi(random, u64(m), u64(s), ref_lane, lanes)
578}
579
580fn phi(random u64, m u64, s u64, lane u32, lanes u32) u32 {
581 mut p := random & u64(0xffffffff)
582 p = (p * p) >> 32
583 p = (p * m) >> 32
584 return lane * lanes + u32((s + m - (p + 1)) % u64(lanes))
585}
586
587fn encode_hash(mode int, salt []u8, hash []u8, time u32, memory u32, threads u8) string {
588 salt_b64 := base64.encode(salt).replace_each(['=', ''])
589 hash_b64 := base64.encode(hash).replace_each(['=', ''])
590 return '\$${mode_name(mode)}\$v=${version}\$m=${memory},t=${time},p=${threads}\$${salt_b64}\$${hash_b64}'
591}
592
593fn decode_hash(encoded string) !DecodedHash {
594 parts := encoded.split('\$')
595 if parts.len != 6 || parts[0] != '' {
596 return InvalidHashError{
597 reason: 'invalid argon2 hash format'
598 }
599 }
600 if parts[2] != 'v=${version}' {
601 return InvalidHashError{
602 reason: 'unsupported argon2 hash version'
603 }
604 }
605 mode := parse_mode(parts[1])!
606 mut memory := u32(0)
607 mut time := u32(0)
608 mut threads := u8(0)
609 for param in parts[3].split(',') {
610 name := param.all_before('=')
611 value := param.all_after('=')
612 match name {
613 'm' {
614 memory = value.u32()
615 }
616 't' {
617 time = value.u32()
618 }
619 'p' {
620 threads = u8(value.int())
621 }
622 else {
623 return InvalidHashError{
624 reason: 'unsupported argon2 parameter ${name}'
625 }
626 }
627 }
628 }
629 if time == 0 || threads == 0 {
630 return InvalidHashError{
631 reason: 'invalid argon2 parameters'
632 }
633 }
634 salt := decode_base64(parts[4])
635 hash := decode_base64(parts[5])
636 if salt.len == 0 || hash.len == 0 {
637 return InvalidHashError{
638 reason: 'invalid argon2 hash payload'
639 }
640 }
641 return DecodedHash{
642 mode: mode
643 time: time
644 memory: normalize_memory(memory, u32(threads))
645 threads: threads
646 salt: salt
647 hash: hash
648 }
649}
650
651fn decode_base64(value string) []u8 {
652 mut padded := value
653 match padded.len % 4 {
654 2 { padded += '==' }
655 3 { padded += '=' }
656 else {}
657 }
658
659 return base64.decode(padded)
660}
661
662fn mode_name(mode int) string {
663 return match mode {
664 argon2_d { 'argon2d' }
665 argon2_i { 'argon2i' }
666 else { 'argon2id' }
667 }
668}
669
670fn parse_mode(name string) !int {
671 return match name {
672 'argon2d' {
673 argon2_d
674 }
675 'argon2i' {
676 argon2_i
677 }
678 'argon2id' {
679 argon2_id
680 }
681 else {
682 return InvalidHashError{
683 reason: 'unsupported argon2 algorithm ${name}'
684 }
685 }
686 }
687}
688