v2 / vlib / x / crypto / ascon / aead128.v
458 lines · 405 sloc · 14.38 KB · 7101472677283577ec8aa3dc4976fbd70ad6a702
Raw
1// Copyright ©2025 blackshirt.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4//
5// This file implements an Authenticated Encryption with Associated Data based on Ascon-AEAD128,
6// AEAD Scheme defined in NIST.SP.800-232 standard.
7module ascon
8
9import encoding.binary
10import crypto.internal.subtle
11
12// The constants for Ascon-AEAD128
13//
14// key_size is 128-bit size of Ascon-AEAD128 key
15pub const key_size = 16
16// nonce_size is 128-bit size of Ascon-AEAD128 nonce
17pub const nonce_size = 16
18// tag_size is 128-bit size of Ascon-AEAD128 authentication tag
19pub const tag_size = 16
20
21// aead128_iv is a precomputed initialization phase values for Ascon-AEAD128
22// See Table 14 of NIST SP 800-232 doc.
23const aead128_iv = u64(0x0000_1000_808c_0001)
24
25// aead128_data_limit is a limit amount of data processed during encryption and decryption,
26// including the nonce, shall not exceed 2⁵⁴ bytes for a given key.
27const aead128_data_limit = u64(1) << 54 - 1
28
29// aead128_block_size is a number (rate) of input bytes processed per invocation of the underlying of state.
30// Ascon-AEAD128 working with 128-bit rate
31const aead128_block_size = 16
32
33// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
34// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
35// was stored within the end of ciphertext.
36// Note: The Ascon-AEAD128 key shall be kept secret,
37pub fn encrypt(key []u8, nonce []u8, ad []u8, msg []u8) ![]u8 {
38 // Preliminary check
39 if key.len != key_size {
40 return error('encrypt: invalid key size')
41 }
42 if nonce.len != nonce_size {
43 return error('encrypt: invalid nonce size')
44 }
45 // The key shall be updated to a new key once the total amount of input data reaches the limit
46 data_length := u64(nonce.len) + u64(msg.len) + u64(ad.len)
47 if data_length > aead128_data_limit {
48 return error('encrypt: exceed data limit')
49 }
50 mut s := State{}
51 mut out := []u8{len: msg.len + tag_size}
52
53 // Ascon-AEAD128 comprises four phases:
54 // - initialization of the state,
55 // - associated data processing,
56 // - plaintext processing,
57 // - and finalization (includes writing the tag).
58 k0, k1 := aead128_init(mut s, key, nonce)
59 aead128_process_ad(mut s, ad)
60 loc := aead128_process_msg(mut out, mut s, msg)
61 aead128_finalize(mut s, k0, k1)
62 aead128_write_tag(mut out, s, loc)
63
64 // clean out the intermediate Ascon state
65 reset_state(mut s)
66 return out
67}
68
69// decrypt decrypts authenticated encrypted messages in ciphertext that encrypted under
70// provided key and nonce with additional data in `ad`.
71// It would check if authentication tag mas matching and return decrypted message
72// if success or error on fails.
73@[direct_array_access]
74pub fn decrypt(key []u8, nonce []u8, ad []u8, ciphertext []u8) ![]u8 {
75 // Preliminary check
76 if key.len != key_size {
77 return error('decrypt: invalid key size')
78 }
79 if nonce.len != nonce_size {
80 return error('decrypt: invalid nonce size')
81 }
82 if ciphertext.len < tag_size {
83 return error('decrypt: invalid ciphertext size')
84 }
85 data_length := u64(nonce.len) + u64(ciphertext.len) + u64(ad.len)
86 if data_length > aead128_data_limit {
87 return error('decrypt: exceed data limit')
88 }
89 mut s := State{}
90 // Initialization phase and additional data processing
91 k0, k1 := aead128_init(mut s, key, nonce)
92 aead128_process_ad(mut s, ad)
93
94 // Decryption phase, start by slicing the ciphertext
95 cmsg := ciphertext[0..ciphertext.len - tag_size]
96 stag := ciphertext[ciphertext.len - tag_size..ciphertext.len]
97 mut msg := []u8{len: ciphertext.len - tag_size}
98
99 // Partially decrypt the cmsg and stored into msg buffer
100 aead128_partial_dec(mut msg, mut s, cmsg)
101
102 // Finalizes the state and calc the tag and compares with expected tag.
103 // It would return error if the tag was unmatching.
104 aead128_finalize(mut s, k0, k1)
105 mut ctag := []u8{len: tag_size}
106 aead128_write_tag(mut ctag, s, 0)
107 if subtle.constant_time_compare(ctag, stag) != 1 {
108 // clean up
109 unsafe {
110 msg.reset()
111 ctag.reset()
112 }
113 reset_state(mut s)
114 return error('decrypt: unmatching tag')
115 }
116 return msg
117}
118
119// Aead128 is an opaque provides an implementation of Ascon-AEAD128 from NIST.SP.800-232 standard.
120// Its implements `x.crypto.chacha20poly1305.AEAD` interfaces.
121@[noinit]
122pub struct Aead128 {
123 State
124mut:
125 // 32-bytes of underlying key
126 key [2]u64
127}
128
129// new_aead128 creates a new Aead128 instance and initialized with supplied key.
130@[direct_array_access]
131pub fn new_aead128(key []u8) !&Aead128 {
132 if key.len != key_size {
133 return error('invalid cipher key size')
134 }
135 k0 := binary.little_endian_u64(key[0..8])
136 k1 := binary.little_endian_u64(key[8..16])
137
138 mut c := &Aead128{}
139 // Partially initializes state
140 c.State.e0 = aead128_iv
141 c.State.e1 = k0
142 c.State.e2 = k1
143 // stores the key
144 c.key[0] = k0
145 c.key[1] = k1
146
147 return c
148}
149
150// nonce_size returns the nonce size of Ascon-AEAD128 Aead128.
151pub fn (c &Aead128) nonce_size() int {
152 return nonce_size
153}
154
155// overhead returns the maximum difference between the lengths of a plaintext and its ciphertext.
156pub fn (c &Aead128) overhead() int {
157 return tag_size
158}
159
160// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
161// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
162// was stored within the end of ciphertext.
163@[direct_array_access]
164pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 {
165 // Check for the nonce
166 if nonce.len != nonce_size {
167 return error('encrypt: invalid nonce size')
168 }
169 data_length := u64(msg.len) + u64(ad.len) + 32
170 if data_length > aead128_data_limit {
171 return error('encrypt: exceed data limit')
172 }
173 // Initialization phase
174 n0 := binary.little_endian_u64(nonce[0..8])
175 n1 := binary.little_endian_u64(nonce[8..16])
176 // setup state
177 c.State.e0 = aead128_iv
178 c.State.e1 = c.key[0]
179 c.State.e2 = c.key[1]
180 c.State.e3 = n0
181 c.State.e4 = n1
182
183 // Update state by permutation
184 ascon_pnr(mut c.State, .ascon_prnd_12)
185 // XOR-ing with the cipher's key
186 c.State.e3 ^= c.key[0]
187 c.State.e4 ^= c.key[1]
188
189 // Associated data processing
190 aead128_process_ad(mut c.State, ad)
191
192 // Message processing
193 mut dst := []u8{len: msg.len + tag_size}
194 n := aead128_process_msg(mut dst, mut c.State, msg)
195
196 // Finalization and writes out the tag into dst
197 aead128_finalize(mut c.State, c.key[0], c.key[1])
198 aead128_write_tag(mut dst, c.State, n)
199
200 return dst
201}
202
203// decrypt decrypts the ciphertext and validates authentication tag with
204// provided nonce and additional data ad. It returns error on fails or tag unmatching.
205@[direct_array_access]
206pub fn (mut c Aead128) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 {
207 // Check for nonce
208 if nonce.len != nonce_size {
209 return error('bad nonce size')
210 }
211 // Check for ciphertext length, its ahould have length >= tag_size
212 if ciphertext.len < tag_size {
213 return error('bad ciphertext size')
214 }
215 // check for data limit overflow
216 data_length := u64(ciphertext.len) + u64(ad.len) + 32
217 if data_length > aead128_data_limit {
218 return error('decrypt: exceed data limit')
219 }
220 // load nonce
221 n0 := binary.little_endian_u64(nonce[0..8])
222 n1 := binary.little_endian_u64(nonce[8..16])
223
224 // Reinitialize internal state
225 c.State.e0 = aead128_iv
226 c.State.e1 = c.key[0]
227 c.State.e2 = c.key[1]
228 c.State.e3 = n0
229 c.State.e4 = n1
230
231 // scrambled with permutation routine
232 ascon_pnr(mut c.State, .ascon_prnd_12)
233 // xor-ing with the cipher's key
234 c.State.e3 ^= c.key[0]
235 c.State.e4 ^= c.key[1]
236
237 // Associated data processing
238 //
239 aead128_process_ad(mut c.State, ad)
240
241 // As we know, ciphertext length was sum of encrypted mesage length plus tag_size
242 // Lets slicing it
243 cxt_len := ciphertext.len
244 cmsg := ciphertext[0..cxt_len - tag_size]
245 ctag := ciphertext[cxt_len - tag_size..cxt_len]
246
247 mut out := []u8{len: cxt_len - tag_size}
248 aead128_partial_dec(mut out, mut c.State, cmsg)
249 aead128_finalize(mut c.State, c.key[0], c.key[1])
250
251 // tag verification
252 mut tag := []u8{len: tag_size}
253 aead128_write_tag(mut tag, c.State, 0)
254 if subtle.constant_time_compare(ctag, tag) != 1 {
255 // Cleans up previously produces bytes (state)
256 reset_state(mut c.State)
257 unsafe {
258 tag.free()
259 out.free()
260 }
261 return error('Aead128.decrypt: unmatching tag')
262 }
263 return out
264}
265
266// Helpers for Ascon-AEAD128
267//
268
269// aead128_init initializes Ascon-AEAD128 state by provided key and nonce.
270// Its return two's u64 values from deserialized key bytes in little-endian form.
271@[direct_array_access; inline]
272fn aead128_init(mut s State, key []u8, nonce []u8) (u64, u64) {
273 // load key and nonce into state in little-endian form,
274 // The endianness has been switched from big endian to little endian
275 k0 := binary.little_endian_u64(key[0..8])
276 k1 := binary.little_endian_u64(key[8..16])
277
278 n0 := binary.little_endian_u64(nonce[0..8])
279 n1 := binary.little_endian_u64(nonce[8..16])
280
281 // Given a 128-bit 𝐾 and a 128-bit 𝑁, the 320-bit internal
282 // state S is initialized as the concatenation of 𝐼𝑉, 𝐾, and 𝑁:
283 // S ← 𝐼𝑉 || 𝐾 || 𝑁,
284 s.e0 = aead128_iv
285 s.e1 = k0
286 s.e2 = k1
287 s.e3 = n0
288 s.e4 = n1
289
290 // updates State using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12], S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](S)
291 ascon_pnr(mut s, .ascon_prnd_12)
292
293 // Then XORing the secret key 𝐾 into the last 128 bits of internal state:
294 // S ← S ⊕ (0¹⁹² ∥ 𝐾).
295 s.e3 ^= k0
296 s.e4 ^= k1
297
298 return k0, k1
299}
300
301// aead128_process_ad absorbs associated data into Ascon-AEAD128 state.
302@[direct_array_access; inline]
303fn aead128_process_ad(mut s State, ad []u8) {
304 mut ad_length := ad.len
305 mut ad_idx := 0
306 if ad_length > 0 {
307 for ad_length >= aead128_block_size {
308 // Each associated data block 𝐴𝑖 (0 ≤ 𝑖 ≤ 𝑚) is absorbed into the first 128 bits of
309 // state as S[0∶127] ← S[0∶127] ⊕ 𝐴𝑖,
310 block := unsafe { ad[ad_idx..ad_idx + aead128_block_size] }
311 s.e0 ^= binary.little_endian_u64(block[0..8])
312 s.e1 ^= binary.little_endian_u64(block[8..16])
313
314 // Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
315 ascon_pnr(mut s, .ascon_prnd_8)
316 // Updates index
317 ad_length -= aead128_block_size
318 ad_idx += aead128_block_size
319 }
320 // process partial block if it exists
321 if ad_length >= 8 {
322 first_block := unsafe { ad[ad_idx..ad_idx + 8] }
323 s.e0 ^= binary.little_endian_u64(first_block)
324
325 // Is there more bytes to process on?
326 last_block := unsafe { ad[ad_idx + 8..] }
327 s.e1 ^= pad(last_block.len)
328 if last_block.len > 0 {
329 s.e1 ^= u64_from_partial_bytes(last_block)
330 }
331 // update index
332 ad_length -= first_block.len + last_block.len
333 ad_idx += first_block.len + last_block.len
334 } else {
335 last_block := unsafe { ad[ad_idx..] }
336 s.e0 ^= pad(last_block.len)
337 if last_block.len > 0 {
338 s.e0 ^= u64_from_partial_bytes(last_block)
339 }
340 }
341 // Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
342 ascon_pnr(mut s, .ascon_prnd_8)
343 }
344 // The final step of processing associated data is to update the state
345 // with a constant that provides domain separation.
346 s.e4 ^= u64(0x8000_0000_0000_0000)
347}
348
349// aead128_process_msg process (encrypt) the messages msg and asborb it into Ascon-AEAD128 state
350// Its written the result into out buffer and return the number of bytes has been written.
351@[direct_array_access; inline]
352fn aead128_process_msg(mut out []u8, mut s State, msg []u8) int {
353 mut pos := 0
354 mut mlen := msg.len
355 mut midx := 0
356 for mlen >= aead128_block_size {
357 block := unsafe { msg[midx..midx + aead128_block_size] }
358 s.e0 ^= binary.little_endian_u64(block[0..8])
359 s.e1 ^= binary.little_endian_u64(block[8..16])
360 // stores
361 binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
362 binary.little_endian_put_u64(mut out[pos + 8..], s.e1)
363 // apply permutation
364 ascon_pnr(mut s, .ascon_prnd_8)
365
366 // updates index
367 mlen -= aead128_block_size
368 pos += aead128_block_size
369 midx += aead128_block_size
370 }
371 // process partial block if it exists
372 if mlen >= 8 {
373 mut block := unsafe { msg[midx..] }
374 s.e0 ^= load_bytes(block[0..8], 8)
375 s.e1 ^= load_bytes(block[8..], mlen - 8)
376 store_bytes(mut out[pos..], s.e0, 8)
377 store_bytes(mut out[pos + 8..], s.e1, mlen - 8)
378 s.e1 ^= pad(mlen - 8)
379 } else {
380 last_block := unsafe { msg[midx..] }
381 s.e0 ^= load_bytes(last_block, last_block.len)
382 store_bytes(mut out[pos..], s.e0, last_block.len)
383 s.e0 ^= pad(last_block.len)
384 }
385 // how much we have written
386 pos += mlen
387
388 return pos
389}
390
391// aead128_partial_dec partially decrypts the encrypted part of the message in the cmsg,
392// and stored into out buffer.
393// Note: The output buffer should have the same length with cmsg, ie, out.len == cmsg.len
394@[direct_array_access]
395fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) {
396 mut cmsg_len := cmsg.len
397 mut pos := 0
398 // assert out.len == cmsg.len
399 for cmsg_len >= aead128_block_size {
400 block := unsafe { cmsg[pos..pos + aead128_block_size] }
401 c0 := binary.little_endian_u64(block[0..8])
402 c1 := binary.little_endian_u64(block[8..16])
403
404 binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0 ^ c0)
405 binary.little_endian_put_u64(mut out[pos + 8..pos + 16], s.e1 ^ c1)
406
407 s.e0 = c0
408 s.e1 = c1
409
410 ascon_pnr(mut s, .ascon_prnd_8)
411 // updates index
412 pos += aead128_block_size
413 cmsg_len -= aead128_block_size
414 }
415 // partial block
416 if cmsg_len >= 8 {
417 mut first_block := unsafe { cmsg[pos..pos + 8] }
418 c0 := binary.little_endian_u64(first_block)
419 binary.little_endian_put_u64(mut out[pos..pos + 8], c0 ^ s.e0)
420
421 last_block := unsafe { cmsg[pos + 8..] }
422 c1 := load_bytes(last_block, last_block.len)
423 store_bytes(mut out[pos + 8..], c1 ^ s.e1, last_block.len)
424
425 s.e0 = c0
426 s.e1 = clear_bytes(s.e1, last_block.len)
427 s.e1 |= c1
428 s.e1 ^= pad(last_block.len)
429 } else {
430 last_block := unsafe { cmsg[pos..] }
431 c0 := load_bytes(last_block, last_block.len)
432 store_bytes(mut out[pos..], s.e0 ^ c0, last_block.len)
433 s.e0 = clear_bytes(s.e0, last_block.len)
434 s.e0 |= c0
435 s.e0 ^= pad(last_block.len)
436 }
437}
438
439// aead128_finalize does finalization step and generates tag value.
440fn aead128_finalize(mut s State, k0 u64, k1 u64) {
441 // Load the key into state, S ← S ⊕ (0¹²⁸ ∥ 𝐾 ∥ 0⁶⁴),
442 s.e2 ^= k0
443 s.e3 ^= k1
444 // then updated using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12]
445 ascon_pnr(mut s, .ascon_prnd_12)
446
447 // Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state:
448 // 𝑇 ← 𝑆[192∶319] ⊕ 𝐾.
449 s.e3 ^= k0
450 s.e4 ^= k1
451}
452
453// aead128_write_tag writes tag from state into out at loc offset.
454@[direct_array_access; inline]
455fn aead128_write_tag(mut out []u8, s State, loc int) {
456 binary.little_endian_put_u64(mut out[loc..loc + 8], s.e3)
457 binary.little_endian_put_u64(mut out[loc + 8..loc + 16], s.e4)
458}
459