Provide argon2i(d) password hashing from sodium when needed · php/php-src@0ba1db7

1+

/*

2+

+----------------------------------------------------------------------+

3+

| PHP Version 7 |

4+

+----------------------------------------------------------------------+

5+

| Copyright (c) The PHP Group |

6+

+----------------------------------------------------------------------+

7+

| This source file is subject to version 3.01 of the PHP license, |

8+

| that is bundled with this package in the file LICENSE, and is |

9+

| available through the world-wide-web at the following url: |

10+

| http://www.php.net/license/3_01.txt |

11+

| If you did not receive a copy of the PHP license and are unable to |

12+

| obtain it through the world-wide-web, please send a note to |

13+

| license@php.net so we can mail you a copy immediately. |

14+

+----------------------------------------------------------------------+

15+

| Authors: Sara Golemon <pollita@php.net> |

16+

+----------------------------------------------------------------------+

17+

*/

18+19+

#ifdef HAVE_CONFIG_H

20+

# include "config.h"

21+

#endif

22+23+

#include "php.h"

24+

#include "php_libsodium.h"

25+

#include "ext/standard/php_password.h"

26+27+

#include <sodium.h>

28+29+

#if SODIUM_LIBRARY_VERSION_MAJOR > 9 || (SODIUM_LIBRARY_VERSION_MAJOR == 9 && SODIUM_LIBRARY_VERSION_MINOR >= 6)

30+31+

/**

32+

* OPSLIMIT and MEMLIMIT are taken from libsodium's MODERATE values.

33+

* MEMLIMIT is normalized to KB even though sodium uses Bytes in order to

34+

* present a consistent user-facing API.

35+

*

36+

* Threads are fixed at 1 by libsodium.

37+

*

38+

* When updating these values, synchronize ext/standard/php_password.h values.

39+

*/

40+

#define PHP_SODIUM_PWHASH_MEMLIMIT (256 << 10)

41+

#define PHP_SODIUM_PWHASH_OPSLIMIT 3

42+

#define PHP_SODIUM_PWHASH_THREADS 1

43+44+

static zend_string *php_sodium_argon2_hash(const zend_string *password, zend_array *options, int alg) {

45+

size_t opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;

46+

size_t memlimit = PHP_SODIUM_PWHASH_MEMLIMIT;

47+

zend_string *ret;

48+49+

if ((ZSTR_LEN(password) >= 0xffffffff)) {

50+

php_error_docref(NULL, E_WARNING, "Password is too long");

51+

return NULL;

52+

}

53+54+

if (options) {

55+

zval *opt;

56+

if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {

57+

memlimit = zval_get_long(opt);

58+

if ((memlimit < crypto_pwhash_MEMLIMIT_MIN) || (memlimit > crypto_pwhash_MEMLIMIT_MAX)) {

59+

php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");

60+

return NULL;

61+

}

62+

}

63+

if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {

64+

opslimit = zval_get_long(opt);

65+

if ((opslimit < crypto_pwhash_OPSLIMIT_MIN) || (opslimit > crypto_pwhash_OPSLIMIT_MAX)) {

66+

php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");

67+

return NULL;

68+

}

69+

}

70+

if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {

71+

php_error_docref(NULL, E_WARNING, "A thread value other than 1 is not supported by this implementation");

72+

return NULL;

73+

}

74+

}

75+76+

ret = zend_string_alloc(crypto_pwhash_STRBYTES - 1, 0);

77+

if (crypto_pwhash_str_alg(ZSTR_VAL(ret), ZSTR_VAL(password), ZSTR_LEN(password), opslimit, memlimit << 10, alg)) {

78+

php_error_docref(NULL, E_WARNING, "Unexpected failure hashing password");

79+

zend_string_release(ret);

80+

return NULL;

81+

}

82+83+

ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));

84+

ZSTR_VAL(ret)[ZSTR_LEN(ret)] = 0;

85+86+

return ret;

87+

}

88+89+

static zend_bool php_sodium_argon2_verify(const zend_string *password, const zend_string *hash) {

90+

if ((ZSTR_LEN(password) >= 0xffffffff) || (ZSTR_LEN(hash) >= 0xffffffff)) {

91+

return 0;

92+

}

93+

return crypto_pwhash_str_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password)) == 0;

94+

}

95+96+

static zend_bool php_sodium_argon2_needs_rehash(const zend_string *hash, zend_array *options) {

97+

size_t opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;

98+

size_t memlimit = PHP_SODIUM_PWHASH_MEMLIMIT;

99+100+

if (options) {

101+

zval *opt;

102+

if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {

103+

memlimit = zval_get_long(opt);

104+

if ((memlimit < crypto_pwhash_MEMLIMIT_MIN) || (memlimit > crypto_pwhash_MEMLIMIT_MAX)) {

105+

php_error_docref(NULL, E_WARNING, "Memory cost is outside of allowed memory range");

106+

return 1;

107+

}

108+

}

109+

if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {

110+

opslimit = zval_get_long(opt);

111+

if ((opslimit < crypto_pwhash_OPSLIMIT_MIN) || (opslimit > crypto_pwhash_OPSLIMIT_MAX)) {

112+

php_error_docref(NULL, E_WARNING, "Time cost is outside of allowed time range");

113+

return 1;

114+

}

115+

}

116+

if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {

117+

php_error_docref(NULL, E_WARNING, "A thread value other than 1 is not supported by this implementation");

118+

return 1;

119+

}

120+

}

121+122+

return crypto_pwhash_str_needs_rehash(ZSTR_VAL(hash), opslimit, memlimit << 10);

123+

}

124+125+

static int php_sodium_argon2_get_info(zval *return_value, const zend_string *hash) {

126+

const char* p = NULL;

127+

zend_long v = 0, threads = 1;

128+

zend_long memory_cost = PHP_SODIUM_PWHASH_MEMLIMIT;

129+

zend_long time_cost = PHP_SODIUM_PWHASH_OPSLIMIT;

130+131+

if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {

132+

return FAILURE;

133+

}

134+135+

p = ZSTR_VAL(hash);

136+

if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) {

137+

p += strlen("$argon2i$");

138+

} else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) {

139+

p += strlen("$argon2id$");

140+

} else {

141+

return FAILURE;

142+

}

143+144+

sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,

145+

&v, &memory_cost, &time_cost, &threads);

146+147+

add_assoc_long(return_value, "memory_cost", memory_cost);

148+

add_assoc_long(return_value, "time_cost", time_cost);

149+

add_assoc_long(return_value, "threads", threads);

150+151+

return SUCCESS;

152+

}

153+154+

/* argon2i specific methods */

155+156+

static zend_string *php_sodium_argon2i_hash(const zend_string *password, zend_array *options) {

157+

return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2I13);

158+

}

159+160+

static const php_password_algo sodium_algo_argon2i = {

161+

"argon2i",

162+

php_sodium_argon2i_hash,

163+

php_sodium_argon2_verify,

164+

php_sodium_argon2_needs_rehash,

165+

php_sodium_argon2_get_info,

166+

NULL,

167+

};

168+169+

/* argon2id specific methods */

170+171+

static zend_string *php_sodium_argon2id_hash(const zend_string *password, zend_array *options) {

172+

return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2ID13);

173+

}

174+175+

static const php_password_algo sodium_algo_argon2id = {

176+

"argon2id",

177+

php_sodium_argon2id_hash,

178+

php_sodium_argon2_verify,

179+

php_sodium_argon2_needs_rehash,

180+

php_sodium_argon2_get_info,

181+

NULL,

182+

};

183+184+

PHP_MINIT_FUNCTION(sodium_password_hash) /* {{{ */ {

185+

zend_string *argon2i = zend_string_init("argon2i", strlen("argon2i"), 1);

186+187+

if (php_password_algo_find(argon2i)) {

188+

/* Nothing to do. Core has registered these algorithms for us. */

189+

zend_string_release(argon2i);

190+

return SUCCESS;

191+

}

192+

zend_string_release(argon2i);

193+194+

if (FAILURE == php_password_algo_register("argon2i", &sodium_algo_argon2i)) {

195+

return FAILURE;

196+

}

197+

REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_CS | CONST_PERSISTENT);

198+199+

if (FAILURE == php_password_algo_register("argon2id", &sodium_algo_argon2id)) {

200+

return FAILURE;

201+

}

202+

REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_CS | CONST_PERSISTENT);

203+204+

REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_SODIUM_PWHASH_MEMLIMIT, CONST_CS | CONST_PERSISTENT);

205+

REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_SODIUM_PWHASH_OPSLIMIT, CONST_CS | CONST_PERSISTENT);

206+

REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_SODIUM_PWHASH_THREADS, CONST_CS | CONST_PERSISTENT);

207+208+

REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "sodium", CONST_CS | CONST_PERSISTENT);

209+210+

return SUCCESS;

211+

}

212+

/* }}} */

213+214+215+

#endif /* HAVE_SODIUM_PASSWORD_HASH */