sodium_pwhash.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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: Sara Golemon <pollita@php.net> |
  14. +----------------------------------------------------------------------+
  15. */
  16. #ifdef HAVE_CONFIG_H
  17. # include "config.h"
  18. #endif
  19. #include "php.h"
  20. #include "php_libsodium.h"
  21. #include "ext/standard/php_password.h"
  22. #include <sodium.h>
  23. #if SODIUM_LIBRARY_VERSION_MAJOR > 9 || (SODIUM_LIBRARY_VERSION_MAJOR == 9 && SODIUM_LIBRARY_VERSION_MINOR >= 6)
  24. /**
  25. * MEMLIMIT is normalized to KB even though sodium uses Bytes in order to
  26. * present a consistent user-facing API.
  27. *
  28. * Threads are fixed at 1 by libsodium.
  29. *
  30. * When updating these values, synchronize ext/standard/php_password.h values.
  31. */
  32. #define PHP_SODIUM_PWHASH_MEMLIMIT (64 << 10)
  33. #define PHP_SODIUM_PWHASH_OPSLIMIT 4
  34. #define PHP_SODIUM_PWHASH_THREADS 1
  35. static inline int get_options(zend_array *options, size_t *memlimit, size_t *opslimit) {
  36. zval *opt;
  37. *opslimit = PHP_SODIUM_PWHASH_OPSLIMIT;
  38. *memlimit = PHP_SODIUM_PWHASH_MEMLIMIT << 10;
  39. if (!options) {
  40. return SUCCESS;
  41. }
  42. if ((opt = zend_hash_str_find(options, "memory_cost", strlen("memory_cost")))) {
  43. zend_long smemlimit = zval_get_long(opt);
  44. if ((smemlimit < 0) || (smemlimit < crypto_pwhash_MEMLIMIT_MIN >> 10) || (smemlimit > (crypto_pwhash_MEMLIMIT_MAX >> 10))) {
  45. zend_value_error("Memory cost is outside of allowed memory range");
  46. return FAILURE;
  47. }
  48. *memlimit = smemlimit << 10;
  49. }
  50. if ((opt = zend_hash_str_find(options, "time_cost", strlen("time_cost")))) {
  51. *opslimit = zval_get_long(opt);
  52. if ((*opslimit < crypto_pwhash_OPSLIMIT_MIN) || (*opslimit > crypto_pwhash_OPSLIMIT_MAX)) {
  53. zend_value_error("Time cost is outside of allowed time range");
  54. return FAILURE;
  55. }
  56. }
  57. if ((opt = zend_hash_str_find(options, "threads", strlen("threads"))) && (zval_get_long(opt) != 1)) {
  58. zend_value_error("A thread value other than 1 is not supported by this implementation");
  59. return FAILURE;
  60. }
  61. return SUCCESS;
  62. }
  63. static zend_string *php_sodium_argon2_hash(const zend_string *password, zend_array *options, int alg) {
  64. size_t opslimit, memlimit;
  65. zend_string *ret;
  66. if ((ZSTR_LEN(password) >= 0xffffffff)) {
  67. zend_value_error("Password is too long");
  68. return NULL;
  69. }
  70. if (get_options(options, &memlimit, &opslimit) == FAILURE) {
  71. return NULL;
  72. }
  73. ret = zend_string_alloc(crypto_pwhash_STRBYTES - 1, 0);
  74. if (crypto_pwhash_str_alg(ZSTR_VAL(ret), ZSTR_VAL(password), ZSTR_LEN(password), opslimit, memlimit, alg)) {
  75. zend_value_error("Unexpected failure hashing password");
  76. zend_string_release(ret);
  77. return NULL;
  78. }
  79. ZSTR_LEN(ret) = strlen(ZSTR_VAL(ret));
  80. ZSTR_VAL(ret)[ZSTR_LEN(ret)] = 0;
  81. return ret;
  82. }
  83. static bool php_sodium_argon2_verify(const zend_string *password, const zend_string *hash) {
  84. if ((ZSTR_LEN(password) >= 0xffffffff) || (ZSTR_LEN(hash) >= 0xffffffff)) {
  85. return 0;
  86. }
  87. return crypto_pwhash_str_verify(ZSTR_VAL(hash), ZSTR_VAL(password), ZSTR_LEN(password)) == 0;
  88. }
  89. static bool php_sodium_argon2_needs_rehash(const zend_string *hash, zend_array *options) {
  90. size_t opslimit, memlimit;
  91. if (get_options(options, &memlimit, &opslimit) == FAILURE) {
  92. return 1;
  93. }
  94. return crypto_pwhash_str_needs_rehash(ZSTR_VAL(hash), opslimit, memlimit);
  95. }
  96. static int php_sodium_argon2_get_info(zval *return_value, const zend_string *hash) {
  97. const char* p = NULL;
  98. zend_long v = 0, threads = 1;
  99. zend_long memory_cost = PHP_SODIUM_PWHASH_MEMLIMIT;
  100. zend_long time_cost = PHP_SODIUM_PWHASH_OPSLIMIT;
  101. if (!hash || (ZSTR_LEN(hash) < sizeof("$argon2id$"))) {
  102. return FAILURE;
  103. }
  104. p = ZSTR_VAL(hash);
  105. if (!memcmp(p, "$argon2i$", strlen("$argon2i$"))) {
  106. p += strlen("$argon2i$");
  107. } else if (!memcmp(p, "$argon2id$", strlen("$argon2id$"))) {
  108. p += strlen("$argon2id$");
  109. } else {
  110. return FAILURE;
  111. }
  112. sscanf(p, "v=" ZEND_LONG_FMT "$m=" ZEND_LONG_FMT ",t=" ZEND_LONG_FMT ",p=" ZEND_LONG_FMT,
  113. &v, &memory_cost, &time_cost, &threads);
  114. add_assoc_long(return_value, "memory_cost", memory_cost);
  115. add_assoc_long(return_value, "time_cost", time_cost);
  116. add_assoc_long(return_value, "threads", threads);
  117. return SUCCESS;
  118. }
  119. /* argon2i specific methods */
  120. static zend_string *php_sodium_argon2i_hash(const zend_string *password, zend_array *options) {
  121. return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2I13);
  122. }
  123. static const php_password_algo sodium_algo_argon2i = {
  124. "argon2i",
  125. php_sodium_argon2i_hash,
  126. php_sodium_argon2_verify,
  127. php_sodium_argon2_needs_rehash,
  128. php_sodium_argon2_get_info,
  129. NULL,
  130. };
  131. /* argon2id specific methods */
  132. static zend_string *php_sodium_argon2id_hash(const zend_string *password, zend_array *options) {
  133. return php_sodium_argon2_hash(password, options, crypto_pwhash_ALG_ARGON2ID13);
  134. }
  135. static const php_password_algo sodium_algo_argon2id = {
  136. "argon2id",
  137. php_sodium_argon2id_hash,
  138. php_sodium_argon2_verify,
  139. php_sodium_argon2_needs_rehash,
  140. php_sodium_argon2_get_info,
  141. NULL,
  142. };
  143. PHP_MINIT_FUNCTION(sodium_password_hash) /* {{{ */ {
  144. zend_string *argon2i = zend_string_init("argon2i", strlen("argon2i"), 1);
  145. if (php_password_algo_find(argon2i)) {
  146. /* Nothing to do. Core has registered these algorithms for us. */
  147. zend_string_release(argon2i);
  148. return SUCCESS;
  149. }
  150. zend_string_release(argon2i);
  151. if (FAILURE == php_password_algo_register("argon2i", &sodium_algo_argon2i)) {
  152. return FAILURE;
  153. }
  154. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2I", "argon2i", CONST_CS | CONST_PERSISTENT);
  155. if (FAILURE == php_password_algo_register("argon2id", &sodium_algo_argon2id)) {
  156. return FAILURE;
  157. }
  158. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2ID", "argon2id", CONST_CS | CONST_PERSISTENT);
  159. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_MEMORY_COST", PHP_SODIUM_PWHASH_MEMLIMIT, CONST_CS | CONST_PERSISTENT);
  160. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_TIME_COST", PHP_SODIUM_PWHASH_OPSLIMIT, CONST_CS | CONST_PERSISTENT);
  161. REGISTER_LONG_CONSTANT("PASSWORD_ARGON2_DEFAULT_THREADS", PHP_SODIUM_PWHASH_THREADS, CONST_CS | CONST_PERSISTENT);
  162. REGISTER_STRING_CONSTANT("PASSWORD_ARGON2_PROVIDER", "sodium", CONST_CS | CONST_PERSISTENT);
  163. return SUCCESS;
  164. }
  165. /* }}} */
  166. #endif /* HAVE_SODIUM_PASSWORD_HASH */