mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
464 lines
15 KiB
V
464 lines
15 KiB
V
// Copyright ©2025 blackshirt.
|
||
// Use of this source code is governed by an MIT license
|
||
// that can be found in the LICENSE file.
|
||
//
|
||
// This file implements an Authenticated Encryption with Associated Data based on Ascon-AEAD128,
|
||
// AEAD Scheme defined in NIST.SP.800-232 standard.
|
||
module ascon
|
||
|
||
import encoding.binary
|
||
import crypto.internal.subtle
|
||
|
||
// The constants for Ascon-AEAD128
|
||
//
|
||
// key_size is 128-bit size of Ascon-AEAD128 key
|
||
pub const key_size = 16
|
||
// nonce_size is 128-bit size of Ascon-AEAD128 nonce
|
||
pub const nonce_size = 16
|
||
// tag_size is 128-bit size of Ascon-AEAD128 authentication tag
|
||
pub const tag_size = 16
|
||
|
||
// aead128_iv is a precomputed initialization phase values for Ascon-AEAD128
|
||
// See Table 14 of NIST SP 800-232 doc.
|
||
const aead128_iv = u64(0x0000_1000_808c_0001)
|
||
|
||
// aead128_data_limit is a limit amount of data processed during encryption and decryption,
|
||
// including the nonce, shall not exceed 2⁵⁴ bytes for a given key.
|
||
const aead128_data_limit = u64(1) << 54 - 1
|
||
|
||
// aead128_block_size is a number (rate) of input bytes processed per invocation of the underlying of state.
|
||
// Ascon-AEAD128 working with 128-bit rate
|
||
const aead128_block_size = 16
|
||
|
||
// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
|
||
// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
|
||
// was stored within the end of ciphertext.
|
||
// Note: The Ascon-AEAD128 key shall be kept secret,
|
||
pub fn encrypt(key []u8, nonce []u8, ad []u8, msg []u8) ![]u8 {
|
||
// Preliminary check
|
||
if key.len != key_size {
|
||
return error('encrypt: invalid key size')
|
||
}
|
||
if nonce.len != nonce_size {
|
||
return error('encrypt: invalid nonce size')
|
||
}
|
||
// The key shall be updated to a new key once the total amount of input data reaches the limit
|
||
data_length := u64(nonce.len) + u64(msg.len) + u64(ad.len)
|
||
if data_length > aead128_data_limit {
|
||
return error('encrypt: exceed data limit')
|
||
}
|
||
mut s := State{}
|
||
mut out := []u8{len: msg.len + tag_size}
|
||
|
||
// Ascon-AEAD128 comprises four phases:
|
||
// - initialization of the state,
|
||
// - associated data processing,
|
||
// - plaintext processing,
|
||
// - and finalization (includes writing the tag).
|
||
k0, k1 := aead128_init(mut s, key, nonce)
|
||
aead128_process_ad(mut s, ad)
|
||
loc := aead128_process_msg(mut out, mut s, msg)
|
||
aead128_finalize(mut s, k0, k1)
|
||
aead128_write_tag(mut out, s, loc)
|
||
|
||
// clean out the intermediate Ascon state
|
||
reset_state(mut s)
|
||
return out
|
||
}
|
||
|
||
// decrypt decrypts authenticated encrypted messages in ciphertext that encrypted under
|
||
// provided key and nonce with additional data in `ad`.
|
||
// It would check if authentication tag mas matching and return decrypted message
|
||
// if success or error on fails.
|
||
@[direct_array_access]
|
||
pub fn decrypt(key []u8, nonce []u8, ad []u8, ciphertext []u8) ![]u8 {
|
||
// Preliminary check
|
||
if key.len != key_size {
|
||
return error('decrypt: invalid key size')
|
||
}
|
||
if nonce.len != nonce_size {
|
||
return error('decrypt: invalid nonce size')
|
||
}
|
||
if ciphertext.len < tag_size {
|
||
return error('decrypt: invalid ciphertext size')
|
||
}
|
||
data_length := u64(nonce.len) + u64(ciphertext.len) + u64(ad.len)
|
||
if data_length > aead128_data_limit {
|
||
return error('decrypt: exceed data limit')
|
||
}
|
||
mut s := State{}
|
||
// Initialization phase and additional data processing
|
||
k0, k1 := aead128_init(mut s, key, nonce)
|
||
aead128_process_ad(mut s, ad)
|
||
|
||
// Decryption phase, start by slicing the ciphertext
|
||
cmsg := ciphertext[0..ciphertext.len - tag_size]
|
||
stag := ciphertext[ciphertext.len - tag_size..ciphertext.len]
|
||
mut msg := []u8{len: ciphertext.len - tag_size}
|
||
|
||
// Partially decrypt the cmsg and stored into msg buffer
|
||
aead128_partial_dec(mut msg, mut s, cmsg)
|
||
|
||
// Finalizes the state and calc the tag and compares with expected tag.
|
||
// It would return error if the tag was unmatching.
|
||
aead128_finalize(mut s, k0, k1)
|
||
mut ctag := []u8{len: tag_size}
|
||
aead128_write_tag(mut ctag, s, 0)
|
||
if subtle.constant_time_compare(ctag, stag) != 1 {
|
||
// clean up
|
||
unsafe {
|
||
msg.reset()
|
||
ctag.reset()
|
||
}
|
||
reset_state(mut s)
|
||
return error('decrypt: unmatching tag')
|
||
}
|
||
return msg
|
||
}
|
||
|
||
// Aead128 is an opaque provides an implementation of Ascon-AEAD128 from NIST.SP.800-232 standard.
|
||
// Its implements `x.crypto.chacha20poly1305.AEAD` interfaces.
|
||
@[noinit]
|
||
pub struct Aead128 {
|
||
State
|
||
mut:
|
||
// 32-bytes of underlying key
|
||
key [2]u64
|
||
}
|
||
|
||
// new_aead128 creates a new Aead128 instance and initialized with supplied key.
|
||
@[direct_array_access]
|
||
pub fn new_aead128(key []u8) !&Aead128 {
|
||
if key.len != key_size {
|
||
return error('invalid cipher key size')
|
||
}
|
||
k0 := binary.little_endian_u64(key[0..8])
|
||
k1 := binary.little_endian_u64(key[8..16])
|
||
|
||
mut c := &Aead128{}
|
||
// Partially initializes state
|
||
c.State.e0 = aead128_iv
|
||
c.State.e1 = k0
|
||
c.State.e2 = k1
|
||
// stores the key
|
||
c.key[0] = k0
|
||
c.key[1] = k1
|
||
|
||
return c
|
||
}
|
||
|
||
// nonce_size returns the nonce size of Ascon-AEAD128 Aead128.
|
||
pub fn (c &Aead128) nonce_size() int {
|
||
return nonce_size
|
||
}
|
||
|
||
// overhead returns the maximum difference between the lengths of a plaintext and its ciphertext.
|
||
pub fn (c &Aead128) overhead() int {
|
||
return tag_size
|
||
}
|
||
|
||
// encrypt encrypts the message under provided key and nonce and supplied additional data in ad.
|
||
// It returns an authenticated output of Ascon-AEAD128 ciphertext where authentication tag
|
||
// was stored within the end of ciphertext.
|
||
@[direct_array_access]
|
||
pub fn (mut c Aead128) encrypt(msg []u8, nonce []u8, ad []u8) ![]u8 {
|
||
// Check for the nonce
|
||
if nonce.len != nonce_size {
|
||
return error('encrypt: invalid nonce size')
|
||
}
|
||
data_length := u64(msg.len) + u64(ad.len) + 32
|
||
if data_length > aead128_data_limit {
|
||
return error('encrypt: exceed data limit')
|
||
}
|
||
// Initialization phase
|
||
n0 := binary.little_endian_u64(nonce[0..8])
|
||
n1 := binary.little_endian_u64(nonce[8..16])
|
||
// setup state
|
||
c.State.e0 = aead128_iv
|
||
c.State.e1 = c.key[0]
|
||
c.State.e2 = c.key[1]
|
||
c.State.e3 = n0
|
||
c.State.e4 = n1
|
||
|
||
// Update state by permutation
|
||
ascon_pnr(mut c.State, ascon_prnd_12)
|
||
// XOR-ing with the cipher's key
|
||
c.State.e3 ^= c.key[0]
|
||
c.State.e4 ^= c.key[1]
|
||
|
||
// Associated data processing
|
||
aead128_process_ad(mut c.State, ad)
|
||
|
||
// Message processing
|
||
mut dst := []u8{len: msg.len + tag_size}
|
||
n := aead128_process_msg(mut dst, mut c.State, msg)
|
||
|
||
// Finalization and writes out the tag into dst
|
||
aead128_finalize(mut c.State, c.key[0], c.key[1])
|
||
aead128_write_tag(mut dst, c.State, n)
|
||
|
||
return dst
|
||
}
|
||
|
||
// decrypt decrypts the ciphertext and validates authentication tag with
|
||
// provided nonce and additional data ad. It returns error on fails or tag unmatching.
|
||
@[direct_array_access]
|
||
pub fn (mut c Aead128) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 {
|
||
// Check for nonce
|
||
if nonce.len != nonce_size {
|
||
return error('bad nonce size')
|
||
}
|
||
// Check for ciphertext length, its ahould have length >= tag_size
|
||
if ciphertext.len < tag_size {
|
||
return error('bad ciphertext size')
|
||
}
|
||
// check for data limit overflow
|
||
data_length := u64(ciphertext.len) + u64(ad.len) + 32
|
||
if data_length > aead128_data_limit {
|
||
return error('decrypt: exceed data limit')
|
||
}
|
||
// load nonce
|
||
n0 := binary.little_endian_u64(nonce[0..8])
|
||
n1 := binary.little_endian_u64(nonce[8..16])
|
||
|
||
// Reinitialize internal state
|
||
c.State.e0 = aead128_iv
|
||
c.State.e1 = c.key[0]
|
||
c.State.e2 = c.key[1]
|
||
c.State.e3 = n0
|
||
c.State.e4 = n1
|
||
|
||
// scrambled with permutation routine
|
||
ascon_pnr(mut c.State, ascon_prnd_12)
|
||
// xor-ing with the cipher's key
|
||
c.State.e3 ^= c.key[0]
|
||
c.State.e4 ^= c.key[1]
|
||
|
||
// Associated data processing
|
||
//
|
||
aead128_process_ad(mut c.State, ad)
|
||
|
||
// As we know, ciphertext length was sum of encrypted mesage length plus tag_size
|
||
// Lets slicing it
|
||
cxt_len := ciphertext.len
|
||
cmsg := ciphertext[0..cxt_len - tag_size]
|
||
ctag := ciphertext[cxt_len - tag_size..cxt_len]
|
||
|
||
mut out := []u8{len: cxt_len - tag_size}
|
||
aead128_partial_dec(mut out, mut c.State, cmsg)
|
||
aead128_finalize(mut c.State, c.key[0], c.key[1])
|
||
|
||
// tag verification
|
||
mut tag := []u8{len: tag_size}
|
||
aead128_write_tag(mut tag, c.State, 0)
|
||
if subtle.constant_time_compare(ctag, tag) != 1 {
|
||
// Cleans up previously produces bytes (state)
|
||
reset_state(mut c.State)
|
||
unsafe {
|
||
tag.free()
|
||
out.free()
|
||
}
|
||
return error('Aead128.decrypt: unmatching tag')
|
||
}
|
||
return out
|
||
}
|
||
|
||
// Helpers for Ascon-AEAD128
|
||
//
|
||
|
||
// aead128_init initializes Ascon-AEAD128 state by provided key and nonce.
|
||
// Its return two's u64 values from deserialized key bytes in little-endian form.
|
||
@[direct_array_access; inline]
|
||
fn aead128_init(mut s State, key []u8, nonce []u8) (u64, u64) {
|
||
// load key and nonce into state in little-endian form,
|
||
// The endianness has been switched from big endian to little endian
|
||
k0 := binary.little_endian_u64(key[0..8])
|
||
k1 := binary.little_endian_u64(key[8..16])
|
||
|
||
n0 := binary.little_endian_u64(nonce[0..8])
|
||
n1 := binary.little_endian_u64(nonce[8..16])
|
||
|
||
// Given a 128-bit 𝐾 and a 128-bit 𝑁, the 320-bit internal
|
||
// state S is initialized as the concatenation of 𝐼𝑉, 𝐾, and 𝑁:
|
||
// S ← 𝐼𝑉 || 𝐾 || 𝑁,
|
||
s.e0 = aead128_iv
|
||
s.e1 = k0
|
||
s.e2 = k1
|
||
s.e3 = n0
|
||
s.e4 = n1
|
||
|
||
// updates State using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12], S ← 𝐴𝑠𝑐𝑜𝑛-𝑝[12](S)
|
||
ascon_pnr(mut s, ascon_prnd_12)
|
||
|
||
// Then XORing the secret key 𝐾 into the last 128 bits of internal state:
|
||
// S ← S ⊕ (0¹⁹² ∥ 𝐾).
|
||
s.e3 ^= k0
|
||
s.e4 ^= k1
|
||
|
||
return k0, k1
|
||
}
|
||
|
||
// aead128_process_ad absorbs associated data into Ascon-AEAD128 state.
|
||
@[direct_array_access; inline]
|
||
fn aead128_process_ad(mut s State, ad []u8) {
|
||
mut ad_length := ad.len
|
||
mut ad_idx := 0
|
||
if ad_length > 0 {
|
||
for ad_length >= aead128_block_size {
|
||
// Each associated data block 𝐴𝑖 (0 ≤ 𝑖 ≤ 𝑚) is absorbed into the first 128 bits of
|
||
// state as S[0∶127] ← S[0∶127] ⊕ 𝐴𝑖,
|
||
block := unsafe { ad[ad_idx..ad_idx + aead128_block_size] }
|
||
s.e0 ^= binary.little_endian_u64(block[0..8])
|
||
s.e1 ^= binary.little_endian_u64(block[8..16])
|
||
|
||
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
|
||
ascon_pnr(mut s, ascon_prnd_8)
|
||
// Updates index
|
||
ad_length -= aead128_block_size
|
||
ad_idx += aead128_block_size
|
||
}
|
||
// process partial block if it exists
|
||
if ad_length >= 8 {
|
||
first_block := unsafe { ad[ad_idx..ad_idx + 8] }
|
||
s.e0 ^= binary.little_endian_u64(first_block)
|
||
|
||
// Is there more bytes to process on?
|
||
last_block := unsafe { ad[ad_idx + 8..] }
|
||
s.e1 ^= pad(last_block.len)
|
||
if last_block.len > 0 {
|
||
s.e1 ^= u64_from_partial_bytes(last_block)
|
||
}
|
||
// update index
|
||
ad_length -= first_block.len + last_block.len
|
||
ad_idx += first_block.len + last_block.len
|
||
} else {
|
||
last_block := unsafe { ad[ad_idx..] }
|
||
s.e0 ^= pad(last_block.len)
|
||
if last_block.len > 0 {
|
||
s.e0 ^= u64_from_partial_bytes(last_block)
|
||
}
|
||
}
|
||
// Apply permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[8] to the state
|
||
ascon_pnr(mut s, ascon_prnd_8)
|
||
}
|
||
// The final step of processing associated data is to update the state
|
||
// with a constant that provides domain separation.
|
||
s.e4 ^= u64(0x8000_0000_0000_0000)
|
||
}
|
||
|
||
// aead128_process_msg process (encrypt) the messages msg and asborb it into Ascon-AEAD128 state
|
||
// Its written the result into out buffer and return the number of bytes has been written.
|
||
@[direct_array_access; inline]
|
||
fn aead128_process_msg(mut out []u8, mut s State, msg []u8) int {
|
||
mut pos := 0
|
||
mut mlen := msg.len
|
||
mut midx := 0
|
||
for mlen >= aead128_block_size {
|
||
block := unsafe { msg[midx..midx + aead128_block_size] }
|
||
s.e0 ^= binary.little_endian_u64(block[0..8])
|
||
s.e1 ^= binary.little_endian_u64(block[8..16])
|
||
// stores
|
||
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
|
||
binary.little_endian_put_u64(mut out[pos + 8..], s.e1)
|
||
// apply permutation
|
||
ascon_pnr(mut s, ascon_prnd_8)
|
||
|
||
// updates index
|
||
mlen -= aead128_block_size
|
||
pos += aead128_block_size
|
||
midx += aead128_block_size
|
||
}
|
||
// process partial block if it exists
|
||
if mlen >= 8 {
|
||
mut first_block := unsafe { msg[midx..] }
|
||
s.e0 ^= load_bytes(first_block[0..8], 8)
|
||
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0)
|
||
|
||
// Is there are reminder bytes to process on?
|
||
last_block := unsafe { msg[midx + 8..] }
|
||
if last_block.len > 0 {
|
||
// We use `load_bytes` to handle length < 8
|
||
s.e1 ^= load_bytes(last_block, last_block.len)
|
||
store_bytes(mut out[pos + 8..], s.e1, last_block.len)
|
||
s.e1 ^= pad(last_block.len)
|
||
}
|
||
} else {
|
||
last_block := unsafe { msg[midx..] }
|
||
s.e0 ^= load_bytes(last_block, last_block.len)
|
||
store_bytes(mut out[pos..], s.e0, last_block.len)
|
||
s.e0 ^= pad(last_block.len)
|
||
}
|
||
// how much we have written
|
||
pos += mlen
|
||
|
||
return pos
|
||
}
|
||
|
||
// aead128_partial_dec partially decrypts the encrypted part of the message in the cmsg,
|
||
// and stored into out buffer.
|
||
// Note: The output buffer should have the same length with cmsg, ie, out.len == cmsg.len
|
||
@[direct_array_access]
|
||
fn aead128_partial_dec(mut out []u8, mut s State, cmsg []u8) {
|
||
mut cmsg_len := cmsg.len
|
||
mut pos := 0
|
||
// assert out.len == cmsg.len
|
||
for cmsg_len >= aead128_block_size {
|
||
block := unsafe { cmsg[pos..pos + aead128_block_size] }
|
||
c0 := binary.little_endian_u64(block[0..8])
|
||
c1 := binary.little_endian_u64(block[8..16])
|
||
|
||
binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0 ^ c0)
|
||
binary.little_endian_put_u64(mut out[pos + 8..pos + 16], s.e1 ^ c1)
|
||
|
||
s.e0 = c0
|
||
s.e1 = c1
|
||
|
||
ascon_pnr(mut s, ascon_prnd_8)
|
||
// updates index
|
||
pos += aead128_block_size
|
||
cmsg_len -= aead128_block_size
|
||
}
|
||
// partial block
|
||
if cmsg_len >= 8 {
|
||
mut first_block := unsafe { cmsg[pos..pos + 8] }
|
||
c0 := binary.little_endian_u64(first_block)
|
||
binary.little_endian_put_u64(mut out[pos..pos + 8], c0 ^ s.e0)
|
||
|
||
last_block := unsafe { cmsg[pos + 8..] }
|
||
c1 := load_bytes(last_block, last_block.len)
|
||
store_bytes(mut out[pos + 8..], c1 ^ s.e1, last_block.len)
|
||
|
||
s.e0 = c0
|
||
s.e1 = clear_bytes(s.e1, last_block.len)
|
||
s.e1 |= c1
|
||
s.e0 ^= pad(last_block.len)
|
||
} else {
|
||
last_block := unsafe { cmsg[pos..] }
|
||
c0 := load_bytes(last_block, last_block.len)
|
||
store_bytes(mut out[pos..], s.e0 ^ c0, last_block.len)
|
||
s.e0 = clear_bytes(s.e0, last_block.len)
|
||
s.e0 |= c0
|
||
s.e0 ^= pad(last_block.len)
|
||
}
|
||
}
|
||
|
||
// aead128_finalize does finalization step and generates tag value.
|
||
fn aead128_finalize(mut s State, k0 u64, k1 u64) {
|
||
// Load the key into state, S ← S ⊕ (0¹²⁸ ∥ 𝐾 ∥ 0⁶⁴),
|
||
s.e2 ^= k0
|
||
s.e3 ^= k1
|
||
// then updated using the permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12]
|
||
ascon_pnr(mut s, ascon_prnd_12)
|
||
|
||
// Finally, the tag 𝑇 is generated by XORing the key with the last 128 bits of the state:
|
||
// 𝑇 ← 𝑆[192∶319] ⊕ 𝐾.
|
||
s.e3 ^= k0
|
||
s.e4 ^= k1
|
||
}
|
||
|
||
// aead128_write_tag writes tag from state into out at loc offset.
|
||
@[direct_array_access; inline]
|
||
fn aead128_write_tag(mut out []u8, s State, loc int) {
|
||
binary.little_endian_put_u64(mut out[loc..loc + 8], s.e3)
|
||
binary.little_endian_put_u64(mut out[loc + 8..loc + 16], s.e4)
|
||
}
|