php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
openssl_pwhash.c
Go to the documentation of this file.
1/*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Remi Collet <remi@php.net> |
14 +----------------------------------------------------------------------+
15*/
16
17#ifdef HAVE_CONFIG_H
18# include "config.h"
19#endif
20
21#include "php.h"
23#include "php_openssl.h"
24
25#if defined(HAVE_OPENSSL_ARGON2)
28#include <ext/standard/base64.h>
30#include <openssl/params.h>
31#include <openssl/core_names.h>
32#include <openssl/kdf.h>
33#include <openssl/thread.h>
34#include <openssl/rand.h>
35
36#define PHP_OPENSSL_MEMLIMIT_MIN 8u
37#define PHP_OPENSSL_MEMLIMIT_MAX UINT32_MAX
38#define PHP_OPENSSL_ITERLIMIT_MIN 1u
39#define PHP_OPENSSL_ITERLIMIT_MAX UINT32_MAX
40#define PHP_OPENSSL_THREADS_MIN 1u
41#define PHP_OPENSSL_THREADS_MAX UINT32_MAX
42
43#define PHP_OPENSSL_ARGON_VERSION 0x13
44
45#define PHP_OPENSSL_SALT_SIZE 16
46#define PHP_OPENSSL_HASH_SIZE 32
47#define PHP_OPENSSL_DIGEST_SIZE 128
48
49static inline zend_result get_options(zend_array *options, uint32_t *memlimit, uint32_t *iterlimit, uint32_t *threads)
50{
51 zval *opt;
52
53 *iterlimit = PHP_OPENSSL_PWHASH_ITERLIMIT;
54 *memlimit = PHP_OPENSSL_PWHASH_MEMLIMIT;
55 *threads = PHP_OPENSSL_PWHASH_THREADS;
56
57 if (!options) {
58 return SUCCESS;
59 }
60 if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {
61 zend_long smemlimit = zval_get_long(opt);
62
63 if ((smemlimit < 0) || (smemlimit < PHP_OPENSSL_MEMLIMIT_MIN) || (smemlimit > (PHP_OPENSSL_MEMLIMIT_MAX))) {
64 zend_value_error("Memory cost is outside of allowed memory range");
65 return FAILURE;
66 }
67 *memlimit = smemlimit;
68 }
69 if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {
70 zend_long siterlimit = zval_get_long(opt);
71 if ((siterlimit < PHP_OPENSSL_ITERLIMIT_MIN) || (siterlimit > PHP_OPENSSL_ITERLIMIT_MAX)) {
72 zend_value_error("Time cost is outside of allowed time range");
73 return FAILURE;
74 }
75 *iterlimit = siterlimit;
76 }
77 if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {
78 zend_long sthreads = zval_get_long(opt);
79 if ((sthreads < PHP_OPENSSL_THREADS_MIN) || (sthreads > PHP_OPENSSL_THREADS_MAX)) {
80 zend_value_error("Invalid number of threads");
81 return FAILURE;
82 }
83 *threads = sthreads;
84 }
85 return SUCCESS;
86}
87
88static bool php_openssl_argon2_compute_hash(
89 const char *algo,
90 uint32_t version, uint32_t memlimit, uint32_t iterlimit, uint32_t threads,
91 const char *pass, size_t pass_len,
92 const unsigned char *salt, size_t salt_len,
93 unsigned char *hash, size_t hash_len)
94{
95 OSSL_PARAM params[7], *p = params;
96 EVP_KDF *kdf = NULL;
97 EVP_KDF_CTX *kctx = NULL;
98 uint32_t oldthreads;
99 bool ret = false;
100
101 oldthreads = OSSL_get_max_threads(NULL);
102 if (OSSL_set_max_threads(NULL, threads) != 1) {
103 goto fail;
104 }
105 p = params;
106 *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_THREADS, &threads);
107 *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_LANES, &threads);
108 *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iterlimit);
109 *p++ = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ARGON2_MEMCOST, &memlimit);
110 *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, (void *)salt, salt_len);
111 *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, (void *)pass, pass_len);
112 *p++ = OSSL_PARAM_construct_end();
113
114 if ((kdf = EVP_KDF_fetch(NULL, algo, NULL)) == NULL) {
115 goto fail;
116 }
117 if ((kctx = EVP_KDF_CTX_new(kdf)) == NULL) {
118 goto fail;
119 }
120 if (EVP_KDF_derive(kctx, hash, hash_len, params) != 1) {
121 zend_value_error("Unexpected failure hashing password");
122 goto fail;
123 }
124
125 ret = true;
126
127fail:
128 EVP_KDF_free(kdf);
129 EVP_KDF_CTX_free(kctx);
130 OSSL_set_max_threads(NULL, oldthreads);
131
132 return ret;
133}
134
135static zend_string *php_openssl_argon2_hash(const zend_string *password, zend_array *options, const char *algo)
136{
137 uint32_t iterlimit, memlimit, threads, version = PHP_OPENSSL_ARGON_VERSION;
138 zend_string *digest = NULL, *salt64 = NULL, *hash64 = NULL;
139 unsigned char hash[PHP_OPENSSL_HASH_SIZE+1], salt[PHP_OPENSSL_SALT_SIZE+1];
140
141 if ((ZSTR_LEN(password) >= UINT32_MAX)) {
142 zend_value_error("Password is too long");
143 return NULL;
144 }
145 if (get_options(options, &memlimit, &iterlimit, &threads) == FAILURE) {
146 return NULL;
147 }
148 if (RAND_bytes(salt, PHP_OPENSSL_SALT_SIZE) <= 0) {
149 return NULL;
150 }
151
152 if (!php_openssl_argon2_compute_hash(algo, version, memlimit, iterlimit, threads,
153 ZSTR_VAL(password), ZSTR_LEN(password), salt, PHP_OPENSSL_SALT_SIZE, hash, PHP_OPENSSL_HASH_SIZE)) {
154 return NULL;
155 }
156
157 hash64 = php_base64_encode_ex(hash, PHP_OPENSSL_HASH_SIZE, PHP_BASE64_NO_PADDING);
158
159 salt64 = php_base64_encode_ex(salt, PHP_OPENSSL_SALT_SIZE, PHP_BASE64_NO_PADDING);
160
161 digest = zend_string_alloc(PHP_OPENSSL_DIGEST_SIZE, 0);
162 ZSTR_LEN(digest) = snprintf(ZSTR_VAL(digest), ZSTR_LEN(digest), "$%s$v=%d$m=%u,t=%u,p=%u$%s$%s",
163 algo, version, memlimit, iterlimit, threads, ZSTR_VAL(salt64), ZSTR_VAL(hash64));
164
165 zend_string_release(salt64);
166 zend_string_release(hash64);
167
168 return digest;
169}
170
171static int php_openssl_argon2_extract(
172 const zend_string *digest, uint32_t *version, uint32_t *memlimit, uint32_t *iterlimit,
173 uint32_t *threads, zend_string **salt, zend_string **hash)
174{
175 const char *p;
176 char *hash64, *salt64;
177
178 if (!digest || (ZSTR_LEN(digest) < sizeof("$argon2id$"))) {
179 return FAILURE;
180 }
181 p = ZSTR_VAL(digest);
182 if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) {
183 p += strlen("$argon2i$");
184 } else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) {
185 p += strlen("$argon2id$");
186 } else {
187 return FAILURE;
188 }
189 if (sscanf(p, "v=%" PRIu32 "$m=%" PRIu32 ",t=%" PRIu32 ",p=%" PRIu32,
190 version, memlimit, iterlimit, threads) != 4) {
191 return FAILURE;
192 }
193 if (salt && hash) {
194 /* start of param */
195 p = strchr(p, '$');
196 if (!p) {
197 return FAILURE;
198 }
199 /* start of salt */
200 p = strchr(p+1, '$');
201 if (!p) {
202 return FAILURE;
203 }
204 salt64 = estrdup(p+1);
205 /* start of hash */
206 hash64 = strchr(salt64, '$');
207 if (!hash64) {
208 efree(salt64);
209 return FAILURE;
210 }
211 *hash64++ = 0;
212 *salt = php_base64_decode((unsigned char *)salt64, strlen(salt64));
213 *hash = php_base64_decode((unsigned char *)hash64, strlen(hash64));
214 efree(salt64);
215 }
216 return SUCCESS;
217}
218
219static bool php_openssl_argon2_verify(const zend_string *password, const zend_string *digest, const char *algo)
220{
221 uint32_t version, iterlimit, memlimit, threads;
222 zend_string *salt, *hash, *new;
223 bool ret = false;
224
225 if ((ZSTR_LEN(password) >= UINT32_MAX) || (ZSTR_LEN(digest) >= UINT32_MAX)) {
226 return false;
227 }
228 if (FAILURE == php_openssl_argon2_extract(digest, &version, &memlimit, &iterlimit, &threads, &salt, &hash)) {
229 return false;
230 }
231
232 new = zend_string_alloc(ZSTR_LEN(hash), 0);
233 if (php_openssl_argon2_compute_hash(algo, version, memlimit, iterlimit, threads,
234 ZSTR_VAL(password), ZSTR_LEN(password), (unsigned char *)ZSTR_VAL(salt),
235 ZSTR_LEN(salt), (unsigned char *)ZSTR_VAL(new), ZSTR_LEN(new))) {
236 ret = (php_safe_bcmp(hash, new) == 0);
237 }
238
239 zend_string_release(new);
240 zend_string_release(salt);
241 zend_string_release(hash);
242
243 return ret;
244}
245
246static bool php_openssl_argon2i_verify(const zend_string *password, const zend_string *digest)
247{
248 return php_openssl_argon2_verify(password, digest, "argon2i");
249}
250
251static bool php_openssl_argon2id_verify(const zend_string *password, const zend_string *digest)
252{
253 return php_openssl_argon2_verify(password, digest, "argon2id");
254}
255
256static bool php_openssl_argon2_needs_rehash(const zend_string *hash, zend_array *options)
257{
258 uint32_t version, iterlimit, memlimit, threads;
259 uint32_t new_version = PHP_OPENSSL_ARGON_VERSION, new_iterlimit, new_memlimit, new_threads;
260
261 if (FAILURE == get_options(options, &new_memlimit, &new_iterlimit, &new_threads)) {
262 return true;
263 }
264 if (FAILURE == php_openssl_argon2_extract(hash, &version, &memlimit, &iterlimit, &threads, NULL, NULL)) {
265 return true;
266 }
267
268 // Algo already checked in pasword_needs_rehash implementation
269 return (version != new_version) ||
270 (iterlimit != new_iterlimit) ||
271 (memlimit != new_memlimit) ||
272 (threads != new_threads);
273}
274
275static int php_openssl_argon2_get_info(zval *return_value, const zend_string *hash)
276{
277 uint32_t v, threads;
278 uint32_t memory_cost;
279 uint32_t time_cost;
280
281 if (FAILURE == php_openssl_argon2_extract(hash, &v, &memory_cost, &time_cost, &threads, NULL, NULL)) {
282 return FAILURE;
283 }
284 add_assoc_long(return_value, "memory_cost", memory_cost);
285 add_assoc_long(return_value, "time_cost", time_cost);
286 add_assoc_long(return_value, "threads", threads);
287
288 return SUCCESS;
289}
290
291
292static zend_string *php_openssl_argon2i_hash(const zend_string *password, zend_array *options)
293{
294 return php_openssl_argon2_hash(password, options, "argon2i");
295}
296
297static const php_password_algo openssl_algo_argon2i = {
298 "argon2i",
299 php_openssl_argon2i_hash,
300 php_openssl_argon2i_verify,
301 php_openssl_argon2_needs_rehash,
302 php_openssl_argon2_get_info,
303 NULL,
304};
305
306static zend_string *php_openssl_argon2id_hash(const zend_string *password, zend_array *options)
307{
308 return php_openssl_argon2_hash(password, options, "argon2id");
309}
310
311static const php_password_algo openssl_algo_argon2id = {
312 "argon2id",
313 php_openssl_argon2id_hash,
314 php_openssl_argon2id_verify,
315 php_openssl_argon2_needs_rehash,
316 php_openssl_argon2_get_info,
317 NULL,
318};
319
321{
322 zend_string *password, *algo, *digest;
324
326 Z_PARAM_STR(algo)
327 Z_PARAM_STR(password)
331
332 if (strcmp(ZSTR_VAL(algo), "argon2i") && strcmp(ZSTR_VAL(algo), "argon2id")) {
333 zend_argument_value_error(1, "must be a valid password openssl hashing algorithm");
335 }
336
337 digest = php_openssl_argon2_hash(password, options, ZSTR_VAL(algo));
338 if (!digest) {
339 if (!EG(exception)) {
340 zend_throw_error(NULL, "Password hashing failed for unknown reason");
341 }
343 }
344
345 RETURN_NEW_STR(digest);
346}
347
349{
350 zend_string *password, *algo, *digest;
351
353 Z_PARAM_STR(algo)
354 Z_PARAM_STR(password)
355 Z_PARAM_STR(digest)
357
358 if (strcmp(ZSTR_VAL(algo), "argon2i") && strcmp(ZSTR_VAL(algo), "argon2id")) {
359 zend_argument_value_error(1, "must be a valid password openssl hashing algorithm");
361 }
362
363 RETURN_BOOL(php_openssl_argon2_verify(password, digest, ZSTR_VAL(algo)));
364}
365
366PHP_MINIT_FUNCTION(openssl_pwhash)
367{
368 zend_string *argon2i = ZSTR_INIT_LITERAL("argon2i", 1);
369
370 if (php_password_algo_find(argon2i)) {
371 /* Nothing to do. Core or sodium has registered these algorithms for us. */
372 zend_string_release(argon2i);
373 return SUCCESS;
374 }
375 zend_string_release(argon2i);
376
377 register_openssl_pwhash_symbols(module_number);
378
379 if (FAILURE == php_password_algo_register("argon2i", &openssl_algo_argon2i)) {
380 return FAILURE;
381 }
382 if (FAILURE == php_password_algo_register("argon2id", &openssl_algo_argon2id)) {
383 return FAILURE;
384 }
385
386 return SUCCESS;
387}
388#endif /* HAVE_OPENSSL_ARGON2 */
bool exception
Definition assert.c:30
PHPAPI zend_string * php_base64_encode_ex(const unsigned char *str, size_t length, zend_long flags)
Definition base64.c:1189
#define PHP_BASE64_NO_PADDING
Definition base64.h:65
sscanf(string $string, string $format, mixed &... $vars)
strchr(string $haystack, string $needle, bool $before_needle=false)
uint32_t v
Definition cdf.c:1237
#define NULL
Definition gdcache.h:45
hash(string $algo, string $data, bool $binary=false, array $options=[])
Definition hash.stub.php:12
#define SUCCESS
Definition hash_sha3.c:261
#define pass(a, b, c, mul)
Definition hash_tiger.c:50
foreach($dp as $el) foreach( $dp as $el) if( $pass2< 2) echo ""
openssl_password_verify(string $algo, #[\SensitiveParameter] string $password, string $hash)
openssl_password_hash(string $algo, #[\SensitiveParameter] string $password, array $options=[])
const php_password_algo * php_password_algo_find(const zend_string *ident)
Definition password.c:459
int php_password_algo_register(const char *ident, const php_password_algo *algo)
Definition password.c:40
#define PHP_FUNCTION
Definition php.h:364
#define PHP_MINIT_FUNCTION
Definition php.h:400
PHPAPI int php_safe_bcmp(const zend_string *a, const zend_string *b)
Definition safe_bcmp.c:26
PHP_JSON_API size_t int options
Definition php_json.h:102
#define UINT32_MAX
struct _php_password_algo php_password_algo
bool fail
Definition session.c:1065
p
Definition session.c:1105
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format,...)
Definition zend.c:1772
ZEND_API ZEND_COLD void zend_value_error(const char *format,...)
Definition zend.c:1849
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format,...)
Definition zend_API.c:433
#define ZEND_PARSE_PARAMETERS_END()
Definition zend_API.h:1641
#define Z_PARAM_OPTIONAL
Definition zend_API.h:1667
#define Z_PARAM_STR(dest)
Definition zend_API.h:2086
#define ZEND_PARSE_PARAMETERS_START(min_num_args, max_num_args)
Definition zend_API.h:1620
#define RETURN_BOOL(b)
Definition zend_API.h:1035
#define RETURN_NEW_STR(s)
Definition zend_API.h:1041
#define RETURN_THROWS()
Definition zend_API.h:1060
#define Z_PARAM_ARRAY_HT(dest)
Definition zend_API.h:1852
#define efree(ptr)
Definition zend_alloc.h:155
#define estrdup(s)
Definition zend_alloc.h:164
struct _zval_struct zval
strlen(string $string)
strcmp(string $string1, string $string2)
#define snprintf
#define EG(v)
ZEND_API zval *ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *str, size_t len)
Definition zend_hash.c:2689
int32_t zend_long
Definition zend_long.h:42
struct _zend_string zend_string
struct _zend_array zend_array
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_INIT_LITERAL(s, persistent)
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
@ FAILURE
Definition zend_types.h:61
ZEND_RESULT_CODE zend_result
Definition zend_types.h:64
zval * return_value
zval * ret