lua-bcrypt

Secure password hashing for Lua
Log | Files | Refs

commit 65f1c2aaf99002d8062b4334de89bebe0e416fd4
parent 94c2b71f62b90efcfbff5df34dad39fda4b7cd0c
Author: Michael Savage <mikejsavage@gmail.com>
Date:   Mon,  5 May 2014 14:11:32 +0100

Better random device and error handling

- add checks to ensure the random device is actually a random device
- add bcrypt.random to change the random device in use
- propagate error codes, and produce better error messages

Diffstat:
README.md | 23+++++++++++++++++++++++
src/main.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
tests.lua | 12+++++++++---
3 files changed, 121 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md @@ -31,3 +31,26 @@ You can also explicitly pass a salt to `bcrypt.digest`: local digest = bcrypt.digest( "password", salt ) assert( bcrypt.verify( "password", digest ) ) + +If you want to use a different random device, you can use +`bcrypt.random`: + + local bcrypt = require( "bcrypt" ) + bcrypt.random( "/dev/random" ) + local digest = bcrypt.digest( "password", rounds ) + + -- bcrypt.random( "/dev/null" ) - fails + + +Chroot +------ + +If you want to use `lua-bcrypt` from inside a chroot, `/dev/urandom` +must still exist when `require( "bcrypt" )` is called, even if you are +planning to use a different random device. The reasoning behind this is +that chroots are a less common use case of this library, and we should +fail as early as possible on errors in the common use case. + +Therefore, if you wish to use chroot, you should run: + + mknod -m 644 /path/to/chroot/dev/urandom c 1 9 diff --git a/src/main.c b/src/main.c @@ -1,7 +1,6 @@ -#include <stdlib.h> #include <string.h> -#include <fcntl.h> -#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> #include <errno.h> #include <lua.h> @@ -13,25 +12,87 @@ #define CRYPT_OUTPUT_SIZE ( 7 + 22 + 31 + 1 ) #define CRYPT_GENSALT_OUTPUT_SIZE ( 7 + 22 + 1 ) #define ENTROPY_SIZE 32 +#define DEFAULT_RANDOM "/dev/urandom" #if LUA_VERSION_NUM < 502 #define luaL_newlib( L, l ) ( lua_newtable( L ), luaL_register( L, NULL, l ) ) #endif -static int urandom; +enum { + BC_ERR_API = 1, + BC_ERR_RNG, + BC_ERR_NORNG, +}; + +static FILE * random_file = NULL; + +static const char * my_strerror( const int err ) { + switch( err ) { + case BC_ERR_API: + return strerror( errno ); + + case BC_ERR_RNG: + return "Bad entropy source"; + + case BC_ERR_NORNG: + return "No entropy source"; + + default: + return "."; + } +} + +// http://insanecoding.blogspot.ca/2014/05/a-good-idea-with-bad-usage-devurandom.html +static int open_random( const char * const path ) { + FILE * f = fopen( path, "r" ); + + if( f == NULL ) { + return BC_ERR_API; + } + + int fd = fileno( f ); + struct stat stat_buf; + + if( fd == -1 || fstat( fd, &stat_buf ) != 0 ) { + return BC_ERR_API; + } + + // is this a random device? + if( S_ISCHR( stat_buf.st_mode ) != 0 && stat_buf.st_rdev != makedev( 1, 8 ) && stat_buf.st_rdev != makedev( 1, 9 ) ) { + return BC_ERR_RNG; + } + + random_file = f; + + return 0; +} + +static int read_random( char * const buf, const size_t len ) { + if( random_file == NULL ) { + return BC_ERR_NORNG; + } + + size_t bytes_read = fread( buf, 1, len, random_file ); + + if( bytes_read < len ) { + return BC_ERR_API; + } + + return 0; +} static int makesalt( char * const salt, const size_t size, const int rounds ) { char entropy[ ENTROPY_SIZE ]; - const ssize_t bytes = read( urandom, entropy, sizeof( entropy ) ); + int rv_random = read_random( entropy, sizeof( entropy ) ); - if( bytes != sizeof( entropy ) ) { - return 1; + if( rv_random != 0 ) { + return rv_random; } const char * const ok = crypt_gensalt_rn( "$2y$", rounds, entropy, sizeof( entropy ), salt, size ); if( ok == NULL ) { - return 1; + return BC_ERR_API; } return 0; @@ -52,7 +113,7 @@ static int luabcrypt_digest( lua_State * const L ) { int rv_salt = makesalt( newsalt, sizeof( newsalt ), rounds ); if( rv_salt != 0 ) { - lua_pushstring( L, strerror( errno ) ); + lua_pushstring( L, my_strerror( rv_salt ) ); return lua_error( L ); } @@ -84,7 +145,7 @@ static int luabcrypt_salt( lua_State * const L ) { int rv = makesalt( salt, sizeof( salt ), rounds ); if( rv != 0 ) { - lua_pushstring( L, strerror( errno ) ); + lua_pushstring( L, my_strerror( rv ) ); return lua_error( L ); } @@ -111,18 +172,33 @@ static int luabcrypt_verify( lua_State * const L ) { return 1; } +static int luabcrypt_random( lua_State * const L ) { + const char * const path = luaL_checkstring( L, 1 ); + + int rv = open_random( path ); + + if( rv != 0 ) { + lua_pushstring( L, strerror( errno ) ); + + return lua_error( L ); + } + + return 0; +} + static const struct luaL_Reg luabcrypt_lib[] = { { "digest", luabcrypt_digest }, { "salt", luabcrypt_salt }, { "verify", luabcrypt_verify }, + { "random", luabcrypt_random }, { NULL, NULL }, }; LUALIB_API int luaopen_bcrypt( lua_State * const L ) { - urandom = open( "/dev/urandom", O_RDONLY ); + int rv = open_random( DEFAULT_RANDOM ); - if( urandom == -1 ) { - lua_pushstring( L, strerror( errno ) ); + if( rv != 0 ) { + lua_pushstring( L, my_strerror( rv ) ); return lua_error( L ); } diff --git a/tests.lua b/tests.lua @@ -2,23 +2,29 @@ local bcrypt = require( "bcrypt" ) +-- test salt generation local salt = bcrypt.salt( 5 ) assert( salt:len() == 29 ) +-- test verification local digest = bcrypt.digest( salt, salt ) assert( bcrypt.verify( salt, digest ) ) + +-- test verification failure assert( not bcrypt.verify( salt:gsub( "(.)$", function( ch ) return string.char( ( ch:byte() + 1 ) % 255 ) end ), digest ) ) +-- test we can't use a bad entropy source +local ok = pcall( bcrypt.random, "/dev/null" ) +assert( not ok ) + +-- some test inputs, mostly taken from john the ripper local tests = { { "$2y$04$TnjywYklQbbZjdjBgBoA4e9G7RJt9blgMgsCvUvus4Iv4TENB5nHy", "test" }, - - -- rest are taken from John the Ripper - -- http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/john/john/src/BF_fmt.c?rev=HEAD { "$2y$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", "U*U"