From 8450dddabd7c9bc91e6065845c4843625f1c1cc9 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 31 Aug 2024 04:10:22 +0900 Subject: [PATCH] core/crypto/aegis: Implement AEGIS-128L/AEGIS-256 --- core/crypto/aegis/aegis.odin | 401 +++++++++++++++++++++++++++++++++++ examples/all/all_main.odin | 2 + 2 files changed, 403 insertions(+) create mode 100644 core/crypto/aegis/aegis.odin diff --git a/core/crypto/aegis/aegis.odin b/core/crypto/aegis/aegis.odin new file mode 100644 index 00000000000..23180b76c10 --- /dev/null +++ b/core/crypto/aegis/aegis.odin @@ -0,0 +1,401 @@ +/* +package aegis implements the AEGIS-128L and AEGIS-256 Authenticated +Encryption with Additional Data algorithms. + +See: +- https://www.ietf.org/archive/id/draft-irtf-cfrg-aegis-aead-11.txt +*/ +package aegis + +import "core:crypto/aes" +import "core:mem" + +// KEY_SIZE_128L is the AEGIS-128L key size in bytes. +KEY_SIZE_128L :: 16 +// KEY_SIZE_256 is the AEGIS-256 key size in bytes. +KEY_SIZE_256 :: 32 +// IV_SIZE_128L is the AEGIS-128L IV size in bytes. +IV_SIZE_128L :: 16 +// IV_SIZE_256 is the AEGIS-256 IV size in bytes. +IV_SIZE_256 :: 32 +// TAG_SIZE_128 is the AEGIS-128L or AEGIS-256 128-bit tag size in bytes. +TAG_SIZE_128 :: 16 +// TAG_SIZE_256 is the AEGIS-128L or AEGIS-256 256-bit tag size in bytes. +TAG_SIZE_256 :: 32 + +@(private) +_A_MAX :: 0x2000000000000000 // 2^61 bytes +@(private) +_P_MAX :: 0x2000000000000000 // 2^61 bytes + +@(private) +_C0 :: [16]byte{ + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, + 0x15, 0x22, 0x37, 0x59, 0x90, 0xe9, 0x79, 0x62, +} + +@(private) +_C1 :: [16]byte { + 0xdb, 0x3d, 0x18, 0x55, 0x6d, 0xc2, 0x2f, 0xf1, + 0x20, 0x11, 0x31, 0x42, 0x73, 0xb5, 0x28, 0xdd, +} + +// Context is a keyed AEGIS-128L or AEGIS-256 instance. +Context :: struct { + _key: [KEY_SIZE_256]byte, + _key_len: int, + _impl: aes.Implementation, + _is_initialized: bool, +} + +// is_hardware_accelerated returns true iff hardware accelerated AEGIS +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + when ODIN_ARCH == .amd64 { + return aes.is_hardware_accelerated() + } else { + return false + } +} + +// init initializes a Context with the provided key, for AEGIS-128L or AEGIS-256. +init :: proc(ctx: ^Context, key: []byte, impl := aes.DEFAULT_IMPLEMENTATION) { + switch len(key) { + case KEY_SIZE_128L, KEY_SIZE_256: + case: + panic("crypto/aegis: invalid key size") + } + + copy(ctx._key[:], key) + ctx._key_len = len(key) + ctx._impl = impl + if ctx._impl == .Hardware && !is_hardware_accelerated() { + ctx._impl = .Portable + } + ctx._is_initialized = true +} + +// seal encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context and iv, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal :: proc(ctx: ^Context, dst, tag, iv, aad, plaintext: []byte) { + assert(ctx._is_initialized) + + // Init(key, iv) + // + // ct = {} + // + // ad_blocks = Split(ZeroPad(ad, 256), 256) + // for ai in ad_blocks: + // Absorb(ai) + // + // msg_blocks = Split(ZeroPad(msg, 256), 256) + // for xi in msg_blocks: + // ct = ct || Enc(xi) + // + // tag = Finalize(|ad|, |msg|) + // ct = Truncate(ct, |msg|) + // + // return ct and tag +} + +// open authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context, iv, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +@(require_results) +open :: proc(ctx: ^Context, dst, iv, aad, ciphertext, tag: []byte) -> bool { + assert(ctx._is_initialized) + + // Init(key, iv) + // + // msg = {} + // + // ad_blocks = Split(ZeroPad(ad, 256), 256) + // for ai in ad_blocks: + // Absorb(ai) + // + // ct_blocks = Split(ct, 256) + // cn = Tail(ct, |ct| mod 256) + // + // for ci in ct_blocks: + // msg = msg || Dec(ci) + // + // if cn is not empty: + // msg = msg || DecPartial(cn) + // + // expected_tag = Finalize(|ad|, |msg|) + // + // if CtEq(tag, expected_tag) is False: + // erase msg + // return "verification failed" error + // else: + // return msg + + return false +} + +// reset sanitizes the Context. The Context must be +// re-initialized to be used again. +reset :: proc "contextless" (ctx: ^Context) { + mem.zero_explicit(&ctx._key, len(ctx._key)) + ctx._key_len = 0 + ctx._is_initialized = false +} + +// Init-128 +// +// Inputs: +// * key: the encryption key. +// * iv: the public iv. +// +// * {S0, ...S7}: the initial state. +// +// Steps: +// +// S0 = key ^ iv +// S1 = C1 +// S2 = C0 +// S3 = C1 +// S4 = key ^ iv +// S5 = key ^ C0 +// S6 = key ^ C1 +// S7 = key ^ C0 +// +// Repeat(10, Update(iv, key)) + +// Init-256 +// +// Inputs: +// * key: the encryption key. +// * iv: the public iv. +// +// * {S0, ...S5}: the initial state. +// +// k0, k1 = Split(key, 128) +// n0, n1 = Split(nonce, 128) +// +// S0 = k0 ^ n0 +// S1 = k1 ^ n1 +// S2 = C1 +// S3 = C0 +// S4 = k0 ^ C0 +// S5 = k1 ^ C1 +// +// Repeat(4, +// Update(k0) +// Update(k1) +// Update(k0 ^ n0) +// Update(k1 ^ n1) +// ) + +// Update-128 +// +// Inputs: +// * M0: the first 128-bit block to be absorbed. +// * M1: the second 128-bit block to be absorbed. +// +// S'0 = AESRound(S7, S0 ^ M0) +// S'1 = AESRound(S0, S1) +// S'2 = AESRound(S1, S2) +// S'3 = AESRound(S2, S3) +// S'4 = AESRound(S3, S4 ^ M1) +// S'5 = AESRound(S4, S5) +// S'6 = AESRound(S5, S6) +// S'7 = AESRound(S6, S7) +// +// S0 = S'0 +// S1 = S'1 +// S2 = S'2 +// S3 = S'3 +// S4 = S'4 +// S5 = S'5 +// S6 = S'6 +// S7 = S'7 + +// Update-256 +// +// Inputs: +// * msg: the 128-block to be absorbed. +// +// S'0 = AESRound(S5, S0 ^ M) +// S'1 = AESRound(S0, S1) +// S'2 = AESRound(S1, S2) +// S'3 = AESRound(S2, S3) +// S'4 = AESRound(S3, S4) +// S'5 = AESRound(S4, S5) +// +// S0 = S'0 +// S1 = S'1 +// S2 = S'2 +// S3 = S'3 +// S4 = S'4 +// S5 = S'5 + +// Absorb-128 +// +// Inputs: +// * ai: the 256-bit input block. +// +// t0, t1 = Split(ai, 128) +// Update(t0, t1) + +// Absorb-256 +// +// Inputs: +// * ai: the input block. +// +// Update(ai) + +// Enc-128 (xi) +// +// Inputs: +// * xi: the 256-bit input block. +// +// Outputs: +// * ci: the 256-bit encrypted block. +// +// z0 = S6 ^ S1 ^ (S2 & S3) +// z1 = S2 ^ S5 ^ (S6 & S7) +// +// t0, t1 = Split(xi, 128) +// out0 = t0 ^ z0 +// out1 = t1 ^ z1 +// +// Update(t0, t1) +// ci = out0 || out1 +// +// return ci + +// Enc-256 +// +// Inputs: +// * xi: the 128-bit input block. +// +// Outputs: +// * ci: the 128-bit encrypted input block. +// +// z = S1 ^ S4 ^ S5 ^ (S2 & S3) +// +// Update(xi) +// +// ci = xi ^ z + +// Dec-128 +// +// Inputs: +// * ci: the 256-bit encrypted block. +// +// Outputs: +// * xi: the 256-bit decrypted block. +// +// z0 = S6 ^ S1 ^ (S2 & S3) +// z1 = S2 ^ S5 ^ (S6 & S7) +// +// t0, t1 = Split(ci, 128) +// out0 = t0 ^ z0 +// out1 = t1 ^ z1 +// +// Update(out0, out1) +// xi = out0 || out1 +// +// return xi + +// Dec-256 +// +// Inputs: +// * ci: the 128-bit encrypted input block. +// +// Outputs: +// * xi: the decrypted block. +// +// z = S1 ^ S4 ^ S5 ^ (S2 & S3) +// +// xi = ci ^ z +// +// Update(xi) +// +// return xi + +// DecPartial-128 +// +// Inputs: +// * cn: the encrypted input. +// +// Outputs: +// * xn: the decryption of cn. +// +// z0 = S6 ^ S1 ^ (S2 & S3) +// z1 = S2 ^ S5 ^ (S6 & S7) +// +// t0, t1 = Split(ZeroPad(cn, 256), 128) +// out0 = t0 ^ z0 +// out1 = t1 ^ z1 +// +// xn = Truncate(out0 || out1, |cn|) +// +// v0, v1 = Split(ZeroPad(xn, 256), 128) +// Update(v0, v1) +// +// return xn + +// DecPartial-256 +// +// Inputs: +// * cn: the encrypted input. +// +// Outputs: +// * xn: the decryption of cn. +// +// z = S1 ^ S4 ^ S5 ^ (S2 & S3) +// +// t = ZeroPad(cn, 128) +// out = t ^ z +// +// xn = Truncate(out, |cn|) +// +// v = ZeroPad(xn, 128) +// Update(v) +// +// return xn + +// Finalize-128 +// +// Inputs: +// * ad_len_bits: the length of the associated data in bits. +// * msg_len_bits: the length of the message in bits. +// +// Outputs: +// * tag: the authentication tag. +// +// t = S2 ^ (LE64(ad_len_bits) || LE64(msg_len_bits)) +// +// Repeat(7, Update(t, t)) +// +// if tag_length == 16: # 128 bits +// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5 ^ S6 +// else: # 256 bits +// tag = (S0 ^ S1 ^ S2 ^ S3) || (S4 ^ S5 ^ S6 ^ S7) +// +// return tag + +// Finalize-128 +// +// Inputs: +// * ad_len_bits: the length of the associated data in bits. +// * msg_len_bits: the length of the message in bits. +// +// Outputs: +// * tag: the authentication tag. +// +// t = S3 ^ (LE64(ad_len_bits) || LE64(msg_len_bits)) +// +// Repeat(7, Update(t)) +// +// if tag_length == 16: # 128 bits +// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5 +// else: # 256 bits +// tag = (S0 ^ S1 ^ S2) || (S3 ^ S4 ^ S5) \ No newline at end of file diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 482958a5f16..c540dbb3198 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -26,6 +26,7 @@ import topological_sort "core:container/topological_sort" import crypto "core:crypto" import aead "core:crypto/aead" +import aegis "core:crypto/aegis" import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" @@ -169,6 +170,7 @@ _ :: topological_sort _ :: crypto _ :: crypto_hash _ :: aead +_ :: aegis _ :: aes _ :: blake2b _ :: blake2s