From 9ec9c446a7ded105d1bb615e8abb7f066c1ea4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Sat, 11 Sep 2021 23:35:31 -0300 Subject: [PATCH] Initial commit. No authentication yet. --- .gitignore | 4 ++ Makefile | 12 ++++ chacha.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++ chacha.h | 20 +++++++ 4 files changed, 198 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 chacha.c create mode 100644 chacha.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c51d53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.a +*.so +test-* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2688502 --- /dev/null +++ b/Makefile @@ -0,0 +1,12 @@ +CFLAGS := ${CFLAGS} -Wall -Wextra + +all: libchacha.a + +libchacha.a: chacha.o + $(AR) r $@ $^ + +test: test-chacha + ./test-chacha + +test-chacha: chacha.c + $(CC) $(CFLAGS) -DTEST $< -o $@ $(LDFLAGS) diff --git a/chacha.c b/chacha.c new file mode 100644 index 0000000..8429be1 --- /dev/null +++ b/chacha.c @@ -0,0 +1,162 @@ +/* + * Implementation of ChaCha20 with some added flexibility to allow + * other round sizes. + * + * Implemented based on RFC 8439 and its test vectors. + * + */ + +#include /* assert() */ +#include /* htole32(), le32toh() */ +#include /* CHAR_BIT */ +#include /* uint8_t, uint32_t */ +#include /* memcpy() */ + +#include "chacha.h" + +struct chacha_state { + uint32_t v[16]; +}; + +struct chacha_serial_state { + uint8_t b[16*4]; +}; + +/* quarter round */ +#define ROT_L(x,n) do{ x = (x << n) | (x >> (sizeof x * CHAR_BIT - n)); }while(0) +#define QROUND(a,b,c,d) \ + do{ \ + a += b; d ^= a; ROT_L(d, 16); \ + c += d; b ^= c; ROT_L(b, 12); \ + a += b; d ^= a; ROT_L(d, 8); \ + c += d; b ^= c; ROT_L(b, 7); \ + }while(0) + +/* chacha inner block; ChaCha20 uses 10 of these */ +static inline void chacha_innerblock(struct chacha_state *s) +{ + /* implement the round without loops to avoid branching */ + #define RUN_QROUND(state, i1, i2, i3, i4) QROUND(state->v[i1], state->v[i2], state->v[i3], state->v[i4]) + RUN_QROUND(s, 0, 4, 8, 12); + RUN_QROUND(s, 1, 5, 9, 13); + RUN_QROUND(s, 2, 6, 10, 14); + RUN_QROUND(s, 3, 7, 11, 15); + RUN_QROUND(s, 0, 5, 10, 15); + RUN_QROUND(s, 1, 6, 11, 12); + RUN_QROUND(s, 2, 7, 8, 13); + RUN_QROUND(s, 3, 4, 9, 14); + #undef RUN_QROUND +} + +static inline void chacha_rounds_withsum(struct chacha_state *s, unsigned rounds) +{ + assert(rounds % 2 == 0); + rounds /= 2; + + struct chacha_state is = *s; + + for (unsigned i=0; i < rounds; i++) chacha_innerblock(s); + for (unsigned i=0; i<16; i++) s->v[i] += is.v[i]; +} + +static inline void chacha_serialize(const struct chacha_state *s, struct chacha_serial_state *dst) +{ + for (unsigned i=0; i<16; i++) { + uint32_t t = htole32(s->v[i]); + memcpy(dst->b + i*4, &t, sizeof t); + } +} + +static inline void chacha_init_state(struct chacha_state *s, const struct chacha_encryption_params *p) +{ + /* section 2.3 */ + s->v[0] = 0x61707865; + s->v[1] = 0x3320646e; + s->v[2] = 0x79622d32; + s->v[3] = 0x6b206574; + + s->v[12] = p->counter; + + uint32_t t; + for (unsigned i=0; i<8; i++) { + memcpy(&t, p->key + i*4, sizeof t); + s->v[i+4] = le32toh(t); + } + for (unsigned i=0; i<3; i++) { + memcpy(&t, p->nonce + i*4, sizeof t); + s->v[i+13] = le32toh(t); + } +} + +static inline void chacha_encrypt(const struct chacha_encryption_params *params, void *data, size_t data_len, unsigned rounds) +{ + struct chacha_encryption_params params_c = *params; + struct chacha_state s; + struct chacha_serial_state ss; + unsigned char *datap = data; + + size_t inter_len = data_len / 64; + for (size_t i=0; i <= inter_len; i++) { + unsigned xor_len; + + chacha_init_state(&s, ¶ms_c); + params_c.counter++; + + chacha_rounds_withsum(&s, rounds); + chacha_serialize(&s, &ss); + + if (i!=inter_len) xor_len = 64; + else xor_len = data_len % 64; + for (unsigned j=0; j < xor_len; j++) datap[i*64+j] ^= ss.b[j]; + } +} + +void chacha20_encrypt_noauth(const struct chacha_encryption_params *params, void *data, size_t data_len) +{ + chacha_encrypt(params, data, data_len, 20); +} + +#ifdef TEST +#include +static void print_block(struct chacha_state *s) +{ + for (int i = 0; i < 4; i++) printf("%08x %08x %08x %08x\n", s->v[i*4], s->v[i*4+1], s->v[i*4+2], s->v[i*4+3]); +} + +int main() +{ + /* test quarter round - section 2.1.1 */ + uint32_t e=0x11111111, f=0x01020304, g=0x9b8d6f43, h=0x01234567; + QROUND(e,f,g,h); + assert(e == 0xea2a92f4); + assert(f == 0xcb1cf8ce); + assert(g == 0x4581472e); + assert(h == 0x5881c4bb); + + /* test inner block (indirectly) - section 2.3.2 */ + struct chacha_state initial1 = {.v = {0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000, 0x4a000000, 0x00000000}}, initial2 = initial1; + struct chacha_state after_innerblock = {.v = {0x837778ab, 0xe238d763, 0xa67ae21e, 0x5950bb2f, 0xc4f2d0c7, 0xfc62bb2f, 0x8fa018fc, 0x3f5ec7b7, 0x335271c2, 0xf29489f3, 0xeabda8fc, 0x82e46ebd, 0xd19c12b4, 0xb04e16de, 0x9e83d0cb, 0x4e3c50a2}}; + for (int i=0; i<10; i++) chacha_innerblock(&initial1); + assert(memcmp(&initial1, &after_innerblock, sizeof initial1) == 0); + /* test full ChaCha20 operation - section 2.3.2 */ + chacha_rounds_withsum(&initial2, 20); + struct chacha_state after_chacha20block = {.v = {0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2}}; + assert(memcmp(&initial2, &after_chacha20block, sizeof initial2) == 0); + /* test serialization code - section 2.3.2 */ + struct chacha_serial_state ss, ss_ref = {.b = {0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71, 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4, 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9, 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8, 0xa2, 0x50, 0x3c, 0x4e}}; + chacha_serialize(&initial2, &ss); + assert(memcmp(&ss, &ss_ref, sizeof ss) == 0); + + /* test full encryption - 2.4.2 */ + struct chacha_encryption_params enc = { + .key = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}, + .nonce = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00}, + .counter = 1, + }; + char text[] = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + uint8_t final_text[] = {0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81, 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b, 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57, 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8, 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e, 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36, 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42, 0x87, 0x4d}; + size_t text_len = strlen(text); + chacha_encrypt(&enc, text, text_len, 20); + assert(memcmp(text, final_text, text_len) == 0); +} +#endif diff --git a/chacha.h b/chacha.h new file mode 100644 index 0000000..7ef76a9 --- /dev/null +++ b/chacha.h @@ -0,0 +1,20 @@ +#ifndef E_CHACHA20_H +#define E_CHACHA20_H + +#include +#include + +#define CHACHA20_KEY_LEN 32 +#define CHACHA20_NONCE_LEN 12 + +struct chacha_encryption_params { + uint8_t key[CHACHA20_KEY_LEN]; + uint8_t nonce[CHACHA20_NONCE_LEN]; + uint32_t counter; +}; + +/* encrypt buffer data of size data_len in place, using parameters defined in params + * WARNING: doesn't perform authentication, shouldn't be used in most situations */ +void chacha20_encrypt_noauth(const struct chacha_encryption_params *params, void *data, size_t data_len); + +#endif