diff --git a/sys/crypto/Makefile b/sys/crypto/Makefile index 48422e909a..883d8c0aed 100644 --- a/sys/crypto/Makefile +++ b/sys/crypto/Makefile @@ -1 +1,7 @@ +ifeq (, ${RIOT_CHACHA_PRNG_DEFAULT}) + RIOT_CHACHA_PRNG_DEFAULT := $(shell head -c 64 /dev/urandom | hexdump -e '"0x%4xull,"') +endif + +CFLAGS += -DRIOT_CHACHA_PRNG_DEFAULT="${RIOT_CHACHA_PRNG_DEFAULT}" + include $(RIOTBASE)/Makefile.base diff --git a/sys/crypto/chacha.c b/sys/crypto/chacha.c new file mode 100644 index 0000000000..5697cd64d0 --- /dev/null +++ b/sys/crypto/chacha.c @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008 D. J. Bernstein (dedicated to the public domain) + * Copyright (C) 2015 René Kijewski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Please notice: + * - This implementation of the ChaCha stream cipher is very stripped down. + * - It assumes a little-endian system. + * - It is implemented for little code and data size, but will likely be + * slower than the refenrence implementation. Optimized implementation will + * out-perform the code even more. + */ + +#include "crypto/chacha.h" +#include "byteorder.h" + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +# error "This code is implementented in a way that it will only work for little-endian systems!" +#endif + +#include + +static void _r(uint32_t *d, uint32_t *a, const uint32_t *b, unsigned c) +{ + *a += *b; + uint32_t tmp = *a ^ *d; + *d = (tmp << c) | (tmp >> (32 - c)); +} + +static void _doubleround(void *output_, const uint32_t input[16], uint8_t rounds) +{ + uint32_t *output = (uint32_t *) output_; + memcpy(output, input, 64); + + rounds *= 4; + for (unsigned i = 0; i < rounds; ++i) { + uint32_t *a = &output[((i + ((i & 4) ? 0 : 0)) & 3) + (4 * 0)]; + uint32_t *b = &output[((i + ((i & 4) ? 1 : 0)) & 3) + (4 * 1)]; + uint32_t *c = &output[((i + ((i & 4) ? 2 : 0)) & 3) + (4 * 2)]; + uint32_t *d = &output[((i + ((i & 4) ? 3 : 0)) & 3) + (4 * 3)]; + + _r(d, a, b, 16); + _r(b, c, d, 12); + _r(d, a, b, 8); + _r(b, c, d, 7); + } + + for (unsigned i = 0; i < 16; ++i) { + output[i] += input[i]; + } +} + +int chacha_init(chacha_ctx *ctx, + unsigned rounds, + const uint8_t *key, uint32_t keylen, + const uint8_t nonce[8]) +{ + if (keylen == 32) { + memcpy(ctx->state + 0, "expand 32-byte k", 16); + memcpy(ctx->state + 4, key, 32); + } + else if (keylen == 16) { + memcpy(ctx->state + 0, "expand 16-byte k", 16); + memcpy(ctx->state + 4, key, 16); + memcpy(ctx->state + 8, key, 16); + } + else { + return -1; + } + + if ((rounds == 20) || (rounds == 12) || (rounds == 8)) { + ctx->rounds = rounds; + } + else { + return -1; + } + + memset(ctx->state + 12, 0, 8); + memcpy(ctx->state + 14, nonce, 8); + + return 0; +} + +void chacha_keystream_bytes(chacha_ctx *ctx, void *x) +{ + _doubleround(x, ctx->state, ctx->rounds); + + ++ctx->state[12]; + if (ctx->state[12] == 0) { + ++ctx->state[13]; + } +} + +void chacha_encrypt_bytes(chacha_ctx *ctx, const uint8_t *m, uint8_t *c) +{ + uint8_t x[64]; + chacha_keystream_bytes(ctx, x); + for (unsigned i = 0 ; i < 64; ++i) { + c[i] = m[i] ^ x[i]; + } +} diff --git a/sys/crypto/chacha_prng.c b/sys/crypto/chacha_prng.c new file mode 100644 index 0000000000..5cc8facc26 --- /dev/null +++ b/sys/crypto/chacha_prng.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 René Kijewski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Please notice: + * - I am not a cryptographer. + * - You should not use this code to run a nuclear power plant without a proper review. + */ + +#include "crypto/chacha.h" +#include "mutex.h" + +#include + +static chacha_ctx _chacha_prng_ctx = { + .state = { RIOT_CHACHA_PRNG_DEFAULT }, + .rounds = 8, +}; +static uint32_t _chacha_prng_data[64]; +static signed _chacha_prng_pos = 0; +static mutex_t _chacha_prng_mutex = MUTEX_INIT; + +void chacha_prng_seed(const void *data, size_t bytes) +{ + mutex_lock(&_chacha_prng_mutex); + + memcpy(_chacha_prng_ctx.state, data, bytes); + _chacha_prng_pos = 0; + + mutex_unlock(&_chacha_prng_mutex); +} + +uint32_t chacha_prng_next(void) +{ + mutex_lock(&_chacha_prng_mutex); + + if (--_chacha_prng_pos < 0) { + _chacha_prng_pos = 15; + chacha_keystream_bytes(&_chacha_prng_ctx, _chacha_prng_data); + } + uint32_t result = _chacha_prng_data[_chacha_prng_pos]; + + mutex_unlock(&_chacha_prng_mutex); + return result; +} diff --git a/sys/include/crypto/chacha.h b/sys/include/crypto/chacha.h new file mode 100644 index 0000000000..5dd18f5632 --- /dev/null +++ b/sys/include/crypto/chacha.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2008 D. J. Bernstein (dedicated to the public domain) + * Copyright (C) 2015 René Kijewski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * @ingroup sys_crypto + * @{ + * + * @file + * @brief ChaCha stream cipher + * + * @author René Kijewski + */ + +#ifndef CRYPTO_CHACHA_H_ +#define CRYPTO_CHACHA_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief A ChaCha cipher stream context. + * @details Initialize with chacha_init(). + */ +typedef struct +{ + uint32_t state[16]; /**< The current state of the stream. */ + uint8_t rounds; /**< Number of iterations. */ +} chacha_ctx; + +/** + * @brief Initialize a ChaCha context + * + * @param[out] ctx The context to initialize + * @param[in] rounds Number of rounds. Recommended: 20. Also in use: 8 and 12. + * @param[in] key The key to use. + * @param[in] keylen Length (in bytes) of @p key. Must be 16 or 32. + * @param[in] nonce IV / nonce to use. + * + * @returns `== 0` on success. + * @returns `< 0` if an illegal value for @p rounds or @p keylen was suppplied. + */ +int chacha_init(chacha_ctx *ctx, + unsigned rounds, + const uint8_t *key, uint32_t keylen, + const uint8_t nonce[8]); + +/** + * @brief Generate next block in the keystream. + * + * @details If you want to seek inside the cipher steam, then you have to + * update the clock in `ctx->state[13]:ctx->state[12]` manually. + * + * @warning You need to re-initialized the context with a new nonce after 2^64 + * encrypted blocks, or the keystream will repeat! + * + * @param[in,out] ctx The ChaCha context + * @param[out] x The block of the keystream (`sizeof(x) == 64`). + */ +void chacha_keystream_bytes(chacha_ctx *ctx, void *x); + +/** + * @brief Encode or decode a block of data. + * + * @details @p m is always the input regardless if it is the plaintext or ciphertext, + * and @p c vice verse. + * + * @warning You need to re-initialized the context with a new nonce after 2^64 + * encrypted blocks, or the keystream will repeat! + * + * @param[in,out] ctx The ChaCha context. + * @param[in] m The input. + * @param[out] c The output. + */ +void chacha_encrypt_bytes(chacha_ctx *ctx, const uint8_t *m, uint8_t *c); + +/** + * @copydoc chacha_encrypt_bytes() + */ +static inline void chacha_decrypt_bytes(chacha_ctx *ctx, const uint8_t *m, uint8_t *c) +{ + chacha_encrypt_bytes(ctx, m, c); +} + +/** + * @brief Seed the pseudo-random number generator. + * + * @details You can seed the random number generator with up to 64 bytes of data. + * If you feed less than 64 bytes of data, then the privous state gets + * only partially overwritten. + * + * If you want to supply multiple information, then you have to concatenate + * them manually before invoking the function. + * + * The PRNG gets a random seed in the build process. + * You can set a deterministic value by supplying a comma separated + * argument to `make` like + * `RIOT_CHACHA_PRNG_DEFAULT="0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"`. + * + * @param[in] data Some random data. + * @param[in] bytes Length of @p data in bytes where `0 < bytes <= 64`. + */ +void chacha_prng_seed(const void *data, size_t bytes); + +/** + * @brief Extract a number from the pseudo-random number generator. + * + * @warning After you have read 2^68 numbers you have to re-seed the PRNG. + * Otherwise the sequence will repeat. + */ +uint32_t chacha_prng_next(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef CRYPTO_CHACHA_H_ */ + +/** + * @} + */ diff --git a/tests/unittests/tests-crypto/tests-crypto-chacha.c b/tests/unittests/tests-crypto/tests-crypto-chacha.c new file mode 100644 index 0000000000..5063493bee --- /dev/null +++ b/tests/unittests/tests-crypto/tests-crypto-chacha.c @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2015 René Kijewski + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "embUnit/embUnit.h" +#include "tests-crypto.h" + +#include "crypto/chacha.h" + +#include + +static const uint8_t TC8_KEY[32] = { + 0xc4, 0x6e, 0xc1, 0xb1, 0x8c, 0xe8, 0xa8, 0x78, + 0x72, 0x5a, 0x37, 0xe7, 0x80, 0xdf, 0xb7, 0x35, +}; +static const uint8_t TC8_IV[8] = { + 0x1a, 0xda, 0x31, 0xd5, 0xcf, 0x68, 0x82, 0x21, +}; + +static const uint32_t TC8_AFTER_INIT[16] = { + 0x61707865, 0x3120646e, 0x79622d36, 0x6b206574, + 0xb1c16ec4, 0x78a8e88c, 0xe7375a72, 0x35b7df80, + 0xb1c16ec4, 0x78a8e88c, 0xe7375a72, 0x35b7df80, + 0x00000000, 0x00000000, 0xd531da1a, 0x218268cf, +}; + +static const uint8_t TC8_CHACHA8_BLOCK0[64] = { + 0x6a, 0x87, 0x01, 0x08, 0x85, 0x9f, 0x67, 0x91, + 0x18, 0xf3, 0xe2, 0x05, 0xe2, 0xa5, 0x6a, 0x68, + 0x26, 0xef, 0x5a, 0x60, 0xa4, 0x10, 0x2a, 0xc8, + 0xd4, 0x77, 0x00, 0x59, 0xfc, 0xb7, 0xc7, 0xba, + 0xe0, 0x2f, 0x5c, 0xe0, 0x04, 0xa6, 0xbf, 0xbb, + 0xea, 0x53, 0x01, 0x4d, 0xd8, 0x21, 0x07, 0xc0, + 0xaa, 0x1c, 0x7c, 0xe1, 0x1b, 0x7d, 0x78, 0xf2, + 0xd5, 0x0b, 0xd3, 0x60, 0x2b, 0xbd, 0x25, 0x94, +}; + +static const uint8_t TC8_CHACHA8_BLOCK1[64] = { + 0x05, 0x60, 0xbb, 0x6a, 0x84, 0x28, 0x9e, 0x0b, + 0x38, 0xf5, 0xdd, 0x21, 0xd6, 0xef, 0x6d, 0x77, + 0x37, 0xe3, 0xec, 0x0f, 0xb7, 0x72, 0xda, 0x2c, + 0x71, 0xc2, 0x39, 0x77, 0x62, 0xe5, 0xdb, 0xbb, + 0xf4, 0x49, 0xe3, 0xd1, 0x63, 0x9c, 0xcb, 0xfa, + 0x3e, 0x06, 0x9c, 0x4d, 0x87, 0x1e, 0xd6, 0x39, + 0x5b, 0x22, 0xaa, 0xf3, 0x5c, 0x8d, 0xa6, 0xde, + 0x2d, 0xec, 0x3d, 0x77, 0x88, 0x0d, 0xa8, 0xe8, +}; + +static const uint8_t TC8_CHACHA12_BLOCK0[64] = { + 0xb0, 0x2b, 0xd8, 0x1e, 0xb5, 0x5c, 0x8f, 0x68, + 0xb5, 0xe9, 0xca, 0x4e, 0x30, 0x70, 0x79, 0xbc, + 0x22, 0x5b, 0xd2, 0x20, 0x07, 0xed, 0xdc, 0x67, + 0x02, 0x80, 0x18, 0x20, 0x70, 0x9c, 0xe0, 0x98, + 0x07, 0x04, 0x6a, 0x0d, 0x2a, 0xa5, 0x52, 0xbf, + 0xdb, 0xb4, 0x94, 0x66, 0x17, 0x6d, 0x56, 0xe3, + 0x2d, 0x51, 0x9e, 0x10, 0xf5, 0xad, 0x5f, 0x27, + 0x46, 0xe2, 0x41, 0xe0, 0x9b, 0xdf, 0x99, 0x59, +}; + +static const uint8_t TC8_CHACHA12_BLOCK1[64] = { + 0x17, 0xbe, 0x08, 0x73, 0xed, 0xde, 0x9a, 0xf5, + 0xb8, 0x62, 0x46, 0x44, 0x1c, 0xe4, 0x10, 0x19, + 0x5b, 0xae, 0xde, 0x41, 0xf8, 0xbd, 0xab, 0x6a, + 0xd2, 0x53, 0x22, 0x63, 0x82, 0xee, 0x38, 0x3e, + 0x34, 0x72, 0xf9, 0x45, 0xa5, 0xe6, 0xbd, 0x62, + 0x8c, 0x7a, 0x58, 0x2b, 0xcf, 0x8f, 0x89, 0x98, + 0x70, 0x59, 0x6a, 0x58, 0xda, 0xb8, 0x3b, 0x51, + 0xa5, 0x0c, 0x7d, 0xbb, 0x4f, 0x3e, 0x6e, 0x76, +}; + +static const uint8_t TC8_CHACHA20_BLOCK0[64] = { + 0x82, 0x6a, 0xbd, 0xd8, 0x44, 0x60, 0xe2, 0xe9, + 0x34, 0x9f, 0x0e, 0xf4, 0xaf, 0x5b, 0x17, 0x9b, + 0x42, 0x6e, 0x4b, 0x2d, 0x10, 0x9a, 0x9c, 0x5b, + 0xb4, 0x40, 0x00, 0xae, 0x51, 0xbe, 0xa9, 0x0a, + 0x49, 0x6b, 0xee, 0xef, 0x62, 0xa7, 0x68, 0x50, + 0xff, 0x3f, 0x04, 0x02, 0xc4, 0xdd, 0xc9, 0x9f, + 0x6d, 0xb0, 0x7f, 0x15, 0x1c, 0x1c, 0x0d, 0xfa, + 0xc2, 0xe5, 0x65, 0x65, 0xd6, 0x28, 0x96, 0x25, +}; + +static const uint8_t TC8_CHACHA20_BLOCK1[64] = { + 0x5b, 0x23, 0x13, 0x2e, 0x7b, 0x46, 0x9c, 0x7b, + 0xfb, 0x88, 0xfa, 0x95, 0xd4, 0x4c, 0xa5, 0xae, + 0x3e, 0x45, 0xe8, 0x48, 0xa4, 0x10, 0x8e, 0x98, + 0xba, 0xd7, 0xa9, 0xeb, 0x15, 0x51, 0x27, 0x84, + 0xa6, 0xa9, 0xe6, 0xe5, 0x91, 0xdc, 0xe6, 0x74, + 0x12, 0x0a, 0xca, 0xf9, 0x04, 0x0f, 0xf5, 0x0f, + 0xf3, 0xac, 0x30, 0xcc, 0xfb, 0x5e, 0x14, 0x20, + 0x4f, 0x5e, 0x42, 0x68, 0xb9, 0x0a, 0x88, 0x04, +}; + +static void _test_crypto_chacha(unsigned rounds, unsigned keylen, + const uint8_t key[32], const uint8_t iv[8], + const uint32_t after_init[16], + const uint8_t block0[64], const uint8_t block1[64]) +{ + chacha_ctx ctx; + uint8_t block[64]; + + TEST_ASSERT_EQUAL_INT(0, chacha_init(&ctx, rounds, key, keylen, iv)); + TEST_ASSERT_EQUAL_INT(0, memcmp(ctx.state, after_init, 64)); + + chacha_keystream_bytes(&ctx, block); + TEST_ASSERT_EQUAL_INT(0, memcmp(block, block0, 64)); + + chacha_keystream_bytes(&ctx, block); + TEST_ASSERT_EQUAL_INT(0, memcmp(block, block1, 64)); +} + +static void test_crypto_chacha8_tc8(void) +{ + _test_crypto_chacha(8, 16, TC8_KEY, TC8_IV, TC8_AFTER_INIT, + TC8_CHACHA8_BLOCK0, TC8_CHACHA8_BLOCK1); +} + +static void test_crypto_chacha12_tc8(void) +{ + _test_crypto_chacha(12, 16, TC8_KEY, TC8_IV, TC8_AFTER_INIT, + TC8_CHACHA12_BLOCK0, TC8_CHACHA12_BLOCK1); +} + +static void test_crypto_chacha20_tc8(void) +{ + _test_crypto_chacha(20, 16, TC8_KEY, TC8_IV, TC8_AFTER_INIT, + TC8_CHACHA20_BLOCK0, TC8_CHACHA20_BLOCK1); +} + +Test *tests_crypto_chacha_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_crypto_chacha8_tc8), + new_TestFixture(test_crypto_chacha12_tc8), + new_TestFixture(test_crypto_chacha20_tc8), + }; + EMB_UNIT_TESTCALLER(crypto_chacha_tests, NULL, NULL, fixtures); + return (Test *) &crypto_chacha_tests; +} diff --git a/tests/unittests/tests-crypto/tests-crypto.c b/tests/unittests/tests-crypto/tests-crypto.c index 0e4caafc53..432b662ff9 100644 --- a/tests/unittests/tests-crypto/tests-crypto.c +++ b/tests/unittests/tests-crypto/tests-crypto.c @@ -11,4 +11,5 @@ void tests_crypto(void) { TESTS_RUN(tests_crypto_sha256_tests()); + TESTS_RUN(tests_crypto_chacha_tests()); } diff --git a/tests/unittests/tests-crypto/tests-crypto.h b/tests/unittests/tests-crypto/tests-crypto.h index 05e9de01a1..b390fe2ec4 100644 --- a/tests/unittests/tests-crypto/tests-crypto.h +++ b/tests/unittests/tests-crypto/tests-crypto.h @@ -36,6 +36,13 @@ void tests_crypto(void); */ Test *tests_crypto_sha256_tests(void); +/** + * @brief Generates tests for crypto/chacha.h + * + * @return embUnit tests if successful, NULL if not. + */ +Test *tests_crypto_chacha_tests(void); + #ifdef __cplusplus } #endif