Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2024 blackshirt.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
//
|
||||
// AEAD_CHACHA20_POLY1305 is an authenticated encryption with additional data algorithm.
|
||||
// The inputs to AEAD_CHACHA20_POLY1305 are:
|
||||
// A 256-bit key
|
||||
// A 64-bit nonce, 96-bit (or bigger 192 bit nonce) -- different for each invocation with the same key
|
||||
// An arbitrary length plaintext
|
||||
// Arbitrary length additional authenticated data (AAD)
|
||||
module chacha20poly1305
|
||||
|
||||
import crypto.cipher
|
||||
import encoding.binary
|
||||
import crypto.internal.subtle
|
||||
import x.crypto.chacha20
|
||||
import x.crypto.poly1305
|
||||
|
||||
// key_size is the size of key (in bytes) which the Chacha20Poly1305 AEAD accepts.
|
||||
pub const key_size = 32
|
||||
|
||||
// orig_nonce_size is the size (in bytes) of nonce of the original (DJ Bernstein) variant
|
||||
// which the Chacha20Poly1305 AEAD accepts.
|
||||
pub const orig_nonce_size = 8
|
||||
// nonce_size is the size of the standard nonce (in bytes) which the Chacha20Poly1305 AEAD accepts.
|
||||
pub const nonce_size = 12
|
||||
// nonce_size is the size of the extended nonce (in bytes) which the Chacha20Poly1305 AEAD accepts.
|
||||
pub const x_nonce_size = 24
|
||||
|
||||
// tag_size is the size of the message authenticated code (in bytes) produced by Chacha20Poly1305 AEAD.
|
||||
pub const tag_size = 16
|
||||
|
||||
// encrypt does one-shot encryption of given plaintext with associated key, nonce and additional data.
|
||||
// It return ciphertext output and authenticated tag appended into it.
|
||||
pub fn encrypt(plaintext []u8, key []u8, nonce []u8, ad []u8, opt chacha20.Options) ![]u8 {
|
||||
mut c := new(key, nonce.len, opt)!
|
||||
return c.encrypt(plaintext, nonce, ad)!
|
||||
}
|
||||
|
||||
// decrypt does one-shot decryption of given ciphertext with associated key, nonce and additional data.
|
||||
// It return plaintext output and verify if resulting tag is a valid message authenticated code (mac)
|
||||
// for given message, key and additional data.
|
||||
pub fn decrypt(ciphertext []u8, key []u8, nonce []u8, ad []u8, opt chacha20.Options) ![]u8 {
|
||||
mut c := new(key, nonce.len, opt)!
|
||||
return c.decrypt(ciphertext, nonce, ad)!
|
||||
}
|
||||
|
||||
// Chacha20Poly1305 represents AEAD algorithm backed by `x.crypto.chacha20` and `x.crypto.poly1305`.
|
||||
@[noinit]
|
||||
struct Chacha20Poly1305 {
|
||||
mut:
|
||||
key []u8 = []u8{len: key_size}
|
||||
nsize int
|
||||
opt chacha20.Options // for XChaCha20 construct
|
||||
}
|
||||
|
||||
// new creates a new Chacha20Poly1305 AEAD instance with given 32 bytes of key
|
||||
// and the nonce size in nsize. The nsize should be 8, 12 or 24 length, otherwise it would return error.
|
||||
pub fn new(key []u8, nsize int, opt chacha20.Options) !&cipher.AEAD {
|
||||
if key.len != key_size {
|
||||
return error('chacha20poly1305: bad key size')
|
||||
}
|
||||
if nsize != orig_nonce_size && nsize != nonce_size && nsize != x_nonce_size {
|
||||
return error('chacha20poly1305: bad nonce size supplied, its should 8, 12 or 24')
|
||||
}
|
||||
return &Chacha20Poly1305{
|
||||
key: key
|
||||
nsize: nsize
|
||||
opt: opt
|
||||
}
|
||||
}
|
||||
|
||||
// nonce_size returns the size of underlying nonce (in bytes) of AEAD algorithm.
|
||||
pub fn (c Chacha20Poly1305) nonce_size() int {
|
||||
return c.nsize
|
||||
}
|
||||
|
||||
// overhead returns maximum difference between the lengths of a plaintext to be encrypted and
|
||||
// ciphertext's output. In the context of Chacha20Poly1305, `.overhead() == .tag_size`.
|
||||
pub fn (c Chacha20Poly1305) overhead() int {
|
||||
return tag_size
|
||||
}
|
||||
|
||||
// encrypt encrypts plaintext, along with nonce and additional data and generates
|
||||
// authenticated tag appended into ciphertext's output.
|
||||
pub fn (c Chacha20Poly1305) encrypt(plaintext []u8, nonce []u8, ad []u8) ![]u8 {
|
||||
// makes sure if the nonce length is matching with internal nonce size
|
||||
if nonce.len != c.nonce_size() {
|
||||
return error('chacha20poly1305: unmatching nonce size')
|
||||
}
|
||||
// check if the plaintext length doesn't exceed the amount of limit.
|
||||
// its comes from the internal of chacha20 mechanism, where the counter are u32
|
||||
// with the facts of chacha20 operates on 64 bytes block, we can measure the amount
|
||||
// of encrypted data possible in a single invocation, ie.,
|
||||
// amount = (2^32-1)*64 = 274,877,906,880 bytes, or nearly 256 GB
|
||||
if u64(plaintext.len) > (u64(1) << 38) - 64 {
|
||||
panic('chacha20poly1305: plaintext too large')
|
||||
}
|
||||
if ad.len > max_u64 {
|
||||
return error('chacha20poly1305: something bad in your additional data')
|
||||
}
|
||||
return c.generic_crypt(plaintext, nonce, ad, .encrypt)!
|
||||
}
|
||||
|
||||
// decrypt decrypts ciphertext along with provided nonce and additional data.
|
||||
// Decryption is similar with the encryption process with slight differences in:
|
||||
// The roles of ciphertext and plaintext are reversed, so the ChaCha20 encryption
|
||||
// function is applied to the ciphertext, producing the plaintext.
|
||||
// The Poly1305 function is still run on the AAD and the ciphertext, not the plaintext.
|
||||
// The calculated mac is bitwise compared to the received mac.
|
||||
// The message is authenticated if and only if the tags match, return error if failed to verify.
|
||||
pub fn (c Chacha20Poly1305) decrypt(ciphertext []u8, nonce []u8, ad []u8) ![]u8 {
|
||||
// Preliminary check
|
||||
if ciphertext.len < tag_size {
|
||||
return error('chacha20poly1305: ciphertext length does not meet minimum required length')
|
||||
}
|
||||
if nonce.len != c.nonce_size() {
|
||||
return error('chacha20poly1305: unmatching nonce size')
|
||||
}
|
||||
// ciphertext max = plaintext max length + tag length
|
||||
// ie, (2^32-1)*64 + overhead = (u64(1) << 38) - 64 + 16 = 274,877,906,896 octets.
|
||||
if u64(ciphertext.len) > (u64(1) << 38) - 48 {
|
||||
return error('chacha20poly1305: ciphertext too large')
|
||||
}
|
||||
return c.generic_crypt(ciphertext, nonce, ad, .decrypt)!
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
// generic_crypt direction
|
||||
enum Mode {
|
||||
encrypt = 0
|
||||
decrypt = 1
|
||||
}
|
||||
|
||||
// generic_crypt does generic encryption or decryption based on the mode flag was passed.
|
||||
// See AEAD Construction at https://datatracker.ietf.org/doc/html/rfc8439#section-2.8
|
||||
@[direct_array_access; inline]
|
||||
fn (c Chacha20Poly1305) generic_crypt(msg []u8, nonce []u8, ad []u8, mode Mode) ![]u8 {
|
||||
// Setup some values
|
||||
mut src := unsafe { msg }
|
||||
mut mac := []u8{} // used in decryption
|
||||
if mode == .decrypt {
|
||||
src = unsafe { msg[0..msg.len - c.overhead()] }
|
||||
mac = unsafe { msg[msg.len - c.overhead()..] }
|
||||
}
|
||||
|
||||
// generates 32-bytes of one-time key for later poly1305 operation
|
||||
mut otkey := []u8{len: key_size}
|
||||
mut s := chacha20.new_cipher(c.key, nonce, c.opt)!
|
||||
s.encrypt(mut otkey, otkey)!
|
||||
|
||||
// destination buffer, with overhead spaces for generated tag without reallocating
|
||||
mut dst := []u8{len: src.len, cap: src.len + c.overhead()}
|
||||
|
||||
// Next, the ChaCha20 encryption function is called to encrypt (decrypt) message input,
|
||||
// using the same key and nonce, and with the initial counter set to 1.
|
||||
s.set_counter(1)
|
||||
s.encrypt(mut dst, src)!
|
||||
|
||||
// Finally, the Poly1305 function is called with the Poly1305 key calculated above
|
||||
// to build message authentication code (tag).
|
||||
// length of constructed message
|
||||
cm_length := if mode == .encrypt {
|
||||
length_constructed_msg(ad, dst)
|
||||
} else {
|
||||
length_constructed_msg(ad, src)
|
||||
}
|
||||
mut constructed_msg := []u8{len: cm_length}
|
||||
if mode == .encrypt {
|
||||
construct_msg(mut constructed_msg, ad, dst)
|
||||
} else {
|
||||
construct_msg(mut constructed_msg, ad, src)
|
||||
}
|
||||
mut tag := []u8{len: tag_size}
|
||||
mut po := poly1305.new(otkey)!
|
||||
po.update(constructed_msg)
|
||||
po.finish(mut tag)
|
||||
|
||||
// If this a decryption mode, lets verify whether this calculated tag was matching
|
||||
// with the supplied mac, otherwise return error on fails and free allocated resources.
|
||||
if mode == .decrypt {
|
||||
if subtle.constant_time_compare(mac, tag) != 1 {
|
||||
// free allocated resource
|
||||
unsafe {
|
||||
s.free()
|
||||
tag.free()
|
||||
otkey.free()
|
||||
dst.free()
|
||||
constructed_msg.free()
|
||||
}
|
||||
return error('chacha20poly1305: unmatching tag')
|
||||
} else {
|
||||
// return the decrypted message (plaintext) when the tag was matching
|
||||
return dst
|
||||
}
|
||||
}
|
||||
// In the encryption mode, appends the tag into end of destination buffer
|
||||
dst << tag
|
||||
return dst
|
||||
}
|
||||
|
||||
// pad x to 16 bytes block
|
||||
@[direct_array_access; inline]
|
||||
fn pad_to_16(x []u8) []u8 {
|
||||
if x.len % 16 == 0 {
|
||||
return x
|
||||
}
|
||||
mut out := []u8{len: x.len + (16 - x.len % 16)}
|
||||
_ := copy(mut out, x)
|
||||
return out
|
||||
}
|
||||
|
||||
// The length of padded x
|
||||
@[inline]
|
||||
fn length_pad_to_16(x []u8) int {
|
||||
if x.len % 16 == 0 {
|
||||
return x.len
|
||||
}
|
||||
//
|
||||
return x.len + (16 - x.len % 16)
|
||||
}
|
||||
|
||||
// The length of constructed message
|
||||
@[inline]
|
||||
fn length_constructed_msg(ad []u8, bytes []u8) int {
|
||||
mut n := 0
|
||||
n += length_pad_to_16(ad)
|
||||
n += length_pad_to_16(bytes)
|
||||
n += 16 // 2 * 8 bytes
|
||||
return n
|
||||
}
|
||||
|
||||
// construct_msg builds a message for later usage and stored into out.
|
||||
// The last step on the AEAD Construction on the how the message was constructed.
|
||||
// See the details on the [2.8](https://datatracker.ietf.org/doc/html/rfc8439#section-2.8)
|
||||
// The message constructed as a concatenation of the following:
|
||||
// * padded to multiple of 16 bytes block of the additional data bytes
|
||||
// * padded to multiple of 16 bytes block of the ciphertext (or plaintext) bytes
|
||||
// * The length of the additional data in octets (as a 64-bit little-endian integer).
|
||||
// * The length of the ciphertext (or plaintext) in octets (as a 64-bit little-endian integer).
|
||||
// Assumed the output buffer length was correctly initialized.
|
||||
@[direct_array_access; inline]
|
||||
fn construct_msg(mut out []u8, ad []u8, bytes []u8) {
|
||||
n0 := copy(mut out, pad_to_16(ad))
|
||||
n1 := copy(mut out[n0..], pad_to_16(bytes))
|
||||
binary.little_endian_put_u64(mut out[n0 + n1..], u64(ad.len))
|
||||
binary.little_endian_put_u64(mut out[n0 + n1 + 8..], u64(bytes.len))
|
||||
}
|
||||
Reference in new issue
Block a user