Initial Commit
[ancientarts.git] / api / argon2.lua
1 -- vim:set st=4 sw=4 sts=4 et:
2 local ffi = require "ffi"
3
4
5 local ffi_new = ffi.new
6 local ffi_str = ffi.string
7 local find = string.find
8 local error = error
9 local type = type
10
11
12 local empty_t = {}
13
14
15 ffi.cdef [[
16 typedef enum Argon2_type {
17 Argon2_d = 0,
18 Argon2_i = 1,
19 Argon2_id = 2
20 } argon2_type;
21
22 const char *argon2_error_message(int error_code);
23
24 size_t argon2_encodedlen(uint32_t t_cost, uint32_t m_cost,
25 uint32_t parallelism, uint32_t saltlen,
26 uint32_t hashlen, argon2_type type);
27
28 int argon2i_hash_encoded(const uint32_t t_cost,
29 const uint32_t m_cost,
30 const uint32_t parallelism,
31 const void *pwd, const size_t pwdlen,
32 const void *salt, const size_t saltlen,
33 const size_t hashlen, char *encoded,
34 const size_t encodedlen);
35
36 int argon2d_hash_encoded(const uint32_t t_cost,
37 const uint32_t m_cost,
38 const uint32_t parallelism,
39 const void *pwd, const size_t pwdlen,
40 const void *salt, const size_t saltlen,
41 const size_t hashlen, char *encoded,
42 const size_t encodedlen);
43
44 int argon2id_hash_encoded(const uint32_t t_cost,
45 const uint32_t m_cost,
46 const uint32_t parallelism,
47 const void *pwd, const size_t pwdlen,
48 const void *salt, const size_t saltlen,
49 const size_t hashlen, char *encoded,
50 const size_t encodedlen);
51
52 int argon2_verify(const char *encoded,
53 const void *pwd,
54 const size_t pwdlen,
55 argon2_type type);
56 ]]
57
58
59 local lib = ffi.load "argon2"
60
61
62 local ARGON2_OK = 0
63 local ARGON2_VERIFY_MISMATCH = -35
64
65
66 local argon2_i, argon2_d, argon2_id
67
68
69 do
70 local argon2_t = ffi.typeof(ffi.new "argon2_type")
71
72 argon2_i = ffi_new(argon2_t, "Argon2_i")
73 argon2_d = ffi_new(argon2_t, "Argon2_d")
74 argon2_id = ffi_new(argon2_t, "Argon2_id")
75 end
76
77
78 local _M = {
79 _VERSION = "3.0.1",
80 _AUTHOR = "Thibault Charbonnier",
81 _LICENSE = "MIT",
82 _URL = "https://github.com/thibaultcha/lua-argon2-ffi",
83 variants = {
84 argon2_i = argon2_i,
85 argon2_d = argon2_d,
86 argon2_id = argon2_id,
87 },
88 }
89
90
91 function _M.hash_encoded(pwd, salt, opts)
92 if type(pwd) ~= "string" then
93 return error("bad argument #1 to 'hash_encoded' (string expected, got "
94 .. type(pwd) .. ")", 2)
95 end
96
97 if type(salt) ~= "string" then
98 return error("bad argument #2 to 'hash_encoded' (string expected, got "
99 .. type(salt) .. ")", 2)
100 end
101
102 if not opts then
103 opts = empty_t
104 end
105
106 if type(opts) ~= "table" then
107 return error("bad argument #3 to 'hash_encoded' (expected to be a "
108 .. "table)", 2)
109 end
110
111
112 local t_cost = opts.t_cost or 3
113 local m_cost = opts.m_cost or 4096
114 local parallelism = opts.parallelism or 1
115 local hash_len = opts.hash_len or 32
116 local variant = opts.variant or argon2_i
117
118 if type(variant) ~= "cdata" then
119 return error("bad argument #3 to 'hash_encoded' (expected " ..
120 "variant to be an argon2_type, got " ..
121 type(opts.variant) .. ")", 2)
122 end
123
124 if type(t_cost) ~= "number" then
125 return error("bad argument #3 to 'hash_encoded' (expected " ..
126 "t_cost to be a number, got " ..
127 type(t_cost) .. ")", 2)
128 end
129
130 if type(m_cost) ~= "number" then
131 return error("bad argument #3 to 'hash_encoded' (expected " ..
132 "m_cost to be a number, got " ..
133 type(m_cost) .. ")", 2)
134 end
135
136 if type(parallelism) ~= "number" then
137 return error("bad argument #3 to 'hash_encoded' (expected " ..
138 "parallelism to be a number, got " ..
139 type(parallelism) .. ")", 2)
140 end
141
142 if type(hash_len) ~= "number" then
143 return error("bad argument #3 to 'hash_encoded' (expected " ..
144 "hash_len to be a number, got " ..
145 type(hash_len) .. ")", 2)
146 end
147
148 local buf_len = lib.argon2_encodedlen(t_cost, m_cost,
149 parallelism, #salt,
150 hash_len, variant)
151
152 local buf = ffi_new("char[?]", buf_len)
153 local ret_code
154
155 if opts.variant == argon2_d then
156 ret_code = lib.argon2d_hash_encoded(t_cost, m_cost,
157 parallelism, pwd, #pwd, salt,
158 #salt, hash_len, buf, buf_len)
159
160 elseif opts.variant == argon2_id then
161 ret_code = lib.argon2id_hash_encoded(t_cost, m_cost,
162 parallelism, pwd, #pwd, salt,
163 #salt, hash_len, buf, buf_len)
164
165 else
166 ret_code = lib.argon2i_hash_encoded(t_cost, m_cost,
167 parallelism, pwd, #pwd, salt,
168 #salt, hash_len, buf, buf_len)
169 end
170
171 if ret_code ~= ARGON2_OK then
172 local c_msg = lib.argon2_error_message(ret_code)
173 return nil, ffi_str(c_msg)
174 end
175
176 return ffi_str(buf)
177 end
178
179
180 function _M.verify(encoded, plain)
181 if type(encoded) ~= "string" then
182 return error("bad argument #1 to 'verify' (string expected, got "
183 .. type(encoded) .. ")", 2)
184 end
185
186 if type(plain) ~= "string" then
187 return error("bad argument #2 to 'verify' (string expected, got "
188 .. type(plain) .. ")", 2)
189 end
190
191 local variant
192
193 if find(encoded, "argon2d", nil, true) then
194 variant = argon2_d
195
196 elseif find(encoded, "argon2id", nil, true) then
197 variant = argon2_id
198
199 else
200 variant = argon2_i
201 end
202
203 local ret_code = lib.argon2_verify(encoded, plain, #plain, variant)
204
205 if ret_code == ARGON2_VERIFY_MISMATCH then
206 return false
207 end
208
209 if ret_code ~= ARGON2_OK then
210 local c_msg = lib.argon2_error_message(ret_code)
211 return nil, ffi_str(c_msg)
212 end
213
214 return true
215 end
216
217
218 return _M