From b2868c7b9cfed0e517547768d2bb9178d409daef Mon Sep 17 00:00:00 2001 From: antirez Date: Thu, 5 Apr 2018 13:24:22 +0200 Subject: [PATCH] Modules API: RM_GetRandomBytes() / GetRandomHexChars(). --- src/module.c | 21 ++++++++++ src/redismodule.h | 5 +++ src/server.h | 3 +- src/util.c | 98 ++++++++++++++++++++--------------------------- 4 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/module.c b/src/module.c index 1aa39884..7cf1a24b 100644 --- a/src/module.c +++ b/src/module.c @@ -4183,6 +4183,25 @@ int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remain return REDISMODULE_OK; } +/* -------------------------------------------------------------------------- + * Modules utility APIs + * -------------------------------------------------------------------------- */ + +/* Return random bytes using SHA1 in counter mode with a /dev/urandom + * initialized seed. This function is fast so can be used to generate + * many bytes without any effect on the operating system entropy pool. + * Currently this function is not thread safe. */ +void RM_GetRandomBytes(unsigned char *dst, size_t len) { + getRandomBytes(dst,len); +} + +/* Like RedisModule_GetRandomBytes() but instead of setting the string to + * random bytes the string is set to random characters in the in the + * hex charset [0-9a-f]. */ +void RM_GetRandomHexChars(char *dst, size_t len) { + getRandomHexChars(dst,len); +} + /* -------------------------------------------------------------------------- * Modules API internals * -------------------------------------------------------------------------- */ @@ -4575,4 +4594,6 @@ void moduleRegisterCoreAPI(void) { REGISTER_API(GetTimerInfo); REGISTER_API(GetMyClusterID); REGISTER_API(GetClusterSize); + REGISTER_API(GetRandomBytes); + REGISTER_API(GetRandomHexChars); } diff --git a/src/redismodule.h b/src/redismodule.h index d232c0fa..f663006a 100644 --- a/src/redismodule.h +++ b/src/redismodule.h @@ -278,6 +278,9 @@ int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModule int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data); const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void); size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void); +void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len); +void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len); + /* Experimental APIs */ #ifdef REDISMODULE_EXPERIMENTAL_API @@ -411,6 +414,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int REDISMODULE_GET_API(GetTimerInfo); REDISMODULE_GET_API(GetMyClusterID); REDISMODULE_GET_API(GetClusterSize); + REDISMODULE_GET_API(GetRandomBytes); + REDISMODULE_GET_API(GetRandomHexChars); #ifdef REDISMODULE_EXPERIMENTAL_API REDISMODULE_GET_API(GetThreadSafeContext); diff --git a/src/server.h b/src/server.h index 682129d0..09c99bdd 100644 --- a/src/server.h +++ b/src/server.h @@ -1366,7 +1366,8 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) /* Utils */ long long ustime(void); long long mstime(void); -void getRandomHexChars(char *p, unsigned int len); +void getRandomHexChars(char *p, size_t len); +void getRandomBytes(unsigned char *p, size_t len); uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); void exitFromChild(int retcode); size_t redisPopcount(void *s, long count); diff --git a/src/util.c b/src/util.c index 36cbc43d..db5b018d 100644 --- a/src/util.c +++ b/src/util.c @@ -536,14 +536,12 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) { return l; } -/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a - * given execution of Redis, so that if you are talking with an instance - * having run_id == A, and you reconnect and it has run_id == B, you can be - * sure that it is either a different instance or it was restarted. */ -void getRandomHexChars(char *p, unsigned int len) { - char *charset = "0123456789abcdef"; - unsigned int j; - +/* Get random bytes, attempts to get an initial seed from /dev/urandom and + * the uses a one way hash function in counter mode to generate a random + * stream. However if /dev/urandom is not available, a weaker seed is used. + * + * This function is not thread safe, since the state is global. */ +void getRandomBytes(unsigned char *p, size_t len) { /* Global state. */ static int seed_initialized = 0; static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */ @@ -555,64 +553,50 @@ void getRandomHexChars(char *p, unsigned int len) { * function we just need non-colliding strings, there are no * cryptographic security needs. */ FILE *fp = fopen("/dev/urandom","r"); - if (fp && fread(seed,sizeof(seed),1,fp) == 1) + if (fp == NULL || fread(seed,sizeof(seed),1,fp) != 1) { + /* Revert to a weaker seed, and in this case reseed again + * at every call.*/ + for (unsigned int j = 0; j < sizeof(seed); j++) { + struct timeval tv; + gettimeofday(&tv,NULL); + pid_t pid = getpid(); + seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp; + } + } else { seed_initialized = 1; + } if (fp) fclose(fp); } - if (seed_initialized) { - while(len) { - unsigned char digest[20]; - SHA1_CTX ctx; - unsigned int copylen = len > 20 ? 20 : len; + while(len) { + unsigned char digest[20]; + SHA1_CTX ctx; + unsigned int copylen = len > 20 ? 20 : len; - SHA1Init(&ctx); - SHA1Update(&ctx, seed, sizeof(seed)); - SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter)); - SHA1Final(digest, &ctx); - counter++; + SHA1Init(&ctx); + SHA1Update(&ctx, seed, sizeof(seed)); + SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter)); + SHA1Final(digest, &ctx); + counter++; - memcpy(p,digest,copylen); - /* Convert to hex digits. */ - for (j = 0; j < copylen; j++) p[j] = charset[p[j] & 0x0F]; - len -= copylen; - p += copylen; - } - } else { - /* If we can't read from /dev/urandom, do some reasonable effort - * in order to create some entropy, since this function is used to - * generate run_id and cluster instance IDs */ - char *x = p; - unsigned int l = len; - struct timeval tv; - pid_t pid = getpid(); - - /* Use time and PID to fill the initial array. */ - gettimeofday(&tv,NULL); - if (l >= sizeof(tv.tv_usec)) { - memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec)); - l -= sizeof(tv.tv_usec); - x += sizeof(tv.tv_usec); - } - if (l >= sizeof(tv.tv_sec)) { - memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec)); - l -= sizeof(tv.tv_sec); - x += sizeof(tv.tv_sec); - } - if (l >= sizeof(pid)) { - memcpy(x,&pid,sizeof(pid)); - l -= sizeof(pid); - x += sizeof(pid); - } - /* Finally xor it with rand() output, that was already seeded with - * time() at startup, and convert to hex digits. */ - for (j = 0; j < len; j++) { - p[j] ^= rand(); - p[j] = charset[p[j] & 0x0F]; - } + memcpy(p,digest,copylen); + len -= copylen; + p += copylen; } } +/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a + * given execution of Redis, so that if you are talking with an instance + * having run_id == A, and you reconnect and it has run_id == B, you can be + * sure that it is either a different instance or it was restarted. */ +void getRandomHexChars(char *p, size_t len) { + char *charset = "0123456789abcdef"; + size_t j; + + getRandomBytes((unsigned char*)p,len); + for (j = 0; j < len; j++) p[j] = charset[p[j] & 0x0F]; +} + /* Given the filename, return the absolute path as an SDS string, or NULL * if it fails for some reason. Note that "filename" may be an absolute path * already, this will be detected and handled correctly.