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 */