-
Notifications
You must be signed in to change notification settings - Fork 145
/
sodium.lua
285 lines (219 loc) · 9.48 KB
/
sodium.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
local ffi = require('ffi')
local bit = require('bit')
local loaded, lib = pcall(ffi.load, 'sodium')
if not loaded then
return nil, lib
end
local typeof = ffi.typeof
ffi.cdef [[
int sodium_init(void);
uint32_t randombytes_random(void);
void randombytes_buf(void * const buf, const size_t size);
size_t crypto_aead_xchacha20poly1305_ietf_npubbytes(void);
size_t crypto_aead_xchacha20poly1305_ietf_keybytes(void);
size_t crypto_aead_xchacha20poly1305_ietf_abytes(void);
size_t crypto_aead_xchacha20poly1305_ietf_messagebytes_max(void);
int crypto_aead_xchacha20poly1305_ietf_encrypt(unsigned char *c,
unsigned long long *clen_p,
const unsigned char *m,
unsigned long long mlen,
const unsigned char *ad,
unsigned long long adlen,
const unsigned char *nsec,
const unsigned char *npub,
const unsigned char *k);
int crypto_aead_xchacha20poly1305_ietf_decrypt(unsigned char *m,
unsigned long long *mlen_p,
unsigned char *nsec,
const unsigned char *c,
unsigned long long clen,
const unsigned char *ad,
unsigned long long adlen,
const unsigned char *npub,
const unsigned char *k);
int crypto_aead_aes256gcm_is_available(void);
size_t crypto_aead_aes256gcm_npubbytes(void);
size_t crypto_aead_aes256gcm_keybytes(void);
size_t crypto_aead_aes256gcm_abytes(void);
size_t crypto_aead_aes256gcm_messagebytes_max(void);
int crypto_aead_aes256gcm_encrypt(unsigned char *c,
unsigned long long *clen_p,
const unsigned char *m,
unsigned long long mlen,
const unsigned char *ad,
unsigned long long adlen,
const unsigned char *nsec,
const unsigned char *npub,
const unsigned char *k);
int crypto_aead_aes256gcm_decrypt(unsigned char *m,
unsigned long long *mlen_p,
unsigned char *nsec,
const unsigned char *c,
unsigned long long clen,
const unsigned char *ad,
unsigned long long adlen,
const unsigned char *npub,
const unsigned char *k);
]]
local unsigned_char_array_t = typeof('unsigned char[?]')
if lib.sodium_init() < 0 then
return nil, 'libsodium initialization failed'
end
local sodium = {}
--- Returns a random unsigned 32-bit integer.
---@return integer
function sodium.random()
return lib.randombytes_random()
end
do -- AEAD XChaCha20 Poly1305
local NPUBBYTES = tonumber(lib.crypto_aead_xchacha20poly1305_ietf_npubbytes())
local KEYBYTES = tonumber(lib.crypto_aead_xchacha20poly1305_ietf_keybytes())
local ABYTES = tonumber(lib.crypto_aead_xchacha20poly1305_ietf_abytes())
local MAX_MESSAGEBYTES = tonumber(lib.crypto_aead_xchacha20poly1305_ietf_messagebytes_max())
local mut_key_t = typeof('unsigned char[$]', KEYBYTES)
local key_t = typeof('const unsigned char[$]', KEYBYTES)
local nonce_t = typeof('const unsigned char[$]', NPUBBYTES)
sodium.aead_xchacha20_poly1305 = {}
function sodium.aead_xchacha20_poly1305.key(key)
assert(#key == KEYBYTES, 'invalid key size')
return key_t(key)
end
function sodium.aead_xchacha20_poly1305.keygen()
local k = mut_key_t()
lib.randombytes_buf(k, KEYBYTES)
return k
end
function sodium.aead_xchacha20_poly1305.nonce(nonce)
if type(nonce) == 'string' then
assert(#nonce == 4, 'invalid nonce bytes')
local a, b, c, d = nonce:byte(1, 4)
return nonce_t(a, b, c, d)
end
assert(nonce <= 0xFFFFFFFF and nonce >= 0, 'invalid nonce')
-- write u32 nonce as big-endian
local a = bit.band(bit.rshift(nonce, 24), 0xFF)
local b = bit.band(bit.rshift(nonce, 16), 0xFF)
local c = bit.band(bit.rshift(nonce, 8), 0xFF)
local d = bit.band(nonce, 0xFF)
return nonce_t(a, b, c, d) -- the rest of the bytes are zero filled
end
--- Encrypt a message `m` using the secret key `k` and public nonce `npub` and generate an
--- authentication tag of both the confidential message and non-confidential additional
--- data `ad` .
---@param m ffi.cdata*|string The message to encrypt
---@param m_len number The length of the message in bytes
---@param ad ffi.cdata*|string The additional data to encrypt
---@param ad_len number The length of the additional data in bytes
---@param npub ffi.cdata* The public nonce
---@param k ffi.cdata* The secret key
function sodium.aead_xchacha20_poly1305.encrypt(m, m_len, ad, ad_len, npub, k)
assert(m_len <= MAX_MESSAGEBYTES, 'message too long')
assert(ffi.istype(nonce_t, npub), 'invalid nonce')
assert(ffi.istype(key_t, k), 'invalid key')
local c_len = m_len + ABYTES
local c = unsigned_char_array_t(c_len)
local c_len_p = ffi.new('unsigned long long[1]', c_len)
if lib.crypto_aead_xchacha20poly1305_ietf_encrypt(c, c_len_p, m, m_len, ad, ad_len, nil, npub, k) < 0 then
return nil, 'libsodium encryption failed'
end
return c, tonumber(c_len_p[0])
end
--- Verifies that `c` includes a valid authentication tag given the secret key `k`, public
--- nonce `npub`, and optional non-confidential additional data `ad`.
---
--- If the ciphertext is validated, the message is decrypted and returned.
---@param c ffi.cdata*|string The ciphertext to decrypt
---@param c_len number The length of the ciphertext
---@param ad ffi.cdata*|string The additional data to verify
---@param ad_len number The length of the additional data
---@param npub ffi.cdata* The public nonce
---@param k ffi.cdata* The secret key
function sodium.aead_xchacha20_poly1305.decrypt(c, c_len, ad, ad_len, npub, k)
assert(c_len - ABYTES <= MAX_MESSAGEBYTES, 'message too long')
assert(ffi.istype(nonce_t, npub), 'invalid nonce')
assert(ffi.istype(key_t, k), 'invalid key')
local m_len = c_len - ABYTES
local m = unsigned_char_array_t(m_len)
local m_len_p = ffi.new('unsigned long long[1]', m_len)
if lib.crypto_aead_xchacha20poly1305_ietf_decrypt(m, m_len_p, nil, c, c_len, ad, ad_len, npub, k) < 0 then
return nil, 'libsodium decryption failed'
end
return m, tonumber(m_len_p[0])
end
end
if lib.crypto_aead_aes256gcm_is_available() ~= 0 then -- AEAD AES256-GCM
local NPUBBYTES = tonumber(lib.crypto_aead_aes256gcm_npubbytes())
local KEYBYTES = tonumber(lib.crypto_aead_aes256gcm_keybytes())
local ABYTES = tonumber(lib.crypto_aead_aes256gcm_abytes())
local MAX_MESSAGEBYTES = tonumber(lib.crypto_aead_aes256gcm_messagebytes_max())
local mut_key_t = typeof('unsigned char[$]', KEYBYTES)
local key_t = typeof('const unsigned char[$]', KEYBYTES)
local nonce_t = typeof('const unsigned char[$]', NPUBBYTES)
sodium.aead_aes256_gcm = {}
function sodium.aead_aes256_gcm.key(key)
assert(#key == KEYBYTES, 'invalid key size')
return key_t(key)
end
function sodium.aead_aes256_gcm.keygen()
local k = mut_key_t()
lib.randombytes_buf(k, KEYBYTES)
return k
end
function sodium.aead_aes256_gcm.nonce(nonce)
if type(nonce) == 'string' then
assert(#nonce == 4, 'invalid nonce bytes')
local a, b, c, d = nonce:byte(1, 4)
return nonce_t(a, b, c, d)
end
assert(nonce <= 0xFFFFFFFF and nonce >= 0, 'invalid nonce')
-- write u32 nonce as big-endian
local a = bit.band(bit.rshift(nonce, 24), 0xFF)
local b = bit.band(bit.rshift(nonce, 16), 0xFF)
local c = bit.band(bit.rshift(nonce, 8), 0xFF)
local d = bit.band(nonce, 0xFF)
return nonce_t(a, b, c, d) -- the rest of the bytes are zero filled
end
--- Encrypt a message `m` using the secret key `k` and public nonce `npub` and generate an
--- authentication tag of both the confidential message and non-confidential additional
--- data `ad` .
---@param m ffi.cdata*|string The message to encrypt
---@param m_len number The length of the message in bytes
---@param ad ffi.cdata*|string The additional data to encrypt
---@param ad_len number The length of the additional data in bytes
---@param npub ffi.cdata* The public nonce
---@param k ffi.cdata* The secret key
function sodium.aead_aes256_gcm.encrypt(m, m_len, ad, ad_len, npub, k)
assert(m_len <= MAX_MESSAGEBYTES, 'message too long')
assert(ffi.istype(nonce_t, npub), 'invalid nonce')
assert(ffi.istype(key_t, k), 'invalid key')
local ciphertext_len = m_len + ABYTES
local ciphertext = unsigned_char_array_t(ciphertext_len)
local ciphertext_len_p = ffi.new('unsigned long long[1]', ciphertext_len)
if lib.crypto_aead_aes256gcm_encrypt(ciphertext, ciphertext_len_p, m, m_len, ad, ad_len, nil, npub, k) < 0 then
return nil, 'libsodium encryption failed'
end
return ciphertext, tonumber(ciphertext_len_p[0])
end
--- Verifies that `c` includes a valid authentication tag given the secret key `k`, public
--- nonce `npub`, and optional non-confidential additional data `ad`.
---
--- If the ciphertext is validated, the message is decrypted and returned.
---@param c ffi.cdata*|string The ciphertext to decrypt
---@param c_len number The length of the ciphertext
---@param ad ffi.cdata*|string The additional data to verify
---@param ad_len number The length of the additional data
---@param npub ffi.cdata* The public nonce
---@param k ffi.cdata* The secret key
function sodium.aead_aes256_gcm.decrypt(c, c_len, ad, ad_len, npub, k)
assert(ffi.istype(nonce_t, npub), 'invalid nonce')
assert(ffi.istype(key_t, k), 'invalid key')
local m_len = c_len - ABYTES
local m = unsigned_char_array_t(m_len)
local m_len_p = ffi.new('unsigned long long[1]', m_len)
if lib.crypto_aead_aes256gcm_decrypt(m, m_len_p, nil, c, c_len, ad, ad_len, npub, k) < 0 then
return nil, 'libsodium decryption failed'
end
return m, tonumber(m_len_p[0])
end
end
return sodium