From 95bb58ffbf3e493f175b491875468c18809156b2 Mon Sep 17 00:00:00 2001 From: Oldes Date: Wed, 26 Jun 2019 13:26:58 +0200 Subject: [PATCH] FEAT: new native `chacha20` for cipher stream encryption/decryption --- NOTICE | 3 + src/core/n-crypt.c | 85 ++++++++++++++++++++ src/core/u-chacha20.c | 129 +++++++++++++++++++++++++++++++ src/include/sys-chacha20.h | 61 +++++++++++++++ src/tests/run-tests.r3 | 1 + src/tests/units/chacha20-test.r3 | 48 ++++++++++++ src/tools/file-base.r | 1 + 7 files changed, 328 insertions(+) create mode 100644 src/core/u-chacha20.c create mode 100644 src/include/sys-chacha20.h create mode 100644 src/tests/units/chacha20-test.r3 diff --git a/NOTICE b/NOTICE index cd266c6c9c..75e6a15b4f 100644 --- a/NOTICE +++ b/NOTICE @@ -15,6 +15,9 @@ ECDH: Copyright (c) 2014, Kenneth MacKay - https://github.com/kmackay/micro-ecc All rights reserved. +CHACHA20: +Copyright (c) 2014, insane coder - http://chacha20.insanecoding.org/ + MD5: This software contains code derived from the RSA Data Security Inc. MD5 Message-Digest Algorithm, including various diff --git a/src/core/n-crypt.c b/src/core/n-crypt.c index 59c9d943af..1d712de949 100644 --- a/src/core/n-crypt.c +++ b/src/core/n-crypt.c @@ -33,6 +33,7 @@ #include "sys-aes.h" #include "sys-rsa.h" #include "sys-dh.h" +#include "sys-chacha20.h" /*********************************************************************** ** @@ -667,3 +668,87 @@ typedef struct { } return R_ARG1; } + + +/*********************************************************************** +** +*/ REBNATIVE(chacha20) +/* +// chacha20: native [ +// "Encrypt/decrypt data using ChaCha20 algorithm. Returns stream cipher context handle or encrypted/decrypted data." +// /key "Provided only for the first time to get stream HANDLE!" +// crypt-key [binary!] "Crypt key (16 or 32 bytes)." +// nonce [binary!] "Initialization nonce (8 bytes)." +// count [integer!] "A 32-bit block count parameter" +// /stream +// ctx [handle!] "Stream cipher context." +// data [binary!] "Data to encrypt/decrypt." +// /into +// out [binary!] "Output buffer (NOT YET IMPLEMENTED)" +// ] +***********************************************************************/ +{ + REBOOL ref_key = D_REF(1); + REBVAL *val_crypt_key = D_ARG(2); + REBVAL *val_nonce = D_ARG(3); + REBVAL *val_count = D_ARG(4); + REBOOL ref_stream = D_REF(5); + REBVAL *val_ctx = D_ARG(6); + REBVAL *val_data = D_ARG(7); + REBOOL ref_into = D_REF(8); + + REBVAL *ret = D_RET; + REBSER *ctx_ser; + REBINT len; + + if (ref_key) { + //key defined - setup new context + + len = VAL_LEN(val_crypt_key); + + if (len != 16 && len != 32 && VAL_LEN(val_nonce) != 8) { + return R_NONE; + } + + //making series from POOL so it will be GCed automaticaly + ctx_ser = Make_Series(sizeof(chacha20_ctx), (REBCNT)1, FALSE); + SERIES_TAIL(ctx_ser) = sizeof(chacha20_ctx); + + chacha20_setup( + (chacha20_ctx*)ctx_ser->data, + VAL_BIN_AT(val_crypt_key), + len, + VAL_BIN_AT(val_nonce) + ); + chacha20_counter_set((chacha20_ctx*)ctx_ser->data, VAL_INT64(val_count)); + + SET_HANDLE(ret, ctx_ser, SYM_CHACHA20, HANDLE_SERIES); + // the ctx in the handle is released by GC once the handle is not referenced + + } else if(ref_stream) { + + ctx_ser = VAL_HANDLE_DATA(val_ctx); + + if (VAL_HANDLE_TYPE(val_ctx) != SYM_CHACHA20 || ctx_ser == NULL || SERIES_TAIL(ctx_ser) != sizeof(chacha20_ctx)){ + Trap0(RE_INVALID_HANDLE); + } + + len = VAL_LEN(val_data); + if (len == 0) return R_NONE; + + REBYTE *data = VAL_BIN_AT(val_data); + REBSER *binaryOut = Make_Binary(len); + + chacha20_encrypt( + (chacha20_ctx *)ctx_ser->data, + (const uint8_t*)data, + ( uint8_t*)BIN_DATA(binaryOut), + len + ); + + SET_BINARY(ret, binaryOut); + VAL_TAIL(ret) = len; + + } + return R_RET; +} \ No newline at end of file diff --git a/src/core/u-chacha20.c b/src/core/u-chacha20.c new file mode 100644 index 0000000000..0ce80069d0 --- /dev/null +++ b/src/core/u-chacha20.c @@ -0,0 +1,129 @@ +/* +Copyright (C) 2014 insane coder (http://insanecoding.blogspot.com/, http://chacha20.insanecoding.org/) + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +This implementation is intended to be simple, many optimizations can be performed. +*/ + +#include +#include "sys-chacha20.h" + +void chacha20_setup(chacha20_ctx *ctx, const uint8_t *key, size_t length, uint8_t nonce[8]) +{ + const char *constants = (length == 32) ? "expand 32-byte k" : "expand 16-byte k"; + + ctx->schedule[0] = LE(constants + 0); + ctx->schedule[1] = LE(constants + 4); + ctx->schedule[2] = LE(constants + 8); + ctx->schedule[3] = LE(constants + 12); + ctx->schedule[4] = LE(key + 0); + ctx->schedule[5] = LE(key + 4); + ctx->schedule[6] = LE(key + 8); + ctx->schedule[7] = LE(key + 12); + ctx->schedule[8] = LE(key + 16 % length); + ctx->schedule[9] = LE(key + 20 % length); + ctx->schedule[10] = LE(key + 24 % length); + ctx->schedule[11] = LE(key + 28 % length); + //Surprise! This is really a block cipher in CTR mode + ctx->schedule[12] = 0; //Counter + ctx->schedule[13] = 0; //Counter + ctx->schedule[14] = LE(nonce+0); + ctx->schedule[15] = LE(nonce+4); + + ctx->available = 0; +} + +void chacha20_counter_set(chacha20_ctx *ctx, uint64_t counter) +{ + ctx->schedule[12] = counter & UINT32_C(0xFFFFFFFF); + ctx->schedule[13] = counter >> 32; + ctx->available = 0; +} + +#define QUARTERROUND(x, a, b, c, d) \ + x[a] += x[b]; x[d] = ROTL32(x[d] ^ x[a], 16); \ + x[c] += x[d]; x[b] = ROTL32(x[b] ^ x[c], 12); \ + x[a] += x[b]; x[d] = ROTL32(x[d] ^ x[a], 8); \ + x[c] += x[d]; x[b] = ROTL32(x[b] ^ x[c], 7); + +void chacha20_block(chacha20_ctx *ctx, uint32_t output[16]) +{ + uint32_t *const nonce = ctx->schedule+12; //12 is where the 128 bit counter is + int i = 10; + + memcpy(output, ctx->schedule, sizeof(ctx->schedule)); + + while (i--) + { + QUARTERROUND(output, 0, 4, 8, 12) + QUARTERROUND(output, 1, 5, 9, 13) + QUARTERROUND(output, 2, 6, 10, 14) + QUARTERROUND(output, 3, 7, 11, 15) + QUARTERROUND(output, 0, 5, 10, 15) + QUARTERROUND(output, 1, 6, 11, 12) + QUARTERROUND(output, 2, 7, 8, 13) + QUARTERROUND(output, 3, 4, 9, 14) + } + for (i = 0; i < 16; ++i) + { + uint32_t result = output[i] + ctx->schedule[i]; + FROMLE((uint8_t *)(output+i), result); + } + + /* + Official specs calls for performing a 64 bit increment here, and limit usage to 2^64 blocks. + However, recommendations for CTR mode in various papers recommend including the nonce component for a 128 bit increment. + This implementation will remain compatible with the official up to 2^64 blocks, and past that point, the official is not intended to be used. + This implementation with this change also allows this algorithm to become compatible for a Fortuna-like construct. + */ + if (!++nonce[0] && !++nonce[1] && !++nonce[2]) { ++nonce[3]; } +} + +static inline void chacha20_xor(uint8_t *keystream, const uint8_t **in, uint8_t **out, size_t length) +{ + uint8_t *end_keystream = keystream + length; + do { *(*out)++ = *(*in)++ ^ *keystream++; } while (keystream < end_keystream); +} + +void chacha20_encrypt(chacha20_ctx *ctx, const uint8_t *in, uint8_t *out, size_t length) +{ + if (length) + { + uint8_t *const k = (uint8_t *)ctx->keystream; + + //First, use any buffered keystream from previous calls + if (ctx->available) + { + size_t amount = MIN(length, ctx->available); + chacha20_xor(k + (sizeof(ctx->keystream)-ctx->available), &in, &out, amount); + ctx->available -= amount; + length -= amount; + } + + //Then, handle new blocks + while (length) + { + size_t amount = MIN(length, sizeof(ctx->keystream)); + chacha20_block(ctx, ctx->keystream); + chacha20_xor(k, &in, &out, amount); + length -= amount; + ctx->available = sizeof(ctx->keystream) - amount; + } + } +} + +void chacha20_decrypt(chacha20_ctx *ctx, const uint8_t *in, uint8_t *out, size_t length) +{ + chacha20_encrypt(ctx, in, out, length); +} diff --git a/src/include/sys-chacha20.h b/src/include/sys-chacha20.h new file mode 100644 index 0000000000..6e58be313f --- /dev/null +++ b/src/include/sys-chacha20.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2014 insane coder (http://insanecoding.blogspot.com/, http://chacha20.insanecoding.org/) + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef CHACHA20_SIMPLE_H +#define CHACHA20_SIMPLE_H +#include + +#define ROTL32(v, n) ((v) << (n)) | ((v) >> (32 - (n))) + +#define LE(p) (((uint32_t)((p)[0])) | ((uint32_t)((p)[1]) << 8) | ((uint32_t)((p)[2]) << 16) | ((uint32_t)((p)[3]) << 24)) +#define FROMLE(b, i) (b)[0] = i & 0xFF; (b)[1] = (i >> 8) & 0xFF; (b)[2] = (i >> 16) & 0xFF; (b)[3] = (i >> 24) & 0xFF; + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef struct +{ + uint32_t schedule[16]; + uint32_t keystream[16]; + size_t available; +} chacha20_ctx; + +//Call this to initilize a chacha20_ctx, must be called before all other functions +void chacha20_setup(chacha20_ctx *ctx, const uint8_t *key, size_t length, uint8_t nonce[8]); + +//Call this if you need to process a particular block number +void chacha20_counter_set(chacha20_ctx *ctx, uint64_t counter); + +//Raw keystream for the current block, convert output to uint8_t[] for individual bytes. Counter is incremented upon use +void chacha20_block(chacha20_ctx *ctx, uint32_t output[16]); + +//Encrypt an arbitrary amount of plaintext, call continuously as needed +void chacha20_encrypt(chacha20_ctx *ctx, const uint8_t *in, uint8_t *out, size_t length); + +//Decrypt an arbitrary amount of ciphertext. Actually, for chacha20, decryption is the same function as encryption +void chacha20_decrypt(chacha20_ctx *ctx, const uint8_t *in, uint8_t *out, size_t length); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/tests/run-tests.r3 b/src/tests/run-tests.r3 index eb92bca2ab..5b95276316 100644 --- a/src/tests/run-tests.r3 +++ b/src/tests/run-tests.r3 @@ -20,6 +20,7 @@ dt [ ;- delta time wrap load %units/mezz-crypt-test.r3 wrap load %units/rc4-test.r3 wrap load %units/aes-test.r3 + wrap load %units/chacha20-test.r3 wrap load %units/rsa-test.r3 wrap load %units/dh-test.r3 wrap load %units/port-test.r3 diff --git a/src/tests/units/chacha20-test.r3 b/src/tests/units/chacha20-test.r3 new file mode 100644 index 0000000000..fb23a08a13 --- /dev/null +++ b/src/tests/units/chacha20-test.r3 @@ -0,0 +1,48 @@ +Rebol [ + Title: "Rebol3 ChaCha20 test script" + Author: "Oldes, Peter W A Wood" + File: %chacha20-test.r3 + Tabs: 4 + Needs: [%../quick-test-module.r3] +] + +~~~start-file~~~ "ChaCha20" + +===start-group=== "ChaCha20 test vectors" +foreach [test-id key nonce counter plain cipher] [ + 1 + #{00000000000000000000000000000000} + #{0000000000000000} 0 + #{00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} + #{89670952608364FD00B2F90936F031C8E756E15DBA04B8493D00429259B20F46CC04F111246B6C2CE066BE3BFB32D9AA0FDDFBC12123D4B9E44F34DCA05A103F} + + ;@@ http://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04#appendix-A.2 + 2 + #{0000000000000000000000000000000000000000000000000000000000000000} + #{0000000000000000} 0 + #{00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000} + #{76B8E0ADA0F13D90405D6AE55386BD28BDD219B8A08DED1AA836EFCC8B770DC7DA41597C5157488D7724E03FB8D84A376A43B8F41518A11CC387B669B2EE6586} + + 3 + #{0000000000000000000000000000000000000000000000000000000000000001} + #{0000000000000002} 1 + #{416e79207375626d697373696f6e20746f20746865204945544620696e74656e6465642062792074686520436f6e7472696275746f7220666f72207075626c69636174696f6e20617320616c6c206f722070617274206f6620616e204945544620496e7465726e65742d4472616674206f722052464320616e6420616e792073746174656d656e74206d6164652077697468696e2074686520636f6e74657874206f6620616e204945544620616374697669747920697320636f6e7369646572656420616e20224945544620436f6e747269627574696f6e222e20537563682073746174656d656e747320696e636c756465206f72616c2073746174656d656e747320696e20494554462073657373696f6e732c2061732077656c6c206173207772697474656e20616e6420656c656374726f6e696320636f6d6d756e69636174696f6e73206d61646520617420616e792074696d65206f7220706c6163652c207768696368206172652061646472657373656420746f} + #{a3fbf07df3fa2fde4f376ca23e82737041605d9f4f4f57bd8cff2c1d4b7955ec2a97948bd3722915c8f3d337f7d370050e9e96d647b7c39f56e031ca5eb6250d4042e02785ececfa4b4bb5e8ead0440e20b6e8db09d881a7c6132f420e52795042bdfa7773d8a9051447b3291ce1411c680465552aa6c405b7764d5e87bea85ad00f8449ed8f72d0d662ab052691ca66424bc86d2df80ea41f43abf937d3259dc4b2d0dfb48a6c9139ddd7f76966e928e635553ba76c5c879d7b35d49eb2e62b0871cdac638939e25e8a1e0ef9d5280fa8ca328b351c3c765989cbcf3daa8b6ccc3aaf9f3979c92b3720fc88dc95ed84a1be059c6499b9fda236e7e818b04b0bc39c1e876b193bfe5569753f88128cc08aaa9b63d1a16f80ef2554d7189c411f5869ca52c5b83fa36ff216b9c1d30062bebcfd2dc5bce0911934fda79a86f6e698ced759c3ff9b6477338f3da4f9cd8514ea9982ccafb341b2384dd902f3d1ab7ac61dd29c6f21ba5b862f3730e37cfdc4fd806c22f221} + + 4 + #{1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0} + #{0000000000000002} 42 + #{2754776173206272696c6c69672c20616e642074686520736c6974687920746f7665730a446964206779726520616e642067696d626c6520696e2074686520776162653a0a416c6c206d696d737920776572652074686520626f726f676f7665732c0a416e6420746865206d6f6d65207261746873206f757467726162652e} + #{62e6347f95ed87a45ffae7426f27a1df5fb69110044c0d73118effa95b01e5cf166d3df2d721caf9b21e5fb14c616871fd84c54f9d65b283196c7fe4f60553ebf39c6402c42234e32a356b3e764312a61a5532055716ead6962568f87d3f3f7704c6a8d1bcd1bf4d50d6154b6da731b187b58dfd728afa36757a797ac188d1} +][ + --test-- join "ChaCha20 test " test-id + --assert handle? k1: chacha20/key key nonce counter + --assert cipher = chacha20/stream k1 plain + + --assert handle? k2: chacha20/key key nonce counter + --assert plain = chacha20/stream k2 cipher +] + +===end-group=== + +~~~end-file~~~ \ No newline at end of file diff --git a/src/tools/file-base.r b/src/tools/file-base.r index 17fdc297f2..53210d3b47 100644 --- a/src/tools/file-base.r +++ b/src/tools/file-base.r @@ -106,6 +106,7 @@ core: [ u-bigint.c ;needed for RSA which is needed in TLS protocol (HTTPS) u-bincode.c u-bmp.c + u-chacha20.c u-compress.c u-dh.c u-dialect.c