src/crypto/password.bend
bend-collections-laws-crypto@1.0.0.0 · 110 lines · 16 definitions
1import Base
2import bend-collections-laws-crypto@1.0.0.0/src/crypto/argon2/argon2.bend as A2
3import bend-collections-laws-crypto@1.0.0.0/src/crypto/argon2/phc.bend as PHC
4import bend-collections-laws-crypto@1.0.0.0/src/crypto/subtle.bend as Subtle
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type Params is Data:
25 Params{m: Nat, t: Nat, p: Nat, tag: Nat, salt: Nat}
26
27def owasp() -> Params:
28 Params{19456n, 2n, 1n, 32n, 16n}
29
30def rfc9106() -> Params:
31 Params{65536n, 3n, 4n, 32n, 16n}
32
33def len(xs: List<&2, U32>) -> Nat:
34 List.length(&2, U32, xs)
35
36def encode(+m: Nat, +t: Nat, +p: Nat, +salt: List<&2, U32>, r: Maybe<&2, List<&2, U32>>) -> Maybe<&2, String>:
37 match r:
38 case None{}:
39 None{}
40 case Some{h}:
41 Some{PHC.format(PHC.Phc{m, t, p, salt, h})}
42
43
44
45def hash_password(+pw: List<&2, U32>, +salt: List<&2, U32>, params: Params) -> Maybe<&2, String>:
46 match params:
47 case Params{+m, +t, +p, +tag, s}:
48 encode(m, t, p, salt, A2.argon2id(pw, salt, Nil{}, Nil{}, t, m, p, tag))
49
50def verify_tag(+hash: List<&2, U32>, r: Maybe<&2, List<&2, U32>>) -> Bool:
51 match r:
52 case None{}:
53 False{}
54 case Some{tag}:
55 Subtle.eq(tag, hash)
56
57def verify_phc(+pw: List<&2, U32>, x: PHC.Phc) -> Bool:
58 match x:
59 case PHC.Phc{+m, +t, +p, +salt, +hash}:
60 verify_tag(hash, A2.argon2id(pw, salt, Nil{}, Nil{}, t, m, p, len(hash)))
61
62def verify_parsed(+pw: List<&2, U32>, r: Maybe<&2, PHC.Phc>) -> Bool:
63 match r:
64 case None{}:
65 False{}
66 case Some{x}:
67 verify_phc(pw, x)
68
69
70def verify_password(+pw: List<&2, U32>, encoded: String) -> Bool:
71 verify_parsed(pw, PHC.parse(encoded))
72
73def same(+x: PHC.Phc, params: Params) -> Bool:
74 match x params:
75 case PHC.Phc{+m, +t, +p, +salt, +hash} Params{+pm, +pt, +pp, +ptag, +psalt}:
76 Bool.and(Bool.and(Nat.is_eq(m, pm), Bool.and(Nat.is_eq(t, pt), Nat.is_eq(p, pp))), Bool.and(Nat.is_eq(len(hash), ptag), Nat.is_eq(len(salt), psalt)))
77
78def rehash_parsed(r: Maybe<&2, PHC.Phc>, params: Params) -> Bool:
79 match r:
80 case None{}:
81 True{}
82 case Some{x}:
83 Bool.not(same(x, params))
84
85
86
87def needs_rehash(encoded: String, params: Params) -> Bool:
88 rehash_parsed(PHC.parse(encoded), params)
89
90
91def random_bytes(n: Nat) -> IO(List<&2, U32>):
92 match n:
93 case 0n:
94 IO.pure(List<&2, U32>, Nil{})
95 case 1n+k:
96 do IO<List<&2, U32>>:
97 w : U32 <- IO.try(U32, IO.random_u32())
98 rest : List<&2, U32> <- random_bytes(k)
99 return U32.and(w, 255) <> rest
100
101def salt_len(+params: Params) -> Nat:
102 match params:
103 case Params{m, t, p, tag, s}:
104 s
105
106
107def hash_password_os(+pw: List<&2, U32>, +params: Params) -> IO(Maybe<&2, String>):
108 do IO<Maybe<&2, String>>:
109 salt : List<&2, U32> <- random_bytes(salt_len(params))
110 return hash_password(pw, salt, params)