1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-12-25 14:33:52 +01:00

Merge pull request #3229 from Kijewski/chacha

sys: add ChaCha stream cipher and PRNG
This commit is contained in:
Philipp Rosenkranz 2015-06-28 21:13:59 +02:00
commit 1913b7ea66
7 changed files with 484 additions and 0 deletions

View File

@ -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

120
sys/crypto/chacha.c Normal file
View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2008 D. J. Bernstein (dedicated to the public domain)
* Copyright (C) 2015 René Kijewski <rene.kijewski@fu-berlin.de>
*
* 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 <string.h>
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];
}
}

64
sys/crypto/chacha_prng.c Normal file
View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2015 René Kijewski <rene.kijewski@fu-berlin.de>
*
* 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 <string.h>
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;
}

144
sys/include/crypto/chacha.h Normal file
View File

@ -0,0 +1,144 @@
/*
* Copyright (C) 2008 D. J. Bernstein (dedicated to the public domain)
* Copyright (C) 2015 René Kijewski <rene.kijewski@fu-berlin.de>
*
* 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 <rene.kijewski@fu-berlin.de>
*/
#ifndef CRYPTO_CHACHA_H_
#define CRYPTO_CHACHA_H_
#include <stdint.h>
#include <stddef.h>
#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_ */
/**
* @}
*/

View File

@ -0,0 +1,142 @@
/*
* Copyright (C) 2015 René Kijewski <rene.kijewski@fu-berlin.de>
*
* 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 <string.h>
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;
}

View File

@ -11,4 +11,5 @@
void tests_crypto(void)
{
TESTS_RUN(tests_crypto_sha256_tests());
TESTS_RUN(tests_crypto_chacha_tests());
}

View File

@ -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