Add local pseudo-RNG to randLIB

Rather than using system rand(), provide our own pseudo-RNG.

Generator used is "xoroshiro128+", which has 16 bytes of state and
2^128-1 period.

Main advantage is that we can now seed with up to 128 bits of entropy,
rather than the 32 bits srand() limited us to. We also can be assured of
the quality of the algorithm.

As the core generator is 64-bit, we now provide a get 64-bit function,
and others are based on this.

Incorporate Linux's /dev/urandom use into the main source file.
pull/3240/head
Kevin Bracey 2016-10-17 09:20:26 +01:00
parent 80f5c491dd
commit fce09a935b
8 changed files with 320 additions and 135 deletions

View File

@ -25,6 +25,10 @@ extern "C" {
extern void arm_random_module_init(void);
/**
* \brief Get random library seed value.
*
* This function should return as random a value as possible, using
* hardware sources. Repeated calls should return different values if
* at all possible.
*/
extern uint32_t arm_random_seed_get(void);
#ifdef __cplusplus

View File

@ -48,11 +48,23 @@ extern "C" {
/**
* \brief Init seed for Pseudo Random.
*
* Makes call(s) to the platform's arm_random_seed_get() to seed the
* pseudo-random generator.
*
* \return None
*
*/
extern void randLIB_seed_random(void);
/**
* \brief Update seed for pseudo-random generator
*
* Adds seed information to existing generator, to perturb the
* sequence.
* \param seed 64 bits of data to add to the seed.
*/
extern void randLIB_add_seed(uint64_t seed);
/**
* \brief Generate 8-bit random number.
*
@ -75,21 +87,29 @@ extern uint16_t randLIB_get_16bit(void);
* \brief Generate 32-bit random number.
*
* \param None
* \return 16-bit random number
* \return 32-bit random number
*
*/
extern uint32_t randLIB_get_32bit(void);
/**
* \brief Generate 64-bit random number.
*
* \param None
* \return 64-bit random number
*
*/
extern uint64_t randLIB_get_64bit(void);
/**
* \brief Generate n-bytes random numbers.
*
* \param data_ptr pointer where random will be stored
* \param eight_bit_boundary how many bytes need random
* \return 0 process valid
* \return -1 Unsupported Parameters
* \param count how many bytes need random
*
* \return data_ptr
*/
extern int8_t randLIB_get_n_bytes_random(uint8_t *data_ptr, uint8_t eight_bit_boundary);
extern void *randLIB_get_n_bytes_random(void *data_ptr, uint8_t count);
/**
* \brief Generate a random number within a range.
@ -117,6 +137,12 @@ uint16_t randLIB_get_random_in_range(uint16_t min, uint16_t max);
*/
uint32_t randLIB_randomise_base(uint32_t base, uint16_t min_factor, uint16_t max_factor);
#ifdef RANDLIB_PRNG
/* \internal Reset the PRNG state to zero (invalid) */
void randLIB_reset(void);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -14,125 +14,171 @@
* limitations under the License.
*/
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>
#include "randLIB.h"
#include "platform/arm_hal_random.h"
#if ((RAND_MAX+1) & RAND_MAX) != 0
#error "RAND_MAX isn't 2^n-1 :("
#endif
/**
* This library is made for getting random numbers for Timing needs in protocols.
* This library is made for getting random numbers for timing needs in
* protocols, plus to generate dynamic ports, random IDs etc.
*
* **not safe to use for security or cryptographic operations.**
*
* Base implementation is a pseudo-RNG, but may also use a system RNG.
* Replay of sequence by reseeding is not possible.
*
* Base pseudo-RNG is the xoroshiro128+ generator by Marsaglia, Blackman and
* Vigna:
*
* http://xoroshiro.di.unimi.it/
*
* Certainly not the fastest for 32-bit or smaller platforms, but speed
* is not critical. None of the long operations in the core are actually hard,
* unlike the divisions and multiplies in the utility functions below, where we
* do try to keep the operations narrow.
*/
/* On some platforms, read from a system RNG, rather than use our own */
/* RANDLIB_PRNG disables this and forces use of the PRNG (useful for test only?) */
#ifndef RANDLIB_PRNG
#ifdef __linux
#define RANDOM_DEVICE "/dev/urandom"
#endif
#endif // RANDLIB_PRNG
/* RAM usage - 16 bytes of state (or a FILE * pointer and underlying FILE, which
* will include a buffer) */
#ifdef RANDOM_DEVICE
#include <stdio.h>
static FILE *random_file;
#else
static uint64_t state[2];
#endif
#ifdef RANDLIB_PRNG
void randLIB_reset(void)
{
state[0] = 0;
state[1] = 0;
}
#endif
#ifndef RANDOM_DEVICE
static inline uint64_t rol(uint64_t n, int bits)
{
return (n << bits) | (n >> (64 - bits));
}
/* Lower-quality generator used only for initial seeding, if platform
* isn't returning multiple seeds itself. Multiplies are rather heavy
* for lower-end platforms, but this is initialisation only.
*/
static uint64_t splitmix64(uint64_t *seed)
{
uint64_t z = (*seed += UINT64_C(0x9E3779B97F4A7C15));
z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9);
z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB);
return z ^ (z >> 31);
}
#endif
/**
* \brief Init seed for Pseudo Random.
*
* \return None
*
*/
void randLIB_seed_random(void)
{
uint32_t rand_seed;
#ifdef RANDOM_DEVICE
if (!random_file) {
random_file = fopen(RANDOM_DEVICE, "rb");
}
#else
arm_random_module_init();
rand_seed = arm_random_seed_get();
srand(rand_seed);
/* We exclusive-OR with the current state, in case they make this call
* multiple times. We don't want to potentially lose entropy.
*/
/* Spell out expressions so we get known ordering of 4 seed calls */
uint64_t s = (uint64_t) arm_random_seed_get() << 32;
state[0] ^= ( s | arm_random_seed_get());
s = (uint64_t) arm_random_seed_get() << 32;
state[1] ^= s | arm_random_seed_get();
/* This check serves to both to stir the state if the platform is returning
* constant seeding values, and to avoid the illegal all-zero state.
*/
if (state[0] == state[1]) {
uint64_t seed = state[0];
state[0] = splitmix64(&seed);
state[1] = splitmix64(&seed);
}
#endif
}
void randLIB_add_seed(uint64_t seed)
{
#ifndef RANDOM_DEVICE
state[1] += seed;
#endif
}
/**
* \brief Generate 8-bit random number.
*
* \param None
* \return 8-bit random number
*
*/
uint8_t randLIB_get_8bit(void)
{
return rand();
uint64_t r = randLIB_get_64bit();
return (uint8_t) (r >> 56);
}
/**
* \brief Generate 16-bit random number.
*
* \param None
* \return 16-bit random number
*
*/
uint16_t randLIB_get_16bit(void)
{
uint16_t ret_val;
ret_val = rand();
#if RAND_MAX == 0x7FFF
ret_val |= (uint16_t) rand() << 15;
#endif
return ret_val;
uint64_t r = randLIB_get_64bit();
return (uint16_t) (r >> 48);
}
/**
* \brief Generate 32-bit random number.
*
* \param None
* \return 32-bit random number
*
*/
uint32_t randLIB_get_32bit(void)
{
uint32_t ret_val;
ret_val = rand();
#if RAND_MAX == 0x7FFF
ret_val |= (uint32_t) rand() << 15;
ret_val |= (uint32_t) rand() << 30;
#elif RAND_MAX == 0x3FFFFFFF /* IAR */
ret_val |= (uint32_t) rand() << 30;
#elif RAND_MAX == 0x7FFFFFFF
ret_val |= (uint32_t) rand() << 31;
#else
#error "randLIB_get_32bit - odd RAND_MAX"
#endif
return ret_val;
uint64_t r = randLIB_get_64bit();
return (uint32_t) (r >> 32);
}
/**
* \brief Generate n-bytes random numbers.
*
* \param data_ptr pointer where random will be stored
* \param eight_bit_boundary how many bytes need random
* \return 0 process valid
* \return -1 Unsupported Parameters
*
*/
int8_t randLIB_get_n_bytes_random(uint8_t *data_ptr, uint8_t eight_bit_boundary)
uint64_t randLIB_get_64bit(void)
{
if ((data_ptr == 0) || (eight_bit_boundary == 0)) {
return -1;
#ifdef RANDOM_DEVICE
if (!random_file) {
return 0;
}
uint64_t result;
if (fread(&result, 1, sizeof result, random_file) != 1) {
result = 0;
}
return result;
#else
const uint64_t s0 = state[0];
uint64_t s1 = state[1];
const uint64_t result = s0 + s1;
while (eight_bit_boundary) {
*data_ptr++ = randLIB_get_8bit();
eight_bit_boundary--;
}
return 0;
s1 ^= s0;
state[0] = rol(s0, 55) ^ s1 ^ (s1 << 14);
state[1] = rol(s1, 36);
return result;
#endif
}
void *randLIB_get_n_bytes_random(void *ptr, uint8_t count)
{
uint8_t *data_ptr = ptr;
uint64_t r = 0;
for (uint_fast8_t i = 0; i < count; i++) {
/* Take 8 bytes at a time */
if (i % 8 == 0) {
r = randLIB_get_64bit();
} else {
r >>= 8;
}
data_ptr[i] = (uint8_t) r;
}
return data_ptr;
}
/**
* \brief Generate a random number within a range.
*
* The result is linearly distributed in the range [min..max], inclusive.
*
* \param min minimum value that can be generated
* \param max maximum value that can be generated
*/
uint16_t randLIB_get_random_in_range(uint16_t min, uint16_t max)
{
/* This special case is potentially common, particularly in this routine's
@ -141,46 +187,52 @@ uint16_t randLIB_get_random_in_range(uint16_t min, uint16_t max)
return min;
}
#if UINT_MAX >= 0xFFFFFFFF
const unsigned int rand_max = 0xFFFFFFFFu; // will use rand32
#else
const unsigned int rand_max = 0xFFFFu; // will use rand16
/* 16-bit arithmetic below fails in this extreme case; we can optimise it */
if (max - min == 0xFFFF) {
return randLIB_get_16bit();
}
#endif
/* We get RAND_MAX+1 values from rand() in the range [0..RAND_MAX], and
/* We get rand_max values from rand16 or 32() in the range [0..rand_max-1], and
* need to divvy them up into the number of values we need. And reroll any
* odd values off the end as we insist every value having equal chance.
*
* Special handling for systems where RAND_MAX is 0x7FFF; we use our
* randLIB_get_16bit() and have to be a bit more careful about
* unsigned integer overflow. (On other systems rand() returns int,
* so we can't overflow if we use unsigned int).
* Using the range [0..rand_max-1] saves long division on the band
* calculation - it means rand_max ends up always being rerolled.
*
* Eg, range(1,3), RAND_MAX = 0x7FFFFFFF:
* We have 3 bands of size 0x2AAAAAAA (0x80000000/3).
* Eg, range(1,2), rand_max = 0xFFFF:
* We have 2 bands of size 0x7FFF (0xFFFF/2).
*
* We roll: 0x00000000..0x2AAAAAAA9 -> 1
* 0x2AAAAAAA..0x555555553 -> 2
* 0x55555554..0x7FFFFFFFD -> 3
* 0x7FFFFFFE..0x7FFFFFFFF -> reroll
* We roll: 0x0000..0x7FFE -> 1
* 0x7FFF..0xFFFD -> 2
* 0xFFFE..0xFFFF -> reroll
* (calculating band size as 0x10000/2 would have avoided the reroll cases)
*
* Eg, range(1,3), rand_max = 0xFFFFFFFF:
* We have 3 bands of size 0x55555555 (0xFFFFFFFF/3).
*
* We roll: 0x00000000..0x555555554 -> 1
* 0x55555555..0xAAAAAAAA9 -> 2
* 0xAAAAAAAA..0xFFFFFFFFE -> 3
* 0xFFFFFFFF -> reroll
*
* (Bias problem clearly pretty insignificant there, but gets worse as
* range increases).
*/
unsigned int values_needed = max + 1 - min;
#if RAND_MAX > 0xFFFF
unsigned int band_size = (RAND_MAX + 1u) / values_needed;
#elif UINT_MAX > 0xFFFF
unsigned int band_size = 0x10000u / values_needed;
#else
const unsigned int values_needed = max + 1 - min;
/* Avoid the need for long division, at the expense of fractionally
* increasing reroll chance. */
unsigned int band_size = 0xFFFFu / values_needed;
#endif
unsigned int top_of_bands = band_size * values_needed;
const unsigned int band_size = rand_max / values_needed;
const unsigned int top_of_bands = band_size * values_needed;
unsigned int result;
do {
#if RAND_MAX > 0xFFFF
result = rand();
#if UINT_MAX > 0xFFFF
result = randLIB_get_32bit();
#else
result = randLIB_get_16bit();
#endif
@ -189,20 +241,6 @@ uint16_t randLIB_get_random_in_range(uint16_t min, uint16_t max)
return min + (uint16_t)(result / band_size);
}
/**
* \brief Randomise a base 32-bit number by a jitter factor
*
* The result is linearly distributed in the jitter range, which is expressed
* as fixed-point unsigned 1.15 values. For example, to produce a number in the
* range [0.75 * base, 1.25 * base], set min_factor to 0x6000 and max_factor to
* 0xA000.
*
* Result is clamped to 0xFFFFFFFF if it overflows.
*
* \param base The base 32-bit value
* \param min_factor The minimum value for the random factor
* \param max_factor The maximum value for the random factor
*/
uint32_t randLIB_randomise_base(uint32_t base, uint16_t min_factor, uint16_t max_factor)
{
uint16_t random_factor = randLIB_get_random_in_range(min_factor, max_factor);

View File

@ -14,5 +14,5 @@ TEST_SRC_FILES = \
include ../MakefileWorker.mk
CPPUTESTFLAGS += -DFEA_TRACE_SUPPORT
CPPUTESTFLAGS += -DFEA_TRACE_SUPPORT -DRANDLIB_PRNG

View File

@ -35,6 +35,11 @@ TEST(randLIB, test_randLIB_get_32bit)
CHECK(test_randLIB_get_32bit());
}
TEST(randLIB, test_randLIB_get_64bit)
{
CHECK(test_randLIB_get_64bit());
}
TEST(randLIB, test_randLIB_get_n_bytes_random)
{
CHECK(test_randLIB_get_n_bytes_random());

View File

@ -8,12 +8,14 @@
bool test_randLIB_seed_random()
{
randLIB_reset();
randLIB_seed_random();
return true;
}
bool test_randLIB_get_8bit()
{
randLIB_reset();
randLIB_seed_random();
uint8_t test = randLIB_get_8bit();
if( test == 0 ) {
@ -27,6 +29,7 @@ bool test_randLIB_get_8bit()
bool test_randLIB_get_16bit()
{
randLIB_reset();
randLIB_seed_random();
uint16_t test = randLIB_get_16bit();
if( test == 0 ) {
@ -40,6 +43,7 @@ bool test_randLIB_get_16bit()
bool test_randLIB_get_32bit()
{
randLIB_reset();
randLIB_seed_random();
uint32_t test = randLIB_get_32bit();
if( test == 0 ) {
@ -51,23 +55,114 @@ bool test_randLIB_get_32bit()
return true;
}
static bool test_output(uint32_t seed, bool seed_inc, const uint64_t expected[8])
{
random_stub_set_seed(seed, seed_inc);
randLIB_reset();
randLIB_seed_random();
for (int i = 0; i < 8; i++) {
if (randLIB_get_64bit() != expected[i]) {
return false;
}
}
return true;
}
bool test_randLIB_get_64bit()
{
/* Initial 8 xoroshiro128+ values with initial seed of
* (0x0000000100000002, 0x00000003000000004).
*/
static const uint64_t expected1234[] = {
UINT64_C(0x0000000400000006),
UINT64_C(0x0100806200818026),
UINT64_C(0x2a30826271904706),
UINT64_C(0x918d7ea50109290d),
UINT64_C(0x5dcbd701c1e1c64c),
UINT64_C(0xaa129b152055f299),
UINT64_C(0x4c95c2b1e1038a4d),
UINT64_C(0x6479e7a3a75d865a)
};
if (!test_output(1, true, expected1234)) {
goto fail;
}
/* If passed all zero seeds, seeding should detect this
* and use splitmix64 to create the actual seed
* (0xe220a8397b1dcdaf, 0x6e789e6aa1b965f4),
* and produce this output:
*/
static const uint64_t expected0000[] = {
UINT64_C(0x509946a41cd733a3),
UINT64_C(0x00885667b1934bfa),
UINT64_C(0x1061f9ad258fd5d5),
UINT64_C(0x3f8be44897a4317c),
UINT64_C(0x60da683bea50e6ab),
UINT64_C(0xd6b52f5379de1de0),
UINT64_C(0x2608bc9fedc5b750),
UINT64_C(0xb9fac9c7ec9de02a)
};
if (!test_output(0, false, expected0000)) {
goto fail;
}
/* If passed all "4" seeds, seeding should detect this
* and use splitmix64 to create the actual seed
* (0x03910b0aab9b37e5, 0x0b309ab13d42b2a6),
* and produce this output:
*/
static const uint64_t expected4444[] = {
UINT64_C(0x0ec1a5bbe8ddea8b),
UINT64_C(0x0be710b8fcf5a491),
UINT64_C(0xb21127f7159348b4),
UINT64_C(0xdf31900d21f92182),
UINT64_C(0xd5a797507d94daa9),
UINT64_C(0x66a1c5a4fb04be3d),
UINT64_C(0x259e5385f48353be),
UINT64_C(0x5d3e3286cd4eae19)
};
if (!test_output(4, false, expected4444)) {
goto fail;
}
/* Last test used constant seed of 4, which is the default */
return true;
fail:
/* Put back the default seed of 4 (other tests might rely on it) */
random_stub_set_seed(4, false);
return false;
}
bool test_randLIB_get_n_bytes_random()
{
int8_t ret = randLIB_get_n_bytes_random(NULL, 0);
if( ret != -1){
randLIB_reset();
randLIB_seed_random();
uint8_t dat[5];
void *ret = randLIB_get_n_bytes_random(dat, 5);
if(ret != dat){
return false;
}
uint8_t dat[5];
ret = randLIB_get_n_bytes_random(&dat, 5);
if( ret != 0){
uint8_t dat2[5];
randLIB_get_n_bytes_random(dat2, 5);
if (memcmp(dat, dat2, 5) == 0) {
return false;
}
return true;
}
bool test_randLIB_get_random_in_range()
{
randLIB_reset();
randLIB_seed_random();
uint16_t ret = randLIB_get_random_in_range(2, 2);
if( ret != 2 ){
return false;
@ -85,6 +180,9 @@ bool test_randLIB_get_random_in_range()
bool test_randLIB_randomise_base()
{
randLIB_reset();
randLIB_seed_random();
uint32_t ret = randLIB_randomise_base(0,0,0);
if( ret ){
return false;

View File

@ -18,6 +18,8 @@ bool test_randLIB_get_16bit();
bool test_randLIB_get_32bit();
bool test_randLIB_get_64bit();
bool test_randLIB_get_n_bytes_random();
bool test_randLIB_get_random_in_range();

View File

@ -1,8 +1,10 @@
/*
* Copyright (c) 2016, ARM Limited, All Rights Reserved
*/
#include "inttypes.h"
#include "random_stub.h"
static uint32_t seed_value = 4;
static bool seed_inc = false;
void arm_random_module_init(void)
{
@ -11,5 +13,15 @@ void arm_random_module_init(void)
uint32_t arm_random_seed_get(void)
{
return 4;
uint32_t result = seed_value;
if (seed_inc) {
++seed_value;
}
return result;
}
void random_stub_set_seed(uint32_t value, bool increment)
{
seed_value = value;
seed_inc = increment;
}