password.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685
  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: Anthony Ferrara <ircmaxell@php.net> |
  14. | Charles R. Portwood II <charlesportwoodii@erianna.com> |
  15. +----------------------------------------------------------------------+
  16. */
  17. #include <stdlib.h>
  18. #include "php.h"
  19. #include "fcntl.h"
  20. #include "php_password.h"
  21. #include "php_rand.h"
  22. #include "php_crypt.h"
  23. #include "base64.h"
  24. #include "zend_interfaces.h"
  25. #include "info.h"
  26. #include "php_random.h"
  27. #if HAVE_ARGON2LIB
  28. #include "argon2.h"
  29. #endif
  30. #ifdef PHP_WIN32
  31. #include "win32/winutil.h"
  32. #endif
  33. static zend_array php_password_algos;
  34. int php_password_algo_register(const char *ident, const php_password_algo *algo) {
  35. zend_string *key = zend_string_init_interned(ident, strlen(ident), 1);
  36. return zend_hash_add_ptr(&php_password_algos, key, (void *) algo) ? SUCCESS : FAILURE;
  37. }
  38. void php_password_algo_unregister(const char *ident) {
  39. zend_hash_str_del(&php_password_algos, ident, strlen(ident));
  40. }
  41. static int php_password_salt_to64(const char *str, const size_t str_len, const size_t out_len, char *ret) /* {{{ */
  42. {
  43. size_t pos = 0;
  44. zend_string *buffer;
  45. if ((int) str_len < 0) {
  46. return FAILURE;
  47. }
  48. buffer = php_base64_encode((unsigned char*) str, str_len);
  49. if (ZSTR_LEN(buffer) < out_len) {
  50. /* Too short of an encoded string generated */
  51. zend_string_release_ex(buffer, 0);
  52. return FAILURE;
  53. }
  54. for (pos = 0; pos < out_len; pos++) {
  55. if (ZSTR_VAL(buffer)[pos] == '+') {
  56. ret[pos] = '.';
  57. } else if (ZSTR_VAL(buffer)[pos] == '=') {
  58. zend_string_free(buffer);
  59. return FAILURE;
  60. } else {
  61. ret[pos] = ZSTR_VAL(buffer)[pos];
  62. }
  63. }
  64. zend_string_free(buffer);
  65. return SUCCESS;
  66. }
  67. /* }}} */
  68. static zend_string* php_password_make_salt(size_t length) /* {{{ */
  69. {
  70. zend_string *ret, *buffer;
  71. if (length > (INT_MAX / 3)) {
  72. zend_value_error("Length is too large to safely generate");
  73. return NULL;
  74. }
  75. buffer = zend_string_alloc(length * 3 / 4 + 1, 0);
  76. if (FAILURE == php_random_bytes_silent(ZSTR_VAL(buffer), ZSTR_LEN(buffer))) {
  77. zend_value_error("Unable to generate salt");
  78. zend_string_release_ex(buffer, 0);
  79. return NULL;
  80. }
  81. ret = zend_string_alloc(length, 0);
  82. if (php_password_salt_to64(ZSTR_VAL(buffer), ZSTR_LEN(buffer), length, ZSTR_VAL(ret)) == FAILURE) {
  83. zend_value_error("Generated salt too short");
  84. zend_string_release_ex(buffer, 0);
  85. zend_string_release_ex(ret, 0);
  86. return NULL;
  87. }
  88. zend_string_release_ex(buffer, 0);
  89. ZSTR_VAL(ret)[length] = 0;
  90. return ret;
  91. }
  92. /* }}} */
  93. static zend_string* php_password_get_salt(zval *unused_, size_t required_salt_len, HashTable *options) {
  94. if (options && zend_hash_str_exists(options, "salt", sizeof("salt") - 1)) {
  95. php_error_docref(NULL, E_WARNING, "The \"salt\" option has been ignored, since providing a custom salt is no longer supported");
  96. }
  97. return php_password_make_salt(required_salt_len);
  98. }
  99. /* bcrypt implementation */
  100. static bool php_password_bcrypt_valid(const zend_string *hash) {
  101. const char *h = ZSTR_VAL(hash);
  102. return (ZSTR_LEN(hash) == 60) &&
  103. (h[0] == '$') && (h[1] == '2') && (h[2] == 'y');
  104. }
  105. static int php_password_bcrypt_get_info(zval *return_value, const zend_string *hash) {
  106. zend_long cost = PHP_PASSWORD_BCRYPT_COST;
  107. if (!php_password_bcrypt_valid(hash)) {
  108. /* Should never get called this way. */
  109. return FAILURE;
  110. }
  111. sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &cost);
  112. add_assoc_long(return_value, "cost", cost);
  113. return SUCCESS;
  114. }
  115. static bool php_password_bcrypt_needs_rehash(const zend_string *hash, zend_array *options) {
  116. zval *znew_cost;
  117. zend_long old_cost = PHP_PASSWORD_BCRYPT_COST;
  118. zend_long new_cost = PHP_PASSWORD_BCRYPT_COST;
  119. if (!php_password_bcrypt_valid(hash)) {
  120. /* Should never get called this way. */
  121. return 1;
  122. }
  123. sscanf(ZSTR_VAL(hash), "$2y$" ZEND_LONG_FMT "$", &old_cost);
  124. if (options && (znew_cost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
  125. new_cost = zval_get_long(znew_cost);
  126. }
  127. return old_cost != new_cost;
  128. }
  129. static bool php_password_bcrypt_verify(const zend_string *password, const zend_string *hash) {
  130. size_t i;
  131. int status = 0;
  132. zend_string *ret = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
  133. if (!ret) {
  134. return 0;
  135. }
  136. if (ZSTR_LEN(ret) != ZSTR_LEN(hash) || ZSTR_LEN(hash) < 13) {
  137. zend_string_free(ret);
  138. return 0;
  139. }
  140. /* We're using this method instead of == in order to provide
  141. * resistance towards timing attacks. This is a constant time
  142. * equality check that will always check every byte of both
  143. * values. */
  144. for (i = 0; i < ZSTR_LEN(hash); i++) {
  145. status |= (ZSTR_VAL(ret)[i] ^ ZSTR_VAL(hash)[i]);
  146. }
  147. zend_string_free(ret);
  148. return status == 0;
  149. }
  150. static zend_string* php_password_bcrypt_hash(const zend_string *password, zend_array *options) {
  151. char hash_format[10];
  152. size_t hash_format_len;
  153. zend_string *result, *hash, *salt;
  154. zval *zcost;
  155. zend_long cost = PHP_PASSWORD_BCRYPT_COST;
  156. if (options && (zcost = zend_hash_str_find(options, "cost", sizeof("cost")-1)) != NULL) {
  157. cost = zval_get_long(zcost);
  158. }
  159. if (cost < 4 || cost > 31) {
  160. zend_value_error("Invalid bcrypt cost parameter specified: " ZEND_LONG_FMT, cost);
  161. return NULL;
  162. }
  163. hash_format_len = snprintf(hash_format, sizeof(hash_format), "$2y$%02" ZEND_LONG_FMT_SPEC "$", cost);
  164. if (!(salt = php_password_get_salt(NULL, Z_UL(22), options))) {
  165. return NULL;
  166. }
  167. ZSTR_VAL(salt)[ZSTR_LEN(salt)] = 0;
  168. hash = zend_string_alloc(ZSTR_LEN(salt) + hash_format_len, 0);
  169. sprintf(ZSTR_VAL(hash), "%s%s", hash_format, ZSTR_VAL(salt));
  170. ZSTR_VAL(hash)[hash_format_len + ZSTR_LEN(salt)] = 0;
  171. zend_string_release_ex(salt, 0);
  172. /* This cast is safe, since both values are defined here in code and cannot overflow */
  173. result = php_crypt(ZSTR_VAL(password), (int)ZSTR_LEN(password), ZSTR_VAL(hash), (int)ZSTR_LEN(hash), 1);
  174. zend_string_release_ex(hash, 0);
  175. if (!result) {
  176. return NULL;
  177. }
  178. if (ZSTR_LEN(result) < 13) {
  179. zend_string_free(result);
  180. return NULL;
  181. }
  182. return result;
  183. }
  184. const php_password_algo php_password_algo_bcrypt = {
  185. "bcrypt",
  186. php_password_bcrypt_hash,
  187. php_password_bcrypt_verify,
  188. php_password_bcrypt_needs_rehash,
  189. php_password_bcrypt_get_info,
  190. php_password_bcrypt_valid,
  191. };
  192. #if HAVE_ARGON2LIB
  193. /* argon2i/argon2id shared implementation */
  194. static int extract_argon2_parameters(const zend_string *hash,
  195. zend_long *v, zend_long *memory_cost,
  196. zend_long *time_cost, zend_long *threads) /* {{{ */
  197. {
  198. const char *p = ZSTR_VAL(hash);
  199. if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {
  200. return FAILURE;
  201. }
  202. if (!memcmp(p, "$argon2i$", sizeof("$argon2i$") - 1)) {
  203. p += sizeof("$argon2i$") - 1;
  204. } else if (!memcmp(p, "$argon2id$", sizeof("$argon2id$") - 1)) {
  205. p += sizeof("$argon2id$") - 1;
  206. } else {
  207. return FAILURE;
  208. }
  209. sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,
  210. v, memory_cost, time_cost, threads);
  211. return SUCCESS;
  212. }
  213. /* }}} */
  214. static int php_password_argon2_get_info(zval *return_value, const zend_string *hash) {
  215. zend_long v = 0;
  216. zend_long memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
  217. zend_long time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
  218. zend_long threads = PHP_PASSWORD_ARGON2_THREADS;
  219. extract_argon2_parameters(hash, &v, &memory_cost, &time_cost, &threads);
  220. add_assoc_long(return_value, "memory_cost", memory_cost);
  221. add_assoc_long(return_value, "time_cost", time_cost);
  222. add_assoc_long(return_value, "threads", threads);
  223. return SUCCESS;
  224. }
  225. static bool php_password_argon2_needs_rehash(const zend_string *hash, zend_array *options) {
  226. zend_long v = 0;
  227. zend_long new_memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST, memory_cost = 0;
  228. zend_long new_time_cost = PHP_PASSWORD_ARGON2_TIME_COST, time_cost = 0;
  229. zend_long new_threads = PHP_PASSWORD_ARGON2_THREADS, threads = 0;
  230. zval *option_buffer;
  231. if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
  232. new_memory_cost = zval_get_long(option_buffer);
  233. }
  234. if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
  235. new_time_cost = zval_get_long(option_buffer);
  236. }
  237. if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
  238. new_threads = zval_get_long(option_buffer);
  239. }
  240. extract_argon2_parameters(hash, &v, &memory_cost, &time_cost, &threads);
  241. return (new_time_cost != time_cost) ||
  242. (new_memory_cost != memory_cost) ||
  243. (new_threads != threads);
  244. }
  245. static zend_string *php_password_argon2_hash(const zend_string *password, zend_array *options, argon2_type type) {
  246. zval *option_buffer;
  247. zend_string *salt, *out, *encoded;
  248. size_t time_cost = PHP_PASSWORD_ARGON2_TIME_COST;
  249. size_t memory_cost = PHP_PASSWORD_ARGON2_MEMORY_COST;
  250. size_t threads = PHP_PASSWORD_ARGON2_THREADS;
  251. size_t encoded_len;
  252. int status = 0;
  253. if (options && (option_buffer = zend_hash_str_find(options, "memory_cost", sizeof("memory_cost")-1)) != NULL) {
  254. memory_cost = zval_get_long(option_buffer);
  255. }
  256. if (memory_cost > ARGON2_MAX_MEMORY || memory_cost < ARGON2_MIN_MEMORY) {
  257. zend_value_error("Memory cost is outside of allowed memory range");
  258. return NULL;
  259. }
  260. if (options && (option_buffer = zend_hash_str_find(options, "time_cost", sizeof("time_cost")-1)) != NULL) {
  261. time_cost = zval_get_long(option_buffer);
  262. }
  263. if (time_cost > ARGON2_MAX_TIME || time_cost < ARGON2_MIN_TIME) {
  264. zend_value_error("Time cost is outside of allowed time range");
  265. return NULL;
  266. }
  267. if (options && (option_buffer = zend_hash_str_find(options, "threads", sizeof("threads")-1)) != NULL) {
  268. threads = zval_get_long(option_buffer);
  269. }
  270. if (threads > ARGON2_MAX_LANES || threads == 0) {
  271. zend_value_error("Invalid number of threads");
  272. return NULL;
  273. }
  274. if (!(salt = php_password_get_salt(NULL, Z_UL(16), options))) {
  275. return NULL;
  276. }
  277. out = zend_string_alloc(32, 0);
  278. encoded_len = argon2_encodedlen(
  279. time_cost,
  280. memory_cost,
  281. threads,
  282. (uint32_t)ZSTR_LEN(salt),
  283. ZSTR_LEN(out),
  284. type
  285. );
  286. encoded = zend_string_alloc(encoded_len - 1, 0);
  287. status = argon2_hash(
  288. time_cost,
  289. memory_cost,
  290. threads,
  291. ZSTR_VAL(password),
  292. ZSTR_LEN(password),
  293. ZSTR_VAL(salt),
  294. ZSTR_LEN(salt),
  295. ZSTR_VAL(out),
  296. ZSTR_LEN(out),
  297. ZSTR_VAL(encoded),
  298. encoded_len,
  299. type,
  300. ARGON2_VERSION_NUMBER
  301. );
  302. zend_string_release_ex(out, 0);
  303. zend_string_release_ex(salt, 0);
  304. if (status != ARGON2_OK) {
  305. zend_string_efree(encoded);
  306. zend_value_error("%s", argon2_error_message(status));
  307. return NULL;
  308. }
  309. ZSTR_VAL(encoded)[ZSTR_LEN(encoded)] = 0;
  310. return encoded;
  311. }
  312. /* argon2i specific methods */
  313. static bool php_password_argon2i_verify(const zend_string *password, const zend_string *hash) {
  314. return ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_i);
  315. }
  316. static zend_string *php_password_argon2i_hash(const zend_string *password, zend_array *options) {
  317. return php_password_argon2_hash(password, options, Argon2_i);
  318. }
  319. const php_password_algo php_password_algo_argon2i = {
  320. "argon2i",
  321. php_password_argon2i_hash,
  322. php_password_argon2i_verify,
  323. php_password_argon2_needs_rehash,
  324. php_password_argon2_get_info,
  325. NULL,
  326. };
  327. /* argon2id specific methods */
  328. static bool php_password_argon2id_verify(const zend_string *password, const zend_string *hash) {
  329. return ARGON2_OK == argon2_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password), Argon2_id);
  330. }
  331. static zend_string *php_password_argon2id_hash(const zend_string *password, zend_array *options) {
  332. return php_password_argon2_hash(password, options, Argon2_id);
  333. }
  334. const php_password_algo php_password_algo_argon2id = {
  335. "argon2id",
  336. php_password_argon2id_hash,
  337. php_password_argon2id_verify,
  338. php_password_argon2_needs_rehash,
  339. php_password_argon2_get_info,
  340. NULL,
  341. };
  342. #endif
  343. PHP_MINIT_FUNCTION(password) /* {{{ */
  344. {
  345. zend_hash_init(&php_password_algos, 4, NULL, ZVAL_PTR_DTOR, 1);
  346. REGISTER_STRING_CONSTANT("PASSWORD_DEFAULT", "2y", CONST_CS | CONST_PERSISTENT);
  347. if (FAILURE == php_password_algo_register("2y", &php_password_algo_bcrypt)) {
  348. return FAILURE;
  349. }
  350. REGISTER_STRING_CONSTANT("PASSWORD_BCRYPT", "2y", CONST_CS | CONST_PERSISTENT);
  351. #if HAVE_ARGON2LIB
  352. if (FAILURE == php_password_algo_register("argon2i", &php_password_algo_argon2i)) {
  353. return FAILURE;
  354. }
  355. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_CS | CONST_PERSISTENT);
  356. if (FAILURE == php_password_algo_register("argon2id", &php_password_algo_argon2id)) {
  357. return FAILURE;
  358. }
  359. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_CS | CONST_PERSISTENT);
  360. #endif
  361. REGISTER_LONG_CONSTANT("PASSWORD_BCRYPT_DEFAULT_COST", PHP_PASSWORD_BCRYPT_COST, CONST_CS | CONST_PERSISTENT);
  362. #if HAVE_ARGON2LIB
  363. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_PASSWORD_ARGON2_MEMORY_COST, CONST_CS | CONST_PERSISTENT);
  364. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_PASSWORD_ARGON2_TIME_COST, CONST_CS | CONST_PERSISTENT);
  365. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_PASSWORD_ARGON2_THREADS, CONST_CS | CONST_PERSISTENT);
  366. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "standard", CONST_CS | CONST_PERSISTENT);
  367. #endif
  368. return SUCCESS;
  369. }
  370. /* }}} */
  371. PHP_MSHUTDOWN_FUNCTION(password) /* {{{ */
  372. {
  373. #ifdef ZTS
  374. if (!tsrm_is_main_thread()) {
  375. return SUCCESS;
  376. }
  377. #endif
  378. zend_hash_destroy(&php_password_algos);
  379. return SUCCESS;
  380. }
  381. /* }}} */
  382. const php_password_algo* php_password_algo_default(void) {
  383. return &php_password_algo_bcrypt;
  384. }
  385. const php_password_algo* php_password_algo_find(const zend_string *ident) {
  386. zval *tmp;
  387. if (!ident) {
  388. return NULL;
  389. }
  390. tmp = zend_hash_find(&php_password_algos, (zend_string*)ident);
  391. if (!tmp || (Z_TYPE_P(tmp) != IS_PTR)) {
  392. return NULL;
  393. }
  394. return Z_PTR_P(tmp);
  395. }
  396. static const php_password_algo* php_password_algo_find_zval(zend_string *arg_str, zend_long arg_long, bool arg_is_null) {
  397. if (arg_is_null) {
  398. return php_password_algo_default();
  399. }
  400. if (arg_str) {
  401. return php_password_algo_find(arg_str);
  402. }
  403. switch (arg_long) {
  404. case 0: return php_password_algo_default();
  405. case 1: return &php_password_algo_bcrypt;
  406. #if HAVE_ARGON2LIB
  407. case 2: return &php_password_algo_argon2i;
  408. case 3: return &php_password_algo_argon2id;
  409. #else
  410. case 2:
  411. {
  412. zend_string *n = zend_string_init("argon2i", sizeof("argon2i")-1, 0);
  413. const php_password_algo* ret = php_password_algo_find(n);
  414. zend_string_release(n);
  415. return ret;
  416. }
  417. case 3:
  418. {
  419. zend_string *n = zend_string_init("argon2id", sizeof("argon2id")-1, 0);
  420. const php_password_algo* ret = php_password_algo_find(n);
  421. zend_string_release(n);
  422. return ret;
  423. }
  424. #endif
  425. }
  426. return NULL;
  427. }
  428. zend_string *php_password_algo_extract_ident(const zend_string* hash) {
  429. const char *ident, *ident_end;
  430. if (!hash || ZSTR_LEN(hash) < 3) {
  431. /* Minimum prefix: "$x$" */
  432. return NULL;
  433. }
  434. ident = ZSTR_VAL(hash) + 1;
  435. ident_end = strchr(ident, '$');
  436. if (!ident_end) {
  437. /* No terminating '$' */
  438. return NULL;
  439. }
  440. return zend_string_init(ident, ident_end - ident, 0);
  441. }
  442. const php_password_algo* php_password_algo_identify_ex(const zend_string* hash, const php_password_algo *default_algo) {
  443. const php_password_algo *algo;
  444. zend_string *ident = php_password_algo_extract_ident(hash);
  445. if (!ident) {
  446. return default_algo;
  447. }
  448. algo = php_password_algo_find(ident);
  449. zend_string_release(ident);
  450. return (!algo || (algo->valid && !algo->valid(hash))) ? default_algo : algo;
  451. }
  452. /* {{{ Retrieves information about a given hash */
  453. PHP_FUNCTION(password_get_info)
  454. {
  455. const php_password_algo *algo;
  456. zend_string *hash, *ident;
  457. zval options;
  458. ZEND_PARSE_PARAMETERS_START(1, 1)
  459. Z_PARAM_STR(hash)
  460. ZEND_PARSE_PARAMETERS_END();
  461. array_init(return_value);
  462. array_init(&options);
  463. ident = php_password_algo_extract_ident(hash);
  464. algo = php_password_algo_find(ident);
  465. if (!algo || (algo->valid && !algo->valid(hash))) {
  466. if (ident) {
  467. zend_string_release(ident);
  468. }
  469. add_assoc_null(return_value, "algo");
  470. add_assoc_string(return_value, "algoName", "unknown");
  471. add_assoc_zval(return_value, "options", &options);
  472. return;
  473. }
  474. add_assoc_str(return_value, "algo", php_password_algo_extract_ident(hash));
  475. zend_string_release(ident);
  476. add_assoc_string(return_value, "algoName", algo->name);
  477. if (algo->get_info) {
  478. algo->get_info(&options, hash);
  479. }
  480. add_assoc_zval(return_value, "options", &options);
  481. }
  482. /** }}} */
  483. /* {{{ Determines if a given hash requires re-hashing based upon parameters */
  484. PHP_FUNCTION(password_needs_rehash)
  485. {
  486. const php_password_algo *old_algo, *new_algo;
  487. zend_string *hash;
  488. zend_string *new_algo_str;
  489. zend_long new_algo_long;
  490. bool new_algo_is_null;
  491. zend_array *options = 0;
  492. ZEND_PARSE_PARAMETERS_START(2, 3)
  493. Z_PARAM_STR(hash)
  494. Z_PARAM_STR_OR_LONG_OR_NULL(new_algo_str, new_algo_long, new_algo_is_null)
  495. Z_PARAM_OPTIONAL
  496. Z_PARAM_ARRAY_HT(options)
  497. ZEND_PARSE_PARAMETERS_END();
  498. new_algo = php_password_algo_find_zval(new_algo_str, new_algo_long, new_algo_is_null);
  499. if (!new_algo) {
  500. /* Unknown new algorithm, never prompt to rehash. */
  501. RETURN_FALSE;
  502. }
  503. old_algo = php_password_algo_identify_ex(hash, NULL);
  504. if (old_algo != new_algo) {
  505. /* Different algorithm preferred, always rehash. */
  506. RETURN_TRUE;
  507. }
  508. RETURN_BOOL(old_algo->needs_rehash(hash, options));
  509. }
  510. /* }}} */
  511. /* {{{ Verify a hash created using crypt() or password_hash() */
  512. PHP_FUNCTION(password_verify)
  513. {
  514. zend_string *password, *hash;
  515. const php_password_algo *algo;
  516. ZEND_PARSE_PARAMETERS_START(2, 2)
  517. Z_PARAM_STR(password)
  518. Z_PARAM_STR(hash)
  519. ZEND_PARSE_PARAMETERS_END();
  520. algo = php_password_algo_identify(hash);
  521. RETURN_BOOL(algo && (!algo->verify || algo->verify(password, hash)));
  522. }
  523. /* }}} */
  524. /* {{{ Hash a password */
  525. PHP_FUNCTION(password_hash)
  526. {
  527. zend_string *password, *digest = NULL;
  528. zend_string *algo_str;
  529. zend_long algo_long;
  530. bool algo_is_null;
  531. const php_password_algo *algo;
  532. zend_array *options = NULL;
  533. ZEND_PARSE_PARAMETERS_START(2, 3)
  534. Z_PARAM_STR(password)
  535. Z_PARAM_STR_OR_LONG_OR_NULL(algo_str, algo_long, algo_is_null)
  536. Z_PARAM_OPTIONAL
  537. Z_PARAM_ARRAY_HT(options)
  538. ZEND_PARSE_PARAMETERS_END();
  539. algo = php_password_algo_find_zval(algo_str, algo_long, algo_is_null);
  540. if (!algo) {
  541. zend_argument_value_error(2, "must be a valid password hashing algorithm");
  542. RETURN_THROWS();
  543. }
  544. digest = algo->hash(password, options);
  545. if (!digest) {
  546. if (!EG(exception)) {
  547. zend_throw_error(NULL, "Password hashing failed for unknown reason");
  548. }
  549. RETURN_THROWS();
  550. }
  551. RETURN_NEW_STR(digest);
  552. }
  553. /* }}} */
  554. /* {{{ */
  555. PHP_FUNCTION(password_algos) {
  556. zend_string *algo;
  557. ZEND_PARSE_PARAMETERS_NONE();
  558. array_init(return_value);
  559. ZEND_HASH_FOREACH_STR_KEY(&php_password_algos, algo) {
  560. add_next_index_str(return_value, zend_string_copy(algo));
  561. } ZEND_HASH_FOREACH_END();
  562. }
  563. /* }}} */